我在Java 8中使用lambda,我遇到警告,从lambda表达式引用的局部变量必须是final或有效的final。我知道当我在匿名类中使用变量时,它们在外部类中必须是final,但final和有效final之间的区别是什么?


当前回答

将变量声明为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 Bit上的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为真,不会改变它的状态。因此这段代码是不可达的(在这个答案中有更多内容)。出于同样的原因,第9行的代码也会出错。i的状态不需要求值,因为它是确定的。

另一方面,虽然变量j实际上是final,但它的处理方式不同。没有应用这样的优化。j的状态被求值两次。不管j是否有效为final,字节码都是相同的。

其他回答

... 从Java SE 8开始,局部类可以访问封闭块的final或有效final的局部变量和参数。在初始化后值从未改变的变量或参数实际上是final。

例如,假设变量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编译器生成一个类似于“内部类引用的局部变量必须是final或有效的final”的错误消息,其中内部类PhoneNumber试图访问numberLength变量:

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

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; 

        }
    }    
}

正如其他人所说,在初始化后值从未改变的变量或参数实际上是final的。在上面的代码中,如果你在内部类FirstLevel中改变了x的值,那么编译器会给你一个错误消息:

从lambda表达式引用的局部变量必须是final或有效final。

根据文件:

在初始化后值从未改变的变量或参数实际上是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;
    }
}

' effective final'是一个变量,如果它被'final'附加,则不会产生编译器错误

在“Brian Goetz”的一篇文章中,

非正式地说,如果局部变量的初始值从未改变,那么它实际上就是final变量——换句话说,声明它为final不会导致编译失败。

lambda-state-final- Brian Goetz

当一个变量被初始化一次并且在其所有者类中从未发生变化时,它就是最终的或有效的最终的。我们不能在循环或内部类中初始化它。

最后:

final int number;
number = 23;

有效的最后:

int number;
number = 34;

注意:Final和Effective Final是相似的(赋值后它们的值不会改变),但只是有效的Final变量 没有使用关键字final声明。