在下面的代码中,为什么我不能从另一个线程中看到变量“i”?
public class Main {
public static void main(String[] args) {
int i = 0;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(i);
}
}).start();
}
}
为什么我可以在下面的代码中看到它?
public class Main {
int i = 0;
public static void main(String[] args) {
new Main().method();
}
private void method() {
new Thread(new Runnable() {
@Override
public void run() {
i = 1;
}
}).start();
}
}
答案 0 :(得分:25)
来自docs:
与实例方法和变量一样,内部类是关联的 使用其封闭类的实例并可直接访问它 对象的方法和领域。
现在先拿第二个例子,当在父类中声明i
时,内部类可以访问它,因为内部类可以访问整个父对象。 i
从内部类正确引用为Main.this.i
。现在编译器在内部类中秘密地复制this.i
。 this
永远不会在对象内部发生变化,this.i
将指向内部和外部类的正确对象。编译器很高兴。
在第一个示例中,i
不是父类的一部分,它在方法中声明,因此它的引用现在是在堆栈上,而不是在堆上。 (在这种情况下,i
无法生活在上图所示的大外圆上)。现在编译器必须秘密地在内部类中复制i
。但是害怕方法的i
可能会在堆栈上发生变化。编译器不能在这里使用外部类连接到i
,并且每次更改时都无法从堆栈中复制i
引用。因此,决定必须使方法的i
引用不可更改,换句话说,final
。
Java播放的晦涩秘密技巧让您可以从内部类访问外部字段,详细说明如下:http://techtracer.com/2008/04/14/mystery-of-accessibility-in-local-inner-classes/
在这里讨论了更多“为什么”:http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04044.html
答案 1 :(得分:10)
Java仅允许您访问匿名类中声明为final
的局部变量的值。不允许写作。这样做的理由是因为函数范围可能(实际上在这种情况下)退出,而变量可以从内部类中访问,该值实际上是在匿名类实例中缓存的。为了避免混淆并使JIT工作,这些变量只允许是最终的。请注意,只能修改原始值或引用,但仍可以修改引用对象中包含的任何内容。
在第二种情况下,它是线程可访问的实例变量。请注意,i = 1
现在代表Main.this.i
; Main.this.
部分是隐含的。
答案 2 :(得分:6)
在第一个程序中,您可以“查看”变量i
,但不能访问它,因为它未声明为final
。只能访问在创建匿名类实例之前声明的成员变量和final
局部变量:
public class Main {
public static void main(String[] args) {
final int i = 123; // Make the variable final
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(i); // Now it works, but you cannot assign it
}
}).start();
}
}
使变量static
也可以正常运行:
public class Main {
private static int i = 321;
public static void main (String[] args) throws java.lang.Exception
{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(i); // Now it works, but you cannot assign it
}
}).start();
}
}