区别在于局部变量与类成员变量之间。成员变量存在于封闭对象的生命周期中,因此它可以被内部类实例引用。然而,局部变量仅在方法调用期间存在,并且由编译器以不同方式处理,因为它的隐式副本作为内部类的成员生成。如果不声明局部变量 final,可以更改它,由于内部类仍然引用该变量的原始值而导致细微错误。
最终局部变量
我知道将局部变量或参数设为 final 有两个原因。第一个原因是您不希望代码更改局部变量或参数。许多人认为更改方法内的参数是不好的风格,因为它会使代码不清楚。作为一种习惯,一些程序员将所有参数设置为“最终”以防止自己更改它们。我不这样做,因为我发现它使我的方法签名有点难看。
第二个原因是当我们想从内部类中访问局部变量或参数时。据我所知,这就是最终局部变量和参数在 JDK 1.1 中被引入 Java 语言的实际原因。
public class Access1 {
public void f() {
final int i = 3;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(i);
}
};
}
}
在 run() 方法中,只有在外部类中将其设为 final 时,我们才能访问 i。要理解其中的道理,我们必须
看看编译器做了什么。它生成两个文件,Access1.class 和 Access1$1.class。当我们用 JAD 反编译它们时,我们得到:
public class Access1 {
public Access1() {}
public void f() {
Access1$1 access1$1 = new Access1$1(this);
}
}
和
class Access1$1 implements Runnable {
Access1$1(Access1 access1) {
this$0 = access1;
}
public void run() {
System.out.println(3);
}
private final Access1 this$0;
}
由于 i 的值是最终的,编译器可以将其“内联”到内部
班级。在我看到上面的内容之前,局部变量必须是最终的才能被内部类访问,这让我感到不安。
当局部变量的值可以针对内部类的不同实例发生变化时,编译器将其添加为内部类的数据成员,并在构造函数中对其进行初始化。这背后的根本原因是Java没有指针,而C有。
考虑以下类:
public class Access2 {
public void f() {
for (int i=0; i<10; i++) {
final int value = i;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(value);
}
};
}
}
}
这里的问题是我们每次通过for循环时都必须创建一个新的本地数据成员,所以我今天有一个想法
在编码时,是将上述代码更改为以下内容:
public class Access3 {
public void f() {
Runnable[] runners = new Runnable[10];
for (final int[] i={0}; i[0]<runners.length; i[0]++) {
runners[i[0]] = new Runnable() {
private int counter = i[0];
public void run() {
System.out.println(counter);
}
};
}
for (int i=0; i<runners.length; i++)
runners[i].run();
}
public static void main(String[] args) {
new Access3().f();
}
}
我们现在不必声明额外的最终局部变量。事实上,这难道不是真的吗?
int[] i 就像一个指向 int 的通用 C 指针?我花了 4 年时间才看到这一点,但如果您在其他地方听到过这个想法,我想听听您的意见。