ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在使用 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: ...”。

S
Suresh Atta

...从 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/…
我认为比非有效最终的示例更有用的是某事何时有效最终的示例。虽然描述确实很清楚。如果没有代码更改 Var 的值,则不需要将 Var 声明为 final。
示例不正确。这段代码完美编译(当然没有点)。要获得编译器错误,此代码应位于某个方法内,以便 numberLength 成为此方法的局部变量。
这个例子如此复杂有什么原因吗?为什么大部分代码都在处理完全不相关的正则表达式操作?而且,正如@mykola 已经说过的那样,它完全没有关于有效最终属性的标记,因为它只与局部变量有关,在这个例子中没有局部变量。
M
Maurice Naftalin

我发现解释“有效最终”的最简单方法是想象将 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 是否有效地是最终的。
E
Eurig Jones

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

final int variable = 123;

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

int variable = 123;
variable = 456;

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

int variable = 123;

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


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

根据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 是这里的参数,而不是字段。
A
AndrewF

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

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

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

lambda-state-final- Brian Goetz


这个答案显示为引用,但是在布赖恩的文章中没有这样的确切文本,肯定不是附加的词。这是一个引用:非正式地,如果一个局部变量的初始值从未改变,那么它实际上是最终的——换句话说,声明它是最终的不会导致编译失败。
从文章逐字复制:非正式地,如果局部变量的初始值从未更改,则它实际上是最终的——换句话说,将其声明为最终的不会导致编译失败。
V
Vishwa Ratna

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

最后:

final int number;
number = 23;

有效决赛:

int number;
number = 34;

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


B
Brad Larson

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

N
Novdar

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

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


T
The Scientific Method

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

final double pi = 3.14 ;

它在整个程序中保持 final,不允许在此行之后更改 pi。

有效的 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
            }
        }   
   }    
}

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

        }
    }    
}

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

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


s
snr

如果您可以将 final 修饰符添加到局部变量,那么它实际上是 final 的。

Lambda 表达式可以访问

静态变量,

实例变量,

有效的最终方法参数,以及

有效地最终局部变量。

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

此外,

一个有效的 final 变量是一个其值永远不会改变的变量,但它不是用 final 关键字声明的。

来源:Starting Out with Java: From Control Structures through Objects (6th Edition), Tony Gaddis

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


L
LuCio

声明一个变量 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 通常你是对的,行为可能会改变。这就是为什么我写“可能会导致(取决于编译器)以不同的字节码”。只是因为缺少优化(不同的字节码),我不会认为执行速度会变慢。但是在显示的情况下仍然有所不同。
J
Jimmy_Rw

Effectively final 变量是一个局部变量,它是:

未定义为最终分配给仅一次。

而最终变量是一个变量,它是:

用 final 关键字声明。


F
FiruzzZ

但是,从 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
    }
);

该声明指的是在 <Java 8 中,只有 final 个变量可以访问,但在 Java 8 中那些有效 i> 最后。
我只看到不起作用的代码,无论您使用的是 Java 7 还是 Java 8。