【问题标题】:Difference between final and effectively final最终和有效最终之间的区别
【发布时间】:2014-01-23 03:44:52
【问题描述】:

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

【问题讨论】:

  • 很多答案,但基本上都是“没有区别”。但这是真的吗?不幸的是,我似乎找不到 Java 8 的语言规范。
  • @AleksandrDubinsky docs.oracle.com/javase/specs
  • @AleksandrDubinsky 不是“真的”真实的。我发现这条规则有一个例外。用常量初始化的局部变量不是编译器的常量表达式。在明确添加 final 关键字之前,您不能将此类变量用于 switch/case 中的 case。例如。 "int k = 1; switch(someInt) { case k: ...".

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


【解决方案1】:

... 从 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 不再是有效的 final。 因此,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 注意:如果引用未更改,即使引用的对象已更改,它实际上也是最终的。
  • @stanleyerror 这可能会有所帮助:stackoverflow.com/questions/4732544/…
  • 我认为比 not 有效最终的示例更有用的是,当某事 有效最终的示例。虽然描述确实很清楚。如果没有代码更改 Var 的值,则不需要将 Var 声明为 final。
  • 示例不正确。这段代码完美编译(当然没有点)。为了得到编译器错误,这段代码应该在某个方法中,以便numberLength成为这个方法的局部变量。
  • 这个例子这么复杂有什么原因吗?为什么大部分代码都在处理完全不相关的正则表达式操作?而且,正如@mykola 已经说过的那样,它完全没有关于 effective final 属性的标记,因为它只与局部变量有关,在这个例子中没有局部变量。
【解决方案2】:

我发现解释“有效最终”的最简单方法是想象将final 修饰符添加到变量声明中。如果通过此更改,程序在编译时和运行时继续以相同的方式运行,则该变量实际上是最终变量。

【讨论】:

  • 这是真的,只要对java 8的“final”的理解很好理解。否则,我会查看一个未声明为 final 的变量,您稍后会对其进行赋值,并错误地认为它不是 final 的。您可能会说“当然”……但并不是每个人都像应有的那样关注最新的语言版本变化。
  • 这个规则的一个例外是用常量初始化的局部变量不是编译器的常量表达式。在明确添加 final 关键字之前,您不能将此类变量用于 switch/case 中的 case。例如。 "int k = 1; switch(someInt) { case k: ...".
  • @HennoVermeulen switch-case 不是此答案中规则的例外。该语言规定case k 需要一个常量表达式,它可以是一个常量变量(“常量变量是原始类型的最终变量或初始化为一个常量表达式“JLS 4.12.4),它是 final 变量的一种特殊情况。
  • 在我的示例中,编译器抱怨 k 不是常量表达式,因此它不能用于切换。添加 final 时,编译行为会发生变化,因为它现在是一个常量变量,并且可以在 switch 中使用。所以你是对的:规则仍然是正确的。它根本不适用于这个例子,也没有说明 k 是否有效。
【解决方案3】:

根据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;
    }
}

【讨论】:

  • 文档谈论局部变量。 bar 在您的示例中不是局部变量,而是一个字段。上述错误消息中的“有效最终”根本不适用于字段。
  • @AnttiHaapala bar 这里是参数,不是字段。
【解决方案4】:

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

final int variable = 123;

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

int variable = 123;
variable = 456;

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

int variable = 123;

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

【讨论】:

  • 答案应该很简单。
  • @Eurig,“所有变量默认为最终变量”需要引用。
  • 当我们可以轻松更改它们的值并有效地“覆盖”最终概念时,为什么它们默认是最终的?
【解决方案5】:

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

来自“Brian Goetz”的一篇文章,

通俗地说,如果一个局部变量的初始值从不改变,那么它实际上就是最终变量——换句话说,将它声明为最终变量不会导致编译失败。

lambda-state-final- Brian Goetz

【讨论】:

  • 这个答案显示为引用,但是在 Brian 的文章中没有这样的确切文本,肯定不是单词 appended。这是一个引用:非正式地,如果一个局部变量的初始值从不改变,那么它实际上是最终的——换句话说,声明它是最终的不会导致编译失败。
  • 来自文章逐字复制:非正式地,如果局部变量的初始值从未改变,则它实际上是最终变量——换句话说,将其声明为最终变量不会导致编译失败。
【解决方案6】:

一个变量是final实际上是final它被初始化一次并且它在它的所有者类中永远不会变异 .而且我们不能在循环内部类中初始化它。

决赛

final int number;
number = 23;

最终有效

int number;
number = 34;

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

【讨论】:

    【解决方案7】:

    当 lambda 表达式使用从其封闭空间分配的局部变量时,有一个重要的限制。 lambda 表达式只能使用值不变的局部变量。该限制被称为“variable capture”,描述为: 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;
            };
        }   
    }
    

    【讨论】:

      【解决方案8】:

      Effective final 主题在JLS 4.12.4 中描述,最后一段包含清晰的解释:

      如果一个变量实际上是 final 的,将 final 修饰符添加到它的声明中不会引入任何编译时错误。相反,在有效程序中声明为 final 的局部变量或参数如果删除 final 修饰符,则变为有效的 final。

      【讨论】:

        【解决方案9】:

        final 是用关键字final 声明的变量,例如:

        final double pi = 3.14 ;
        

        在整个程序中它仍然是final,永远不允许在此行之后更改 pi。

        实际上是最终的:任何局部变量或参数,现在只赋值一次(或只更新一次)。它可能不会在整个计划中保持有效的最终效果。因此这意味着 有效最终 变量可能会在立即分配/更新至少一个分配后立即失去其有效最终属性。示例:

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

        【讨论】:

        • 根据 Java 语言规范,这是不正确的:“每当它出现在赋值表达式的左侧时,它肯定是未赋值的,在赋值之前也不是肯定赋值的。”变量/参数要么总是要么永远不会是最终的。更明确地说,如果您不能在不引入编译错误的情况下将 final 关键字添加到声明中,那么它就不是实际上是最终的。它是这句话的反面:“如果一个变量实际上是 final 的,在它的声明中添加 final 修饰符不会引入任何编译时错误。”
        • 示例代码中的 cmets 不正确,原因是我的评论中描述的所有原因。 “有效地最终”不是一种会随着时间而改变的状态。
        • @AndrewF 如果它不随时间变化,你认为最后一行没有编译是什么? rem 在计算方法的第 1 行实际上是最终的。然而,在最后一行,编译器抱怨 rem 不是有效的 final
        • 您是正确的,需要从代码块中删除某些代码才能进行编译,但这并不反映运行时行为。在编译时,您可以决定一个变量是否是有效的最终变量——根据规范,它要么是总是有效的最终变量,要么是从不有效的最终变量。编译器可以通过静态查看变量在其范围内的使用方式来判断。程序运行时,财产不能获得或丢失。该术语已由规范明确定义 - 查看其他答案,这很好地解释了它。
        【解决方案10】:
        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; 
        
                }
            }    
        }
        

        正如其他人所说,一个变量或参数在初始化后其值永远不会改变,它实际上是最终的。在上面的代码中,如果你改变了内部类FirstLevelx的值,那么编译器会给你错误信息:

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

        【讨论】:

          【解决方案11】:

          如果您可以将 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的含义,它在第一次使用之前被初始化了一次。

          【讨论】:

            【解决方案12】:

            声明一个变量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 实际上是最终的,但它的处理方式不同。没有应用此类优化。 j 的状态被评估两次。无论j实际上是最终的,字节码都是相同的。

            【讨论】:

            • 我认为这是一种编译器效率低下的问题,不一定在较新的编译器中仍然如此。在完美的编译中,如果一个变量实际上是 final 的,那么它将生成与声明的 final 完全相同的优化。因此,不要相信有效 final 会自动比声明 final 慢的概念。
            • @AndrewF 通常你是对的,行为可能会改变。这就是为什么我写了“可能导致(取决于编译器)用不同的字节码”。只是因为缺少优化(不同的字节码),我不会认为执行速度会变慢。但在显示的情况下仍然有所不同。
            【解决方案13】:

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

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

            虽然最终变量是一个变量:

            1. 使用 final 关键字声明。

            【讨论】:

              【解决方案14】:

              但是,从 Java SE 8 开始,本地类可以访问 >enclosure 块的本地变量和参数,它们是 final 或实际上是 final。

              这不是从 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
                  }
              );
              

              【讨论】:

              • 该声明是指在 only final 变量可以访问,但在 Java 8 中 实际上是最终的。
              • 我只看到不起作用的代码,无论您使用的是 Java 7 还是 Java 8。
              猜你喜欢
              • 2018-02-03
              • 2021-03-08
              • 2013-03-02
              • 2020-05-11
              • 1970-01-01
              • 2014-09-14
              • 1970-01-01
              • 2021-01-11
              相关资源
              最近更新 更多