我正在使用 Java 8 中的 lambda,我遇到了警告 local variables referenced from a lambda expression must be final or effectively final
。我知道当我在匿名类中使用变量时,它们在外部类中必须是最终的,但是 - final 和 有效地最终 之间有什么区别?
...从 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
我发现解释“有效最终”的最简单方法是想象将 final
修饰符添加到变量声明中。如果通过此更改,程序在编译时和运行时继续以相同的方式运行,那么该变量实际上是最终的。
case k
需要一个 常量表达式,它可以是一个 常量变量(“常量变量是原始类型的最终变量或初始化为一个常量表达式“JLS 4.12.4),它是 final 变量的一种特殊情况。
下面的这个变量是最终的,所以一旦初始化我们就不能改变它的值。如果我们尝试这样做,我们会得到一个编译错误......
final int variable = 123;
但是如果我们像这样创建一个变量,我们可以改变它的值......
int variable = 123;
variable = 456;
但在 Java 8 中,默认情况下所有变量都是 final 的。但是代码中第二行的存在使得它不是最终的。因此,如果我们从上面的代码中删除第二行,我们的变量现在“实际上是最终的”......
int variable = 123;
所以..任何分配一次且仅一次的变量都是“有效的最终”。
根据docs:
一个变量或参数,其值在初始化后永远不会改变,它实际上是最终的。
基本上,如果编译器发现一个变量没有出现在其初始化之外的赋值中,那么该变量被认为是有效的最终变量。
例如,考虑一些类:
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
是这里的参数,而不是字段。
'Effectively final' 是一个变量,如果它被 'final' 附加,它不会给出编译器错误
来自“Brian Goetz”的一篇文章,
非正式地,如果一个局部变量的初始值从不改变,那么它实际上是最终的——换句话说,声明它是最终的不会导致编译失败。
lambda-state-final- Brian Goetz
当一个变量被初始化一次并且它从未在其所有者类中发生变异时,它就是最终的或有效的最终变量。而且我们不能在循环或内部类中初始化它。
最后:
final int number;
number = 23;
有效决赛:
int number;
number = 34;
注意:Final 和 Effective Final 是相似的(它们的值在赋值后不会改变),只是有效的 Final 变量没有用关键字 final 声明。
当 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;
};
}
}
Effective final 主题在 JLS 4.12.4 中进行了描述,最后一段包含清晰的解释:
如果一个变量实际上是 final 的,将 final 修饰符添加到它的声明中不会引入任何编译时错误。相反,如果 final 修饰符被删除,在有效程序中声明为 final 的局部变量或参数将变为有效的 final。
final 是用关键字 final
声明的变量,例如:
final double pi = 3.14 ;
它在整个程序中保持 final
,不允许在此行之后更改 pi。
有效的 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;
}
}
}
正如其他人所说,初始化后其值永远不会改变的变量或参数实际上是最终的。在上面的代码中,如果你在内部类 FirstLevel
中改变 x
的值,那么编译器会给你错误信息:
从 lambda 表达式引用的局部变量必须是 final 或有效 final。
如果您可以将 final 修饰符添加到局部变量,那么它实际上是 final 的。
Lambda 表达式可以访问
静态变量,
实例变量,
有效的最终方法参数,以及
有效地最终局部变量。
此外,
一个有效的 final 变量是一个其值永远不会改变的变量,但它不是用 final 关键字声明的。
来源:Starting Out with Java: From Control Structures through Objects (6th Edition), 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
并且不会改变它的状态。因此,此代码无法访问(此 answer 中有更多内容)。出于同样的原因,第 9
行的代码也丢失了。 i
的状态不必评估,因为它肯定是 true
。
另一方面,虽然变量 j
实际上是最终的,但它的处理方式不同。没有应用此类优化。 j
的状态被评估两次。无论 j
是否实际上是最终的,字节码都是相同的。
Effectively final 变量是一个局部变量,它是:
未定义为最终分配给仅一次。
而最终变量是一个变量,它是:
用 final 关键字声明。
但是,从 Java SE 8 开始,本地类可以访问 >enclosure 块的局部变量和参数,它们是 final 或有效 final。
这不是从 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 中也那些有效 i> 最后。
不定期副业成功案例分享
numberLength
成为此方法的局部变量。