最终和有效决赛之间的区别

时间:2014-01-05 19:30:32

标签: java lambda inner-classes final java-8

我在Java 8中玩lambdas,我遇到了警告local variables referenced from a lambda expression must be final or effectively final。我知道当我在匿名类中使用变量时,它们必须在外部类中是最终的,但仍然 - final 有效最终之间有什么区别?

14 个答案:

答案 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;

lambda-state-final- Brian Goetz

答案 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表达式可以访问

  • 静态变量,

  • 实例变量,

  • 有效决赛 方法参数和

  • 有效决赛 局部变量。

来源:OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide, Jeanne Boyarsky, Scott Selikoff

此外,

  

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

我们在第121314行看到的源代码未出现在字节码中。这是因为itrue,并且不会更改其状态。因此,此代码不可访问(在answer中更多)。出于同样的原因,第9行的代码也丢失了。 i的状态不必确定,因为它的状态肯定是true

另一方面,尽管变量j有效地是final ,但它的处理方式不同。没有应用此类优化。 j的状态被评估两次。不管j有效地是 ,字节码都是相同的。

答案 12 :(得分:0)

有效最终变量是一个局部变量,

  1. 未定义为final
  2. 仅分配一次。

最终变量是以下变量:

  1. 用一个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
    }
);