我在Java 8中玩lambdas,我遇到了警告local variables referenced from a lambda expression must be final or effectively final
。我知道当我在匿名类中使用变量时,它们必须在外部类中是最终的,但仍然 - final 和有效最终之间有什么区别?
答案 0 :(得分:209)
...从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不再是最终的。 因此,Java编译器生成类似于“从内部类引用的局部变量必须是最终的或有效的最终”的错误消息,其中内部类PhoneNumber尝试访问numberLength变量:
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
答案 1 :(得分:120)
我发现解释“有效最终”的最简单方法是想象将final
修饰符添加到变量声明中。如果通过此更改,程序在编译时和运行时继续以相同的方式运行,那么该变量实际上是最终的。
答案 2 :(得分:34)
根据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;
}
}
答案 3 :(得分:25)
来自Brian Goetz的文章
,&#39;有效决赛&#39;是一个不会给编译器错误的变量 如果要附加&#39; final&#39;
答案 4 :(得分:19)
下面的这个变量是 final ,因此我们无法在初始化后更改它的值。如果我们试图得到编译错误......
final int variable = 123;
但是如果我们创建这样的变量,我们可以改变它的值...
int variable = 123;
variable = 456;
但是在 Java 8 中,默认情况下所有变量都是 final 。但是代码中第二行的存在使得非最终。因此,如果我们从上面的代码中删除第二行,我们的变量现在“有效最终” ......
int variable = 123;
所以.. 任何一次只分配一次的变量是“有效最终”。
答案 5 :(得分:7)
当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;
};
}
}
答案 6 :(得分:7)
变量最终或有效最终 初始化一次并且其所有者类中从未变异 。我们无法在循环或内部类中初始化。
<强>最终强>:
final int number;
number = 23;
有效决赛:
int number;
number = 34;
答案 7 :(得分:2)
有效的最终主题在JLS 4.12.4中描述,最后一段包含明确的解释:
如果变量实际上是final,则将final修饰符添加到其声明中不会引入任何编译时错误。相反,如果删除了最终修饰符,则在有效程序中声明为final的局部变量或参数将成为最终的。
答案 8 :(得分:1)
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表达式引用的局部变量必须是最终的或有效的最终。
答案 9 :(得分:1)
如果你可以将
final
修饰符添加到局部变量,那就是 实际上是最终的。
Lambda表达式可以访问
静态变量,
实例变量,
有效决赛 方法参数和
有效决赛 局部变量。
此外,
effectively final
变量是一个永远不变的变量 已更改,但未使用final
关键字声明。
来源:Starting Out with Java: From Control Structures through Objects (6th Edition), Tony Gaddis
此外,请不要忘记final
在第一次使用之前初始化一次的含义。
答案 10 :(得分:1)
final 是使用关键字final
声明的变量,例如:
final double pi = 3.14 ;
在整个程序中仍为final
。
有效地定论:任何仅一次分配了值(或仅更新一次)的局部变量或参数。在整个程序中,它可能不会保持有效的最终结果。因此,这意味着有效最终变量可能会在分配/更新至少一个以上分配后立即失去其有效的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
}
}
}
}
答案 11 :(得分:0)
声明变量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
有效地是final ,但它的处理方式不同。没有应用此类优化。 j
的状态被评估两次。不管j
有效地是 ,字节码都是相同的。
答案 12 :(得分:0)
有效最终变量是一个局部变量,
final
最终变量是以下变量:
final
关键字声明。答案 13 :(得分:-6)
但是,从Java SE 8开始,本地类可以访问最终或有效最终的&gt;封闭块的局部变量和参数。
这不是从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
}
);