我在用Java 8玩lambda,遇到警告local variables referenced from a lambda expression must be final or effectively final
。我知道,当我在匿名类中使用变量时,它们必须在外部类中为final,但仍然-final和有效的final之间有什么区别?
我在用Java 8玩lambda,遇到警告local variables referenced from a lambda expression must be final or effectively final
。我知道,当我在匿名类中使用变量时,它们必须在外部类中为final,但仍然-final和有效的final之间有什么区别?
Answers:
...从Java SE 8开始,本地类可以访问最终变量或有效最终变量的局部变量和封闭块的参数。变量或参数的值在初始化后从未更改,实际上是最终的。
例如,假设numberLength
未将变量声明为final,则在PhoneNumber
构造函数中添加了标记的赋值语句:
public class OutterClass {
int numberLength; // <== not *final*
class PhoneNumber {
PhoneNumber(String phoneNumber) {
numberLength = 7; // <== assignment to numberLength
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
...
}
...
}
由于该赋值语句,变量numberLength不再有效地变为final。结果,Java编译器生成一条错误消息,类似于“内部类引用的本地变量必须是最终的或实际上是最终的”,其中内部类PhoneNumber尝试访问numberLength变量:
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
numberLength
成为此方法的局部变量。
我发现解释“有效最终”的最简单方法是想象将final
修饰符添加到变量声明中。如果通过此更改,程序在编译时和运行时都继续以相同的方式运行,则该变量实际上是最终变量。
case k
需要一个常量表达式,该常量表达式可以是常量变量(“常量变量是原始类型或使用常量表达式初始化的String类型的最终变量” JLS 4.12.4),这是final的特殊情况。变量。
根据文档:
变量或参数的值在初始化后从未更改,实际上是最终的。
基本上,如果编译器发现变量没有在其初始化之外的赋值中出现,则该变量实际上被认为是final。
例如,考虑一些类:
public class Foo {
public void baz(int bar) {
// While the next line is commented, bar is effectively final
// and while it is uncommented, the assignment means it is not
// effectively final.
// bar = 2;
}
}
bar
在您的示例中,不是本地变量,而是字段。上面的错误消息中的“有效最终”根本不适用于该字段。
bar
是此处的参数,而不是字段。
“有效地最终”是一个变量,如果要附加“最终”,则不会给出编译器错误
从“ Brian Goetz”的文章中,
非正式地,如果局部变量的初始值从未改变,则它实际上是最终的-换句话说,声明它的最终不会导致编译失败。
下面的这个变量是final,所以初始化后就无法更改其值。如果我们尝试这样做,将会出现编译错误...
final int variable = 123;
但是,如果我们创建这样的变量,我们可以更改它的值...
int variable = 123;
variable = 456;
但是在Java 8中,默认情况下所有变量都是final。但是代码中第二行的存在使它成为非最终的。因此,如果我们从上面的代码中删除第二行,那么我们的变量现在是“有效的最终值” ...
int variable = 123;
因此,任何仅分配一次的变量都是“有效最终”变量。
当lambda表达式从其封闭空间使用分配的局部变量时,存在一个重要的限制。Lambda表达式只能使用其值不变的局部变量。该限制称为“ 变量捕获 ”,描述为:lambda表达式捕获值,而不是变量。
lambda表达式可以使用的局部变量称为“ 有效最终 ”。
有效的最终变量是在首次赋值后其值不变的变量。无需将此类变量显式声明为final,尽管这样做不会出错。
让我们看一个例子,我们有一个局部变量i,它的初始值是7,在lambda表达式中,我们试图通过给i赋一个新值来改变该值。这将导致编译器错误-“ 在封闭范围内定义的局部变量必须是最终的或实际上是最终的 ”
@FunctionalInterface
interface IFuncInt {
int func(int num1, int num2);
public String toString();
}
public class LambdaVarDemo {
public static void main(String[] args){
int i = 7;
IFuncInt funcInt = (num1, num2) -> {
i = num1 + num2;
return i;
};
}
}
有效的最终主题在JLS 4.12.4中进行了描述,最后一段包含清晰的解释:
如果变量实际上是final变量,则将final修饰符添加到其声明中不会引入任何编译时错误。相反,如果删除了final修饰符,则在有效程序中声明为final的局部变量或参数实际上将变为final。
final是使用关键字声明的变量final
,例如:
final double pi = 3.14 ;
它仍然final
贯穿整个程序。
最终有效:任何局部变量或参数,现在仅分配一次值(或仅更新一次)。在整个程序中,它可能无法保持有效的最终状态。因此,这意味着有效最终变量可能会在分配/更新至少一个以上分配后立即失去其有效最终属性。例:
class EffectivelyFinal {
public static void main(String[] args) {
calculate(124,53);
}
public static void calculate( int operand1, int operand2){
int rem = 0; // operand1, operand2 and rem are effectively final here
rem = operand1%2 // rem lost its effectively final property here because it gets its second assignment
// operand1, operand2 are still effectively final here
class operators{
void setNum(){
operand1 = operand2%2; // operand1 lost its effectively final property here because it gets its second assignment
}
int add(){
return rem + operand2; // does not compile because rem is not effectively final
}
int multiply(){
return rem * operand1; // does not compile because both rem and operand1 are not effectively final
}
}
}
}
final
不引入编译错误的情况下将关键字添加到声明中,那么它实际上不是final。这与该语句相反:“如果变量实际上是最终变量,则在其声明中添加final修饰符不会引入任何编译时错误。”
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
// The following statement causes the compiler to generate
// the error "local variables referenced from a lambda expression
// must be final or effectively final" in statement A:
//
// x = 99;
}
}
}
就像其他人所说的那样,变量或参数的值在初始化之后再也不会改变,实际上是最终的。在上面的代码中,如果您更改x
内部类中的值,FirstLevel
则编译器将给您错误消息:
从lambda表达式引用的局部变量必须是final或有效的final。
如果可以将
final
修饰符添加到局部变量,则它实际上是 最终的。
Lambda表达式可以访问
静态变量
实例变量
有效的最终方法参数,以及
有效地最终局部变量。
来源:OCP:Oracle认证专业Java SE 8程序员II学习指南,Jeanne Boyarsky,Scott Selikoff
另外,
一个
effectively final
变量是一个变量,其值永远不会改变,但它不与声明的final
关键字。
资料来源:从Java入手:从控制结构到对象(第6版),Tony Gaddis
此外,不要忘记final
在第一次使用它之前将其初始化一次的含义。
声明变量final
或不声明变量final
,但将其有效地保持为最终变量可能会导致不同的字节码(取决于编译器)。
让我们看一个小例子:
public static void main(String[] args) {
final boolean i = true; // 6 // final by declaration
boolean j = true; // 7 // effectively final
if (i) { // 9
System.out.println(i);// 10
}
if (!i) { // 12
System.out.println(i);// 13
}
if (j) { // 15
System.out.println(j);// 16
}
if (!j) { // 18
System.out.println(j);// 19
}
}
方法的相应字节码main
(Windows 64位上的Java 8u161):
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iconst_1
3: istore_2
4: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iconst_1
8: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
11: iload_2
12: ifeq 22
15: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_2
19: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
22: iload_2
23: ifne 33
26: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
29: iload_2
30: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
33: return
对应的行号表:
LineNumberTable:
line 6: 0
line 7: 2
line 10: 4
line 15: 11
line 16: 15
line 18: 22
line 19: 26
line 21: 33
正如我们所看到的行源代码12
,13
,14
不会出现在字节码。那是因为i
是true
并且不会改变它的状态。因此,此代码不可访问(在此答案中更多)。出于同样的原因,行上的代码9
也丢失了。i
因为是true
肯定的,所以不必评估的状态。
另一方面,尽管变量j
实际上是最终变量,但它的处理方式不同。没有应用此类优化。的状态j
被评估两次。字节码是不分相同的j
是有效的最后。
但是,从Java SE 8开始,本地类可以访问最终或有效最终的>封闭块的局部变量和参数。
这不是从Java 8开始的,我已经使用了很长时间了。此代码在Java 8之前使用是合法的:
String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ann = str; // <---- error, must be final (IDE's gives the hint);
String ann = strFin; // <---- legal;
String str = "legal statement on java 7,"
+"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl.";
//we are forced to use another name than str
}
);
final
变量可以被访问,但在Java 8 也是那些有效的决赛。