Что именно происходит, когда у вас есть конечные значения и внутренние классы в методе?

Я столкнулся со многими ситуациями, когда мне нужно было передать значение другому потоку, и я понял, что могу это сделать, но мне было интересно, как он работает?

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, которое является ссылкой на внешний класс. Это, в свою очередь, показывает, как нестатические внутренние классы могут взаимодействовать с внешними классами.

Ответ 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();

Так как внутренний класс фактически получает переданную копию конечной переменной, он не может вносить в нее никаких изменений, которые будут видны во внешнем классе. Чтобы запретить даже пытаться внести изменения в этот параметр, разрешено использовать только конечные переменные во внутренних классах.