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


当前回答

When a lambda expression uses an assigned local variable from its enclosing space there is an important restriction. A lambda expression may only use local variable whose value doesn't change. That restriction is referred as "variable capture" which is described as; lambda expression capture values, not variables. The local variables that a lambda expression may use are known as "effectively final". An effectively final variable is one whose value does not change after it is first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error. Let's see it with an example, we have a local variable i which is initialized with the value 7, with in the lambda expression we are trying to change that value by assigning a new value to i. This will result in compiler error - "Local variable i defined in an enclosing scope must be final or effectively final"

@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;
        };
    }   
}

其他回答

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

最后:

final int number;
number = 23;

有效的最后:

int number;
number = 34;

注意:Final和Effective 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,字节码都是相同的。

... 从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

effective final变量是一个局部变量,即:

未定义为最终 只分配一次。

final变量是这样的变量:

用final关键字声明。