Что именно происходит, когда у вас есть конечные значения и внутренние классы в методе?
Я столкнулся со многими ситуациями, когда мне нужно было передать значение другому потоку, и я понял, что могу это сделать, но мне было интересно, как он работает?
public void method() {
final EventHandler handle = someReference;
Thread thread = new Thread() {
public void run() {
handle.onEvent();
}
};
thread.start();
}
Изменить: Просто осознайте, что мой вопрос не совсем указывал на то, что я хотел знать. Это больше "как" работает, а не "почему".
Ответы
Ответ 1
Вы можете заметить, что происходит под простой декомпиляцией внутреннего класса. Вот краткий пример:
После компиляции этого фрагмента кода:
public class Test {
public void method() {
final Object handle = new Object();
Thread thread = new Thread() {
public void run() {
handle.toString();
}
};
thread.start();
}
}
вы получите Test.class
для Test.java
и Test$1.class
для внутреннего класса в Test.java
. После декомпиляции Test$1.class
вы увидите следующее:
class Test$1 extends Thread
{
final Test this$0;
private final Object val$handle;
public void run()
{
this.val$handle.toString();
}
}
Как вы можете видеть вместо переменной handle
есть this.val$handle
. Это означает, что handle
копируется как поле val$handle
во внутренний класс. И это будет корректно работать, только если handle
никогда не изменится - что в Java означает, что оно должно быть final
.
Вы также можете заметить, что внутренний класс имеет поле this$0
, которое является ссылкой на внешний класс. Это, в свою очередь, показывает, как нестатические внутренние классы могут взаимодействовать с внешними классами.
Ответ 2
Здесь более длинное объяснение того, как это работает: http://techtracer.com/2008/04/14/mystery-of-accessibility-in-local-inner-classes/
Ответ 3
Никакой метод не может обращаться к локальным переменным других методов. Это включает в себя методы анонимных классов, как в вашем примере. То есть метод run
в анонимном классе Thread не может получить доступ к локальной переменной method()
.
Запись final
перед локальной переменной - это путь для вас, как программиста, чтобы компилятор знал, что переменная на самом деле может рассматриваться как значение. Поскольку эта переменная (read value!) Не изменится, она "нормально" получит доступ к ней в других методах.
Ответ 4
Этот внутренний класс переводится в код, похожий на:
class InnerClass extends Thread {
private EventHandler handle;
public InnerClass(EventHandler handle) {
this.handle = handle;
}
public void run() {
handle.onEvent();
}
}
...
EventHandler handle = someReference;
Thread thread = new InnerClass(handle);
thread.start();
Так как внутренний класс фактически получает переданную копию конечной переменной, он не может вносить в нее никаких изменений, которые будут видны во внешнем классе. Чтобы запретить даже пытаться внести изменения в этот параметр, разрешено использовать только конечные переменные во внутренних классах.