Подтвердить что ты не робот

Переменная видимость в Java

В следующем коде, почему я не вижу переменную "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();
    }
}
4b9b3361

Ответ 1

От docs:

Как и в случае с методами и переменными экземпляра, внутренний класс ассоциируется с экземпляром его охватывающего класса и имеет прямой доступ к этому методы и поля объектов.

enter image description here

Теперь, взяв второй пример, когда 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/ p >

и обсудили более "почему" здесь: http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04044.html

tl; dr: Поля экземпляра напрямую доступны внутренними классами, локальные поля должны быть окончательными, чтобы быть доступными внутренним классом.

Ответ 2

Java позволяет вам получить доступ к значениям локальных переменных, объявленных final в анонимных классах. Письмо не допускается. Обоснование этого состоит в том, что область действия функции (и, действительно, в этом случае) завершается, пока переменная доступна из внутреннего класса, значение фактически кэшируется в экземплярах анонимного класса. Чтобы избежать путаницы и сделать работу JIT, эти переменные могут быть окончательными. Обратите внимание, что только примитивное значение или ссылка не могут быть изменены, но все, что содержится в ссылочном объекте, может быть изменено.

Во втором случае это переменная экземпляра, доступная для потока. Обратите внимание, что i = 1 теперь стоит Main.this.i; часть Main.this. является неявной.

Ответ 3

В первой программе вы можете "видеть" переменную 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();
    }
}

Демоверсия