使用匿名类,实际上是在声明一个“无名”的嵌套类。对于嵌套类,编译器会生成一个新的独立的公共类,并为其生成一个构造函数,该构造函数将使用的所有变量作为参数(对于“有名”嵌套类,这始终是原始/封闭类的实例)。这是因为运行时环境没有嵌套类的概念,因此需要从嵌套类到独立类进行(自动)转换。
以这段代码为例:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
System.out.println(shared);
}
}.start();
shared = "other hello";
System.out.println(shared);
}
}
这样做是行不通的,因为编译器在幕后会执行以下操作:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
shared = "other hello";
System.out.println(shared);
}
原本的匿名类被编译器生成了一些独立的类来替代(代码不是精确的,但应该能给您一个很好的想法):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
如您所见,独立类包含对共享对象的引用。请记住,在Java中一切都是传值,因此即使EnclosingClass中的引用变量“shared”被更改,它指向的实例不会被修改,所有其他指向它的引用变量(例如匿名类中的变量:Enclosing$1)也不会意识到这一点。这就是编译器强制您将这些“shared”变量声明为final的主要原因,以便这种类型的行为不会进入已运行的代码。
现在,当您在匿名类中使用实例变量时(这是解决问题的方法,将逻辑移动到“instance”方法或类的构造函数中),会发生以下情况:
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared);
}
}.start();
shared = "other hello";
System.out.println(shared);
}
}
这段代码可以正常编译,因为编译器会修改代码,生成一个名为Enclosing$1的新类,并在其中存储实例化EnclosingClass时的引用(这只是一种表现形式,但应该能帮助您理解)。
public void someMethod() {
new EnclosingClass$1(this).start();
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
就像这样,当EnclosingClass中的引用变量“shared”被重新分配,并且在调用Thread#run()之前发生时,您将看到“other hello”打印两次,因为现在EnclosingClass$1#enclosing变量将保留对其声明所在类的对象的引用,因此对该对象上任何属性的更改都将对EnclosingClass$1的实例可见。
有关此主题的更多信息,请参见此优秀的博客文章(非我撰写):
http://kevinboone.net/java_inner.html