22

在下面的代码中,为什么我看不到另一个线程中的变量“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();
    }
}
4

3 回答 3

25

来自文档

与实例方法和变量一样,内部类与其封闭类的实例相关联,并且可以直接访问该对象的方法和字段。

在此处输入图像描述

现在先举第二个例子,当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

tl; dr:内部类可以直接访问实例字段,必须将本地字段设置为 final 才能被内部类访问。

于 2013-08-24T10:16:39.050 回答
10

Java 只允许您访问final在匿名类中声明的局部变量的值。不允许书写。这样做的基本原理是因为函数作用域可能(并且在这种情况下确实是)退出,而变量可以从内部类中访问,该值实际上缓存在匿名类实例中。为避免混淆并使 JIT 工作,这些变量只允许为最终变量。请注意,只有原始值或引用不能修改,但引用对象中包含的任何内容仍然可以修改。

在第二种情况下,它是线程可访问的实例变量。请注意,i = 1现在代表Main.this.i; Main.this.隐含的部分。

于 2013-08-24T10:01:55.880 回答
6

在第一个程序中,您可以“看到”变量i,但不能访问它,因为它没有被声明finalfinal只能访问在创建匿名类的实例之前声明的成员变量和局部变量:

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();
    }
}

ideone 上的演示

使您的变量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();
    }
}

演示。

于 2013-08-24T10:01:47.250 回答