final和effective final的区别

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

197101 次浏览

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

根据文档:

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


}
}
}

正如其他人所说,在初始化后值从未改变的变量或参数实际上是final的。在上面的代码中,如果你在内部类FirstLevel中改变了x的值,那么编译器会给你一个错误消息:

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

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

但是,从Java SE 8开始,局部类可以访问>外围块的局部变量和参数,这些变量和参数是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
}
);
当lambda表达式使用来自其封闭空间的赋值局部变量时,有一个重要的限制。lambda表达式只能使用值不变的局部变量。该限制被称为“变量捕获”,它被描述为;Lambda表达式捕获值,而不是变量
lambda表达式可以使用的局部变量称为“有效的最终”。
有效最终变量是指其值在第一次赋值后不会改变的变量。没有必要显式地将这样一个变量声明为final,尽管这样做不会出错 让我们用一个例子来看看,我们有一个局部变量i,初始值为7,在lambda表达式中,我们试图通过给i赋一个新值来改变这个值。这将导致编译器错误- "在封闭范围内定义的局部变量i必须是final或有效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;
};
}
}

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

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

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

lambda-state-final- Brian Goetz

变量在它只初始化了一次时为最后有效的最终,并且在其所有者类中从不为突变。我们在循环内部类不能初始化它。

最后:

final int number;
number = 23;

有效的最终:

int number;
number = 34;

请注意: 最后有效的最终是类似的(赋值后它们的值不会改变),但只是有效的Final变量

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

final int variable = 123;

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

int variable = 123;
variable = 456;

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

int variable = 123;

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

如果你可以将final修饰符添加到一个局部变量,它是 有效的决赛。< / em >

Lambda表达式可以访问

  • 静态变量,

  • 实例变量,

  • <李> < p >有效决赛 方法参数,

    <李> < p >有效决赛 李本地变量。< / p > < / >

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

此外,

effectively final变量是一个值为never的变量 改变了,但是没有使用final关键字来声明

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

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

有效的最终主题在JLS 4.12.4中描述,最后一段包含一个明确的解释:

如果变量实际上是final,在其声明中添加final修饰符将不会引入任何编译时错误。相反,在有效程序中声明为final的局部变量或参数,如果final修饰符被移除,则变为有效的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
}
}
}
}

声明变量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,不会改变它的状态。因此,这段代码是不可达的(更多内容在回答中)。出于同样的原因,第9行的代码也会出错。i的状态不需要计算,因为它肯定是true

另一方面,虽然变量j有效的最终,但它不是以同样的方式处理的。没有应用这样的优化。j的状态被求值两次。不管j是否为有效的最终,字节码都是相同的。

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

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

final变量是这样的变量:

  1. final关键字声明。