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


当前回答

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

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

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

lambda-state-final- Brian Goetz

其他回答

将变量声明为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,字节码都是相同的。

如果可以将最后一个修饰符添加到局部变量,那么就是 有效的决赛。

Lambda表达式可以访问

静态变量, 实例变量, 有效的最终 方法参数,以及 有效的最终 局部变量。

来源:OCP: Oracle认证专业Java SE 8程序员II学习指南,Jeanne Boyarsky, Scott Selikoff

此外,

有效最终变量是一个值为never的变量 更改了,但没有使用final关键字声明。

来源:从Java开始:从控制结构到对象(第6版),Tony Gaddis

此外,不要忘记final的含义,它在第一次使用之前只初始化一次。

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

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

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

lambda-state-final- Brian Goetz

下面这个变量是最终的,所以我们不能改变它的值一旦初始化。如果我们尝试,我们会得到一个编译错误…

final int variable = 123;

但是如果我们创建一个这样的变量,我们可以改变它的值…

int variable = 123;
variable = 456;

但是在Java 8中,默认情况下所有变量都是final。但是代码中第二行的存在使得它不是最终的。因此,如果我们从上面的代码中删除第二行,我们的变量现在是“有效的final”…

int variable = 123;

所以. .任何赋值一次且仅一次的变量都是“有效的最终变量”。

根据文件:

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