2013-09-19 4 views
6

Я до сих пор не удовлетворен объяснением анонимного класса и конечного поля. Были тонны вопросов, пытаясь объяснить очевидную проблему, но я не нашел ответы на все мои вопросы :-)Заключительное поле и анонимный класс

Пусть следующий код:

public void method(final int i, int j) { 
    final int z = 6; 
    final int x = j; 
    int k = 5; 
    new Runnable() { 
     public void run() { 
      System.out.print(i); 
      System.out.print(x); 
      System.out.print(z); 
      System.out.print(k); 
     } 
    }; 
} 
  1. Это невозможно скомпилировать этот код из-за " unfinal "k Недвижимость.
  2. Я понимаю, что компилятор может заменить свойство z с объявленным значением во время компиляции.

Когда я искал решение, как именно может работает i и x я нашел этот answer, который говорит:

компилятор может затем просто заменить использование lastPrice и цену в анонимном классе с значения констант (во время компиляции, конечно), и вы не будете иметь проблем с доступом несуществующих переменными больше

Как это может работать на поля i и x если они являются параметрами метода? Они не известны во время компиляции? Этот подход может работать для z.

С другой стороны, есть объяснение относительно stack issues:

Это позволяет компилятору Java, чтобы «захватить» значение переменной во время выполнения и сохранить копию как поле во внутреннем класс. После того, как внешний метод завершается и его кадр стека был удален, исходные переменный исчезают, но частная копия внутреннего класса в упорствует в собственной памяти класса

Я понимаю, что анонимный класс как-то скопировал все необходимое содержание (поля) во время его создания. Отсутствие final имеет очевидную проблему: если какой-то код ниже, объявление анонимного класса изменит значение, возможно выполнение с использованием stale значений.

Но это должно решить проблему, когда метод анонимного класса выполняется из области использования используемых свойств.

Но этот подход должен работать даже без объявления final, поскольку он просто копирует все поля.

Оба подхода кажутся независимыми для меня. Говоря о котором - и он мог решить мои вопросы - я не нашел, как работает поле метода final. Они не удаляются из стека, даже если метод завершается? Для меня это похоже, но это объясняет много вещей :-)

Каков правильный ответ?

ответ

7

Мне кажется, что вы путаетесь между объявляемой переменной final и константой .

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

Так этот код:

public static void method(final int x) { 
    Runnable r = new Runnable() { 
     @Override public void run() { 
      System.out.println(x); 
     } 
    }; 
    r.run(); 
} 

в общих чертах эквивалентен:

public static void method(final int x) { 
    Runnable r = new AnonymousRunnable(x); 
    r.run(); 
} 

private static class AnonymousRunnable implements Runnable { 
    private final int x; 

    AnonymousRunnable(int x) { 
     this.x = x; 
    } 

    @Override public void run() { 
     System.out.println(x); 
    } 
} 

я сделал как метод и вложенный класс статического, чтобы избежать беспокоиться о том, является ли this захвачен или нет.

Локальная переменная должна быть final, когда она захвачена, чтобы избежать ситуаций, которые в противном случае могли бы ввести в заблуждение. Предположим, что было не так - рассмотрим следующий пример:

void method() { 
    int x = 10; 
    Runnable r = new Runnable() { 
     @Override public void run() { 
      System.out.println(x); 
     } 
    }; 
    x = 20; 
    r.run(); // Should this print 10 or 20? 
} 

Используя текущий путь, что анонимные классы работают, но только удалив final ограничение, было бы напечатать 10 ... но разработчики могли бы ожидать, что для печати 20 Аналогично, вы должны рассмотреть, что произойдет, если вы изменили x в методе run. (Как отмечалось в ответе Joop, в Java 8 захваченные локальные переменные являются «фактически окончательными», поэтому они действуют так, как будто вы заявили, что они являются окончательными, но без этого явно.)

В качестве примера другой подход, C# обрабатывает закрытие (для анонимных функций) по-другому, перетаскивая локальные переменные в какой-то анонимный класс, чтобы их можно было изменить . Это более сложный подход, но немного более гибкий. Вы можете найти мой article about closures in Java and C# полезный.

+0

Спасибо за разъяснение. Так я думаю об этом. Поэтому я не понимаю, почему он должен быть определен как «final», когда анонимное создание просто использует моментальный снимок всех используемых свойств и всех. Этот подход работает даже без ключевого слова final, как я писал в моем вопросе. –

+0

Может быть, я этого не понял, но ваш второй пример можно было бы написать без объявления обеих переменных как «final», а первый требует, чтобы 'x' был объявлен окончательным из-за использования внутри анонимного класса, - и это то, что OP попросил нас уточнить. – alfasin

+0

@MartinPodval: Посмотрите, помогает ли мое редактирование ... –

-2

Я думаю, вы запутались, потому что использовали базовые типы. Если вы думаете о ссылках, это должно стать более ясным.

Вы правы при создании анонимного класса копирует все ссылки в свой контекст. И для этого нужно, чтобы все используемые локальные переменные (а параметры - всего лишь один вид локальных переменных) должны быть окончательными. Значит, речь идет не о значении, а о ссылке. И базовые типы - это особый случай в java (это печально). В этом случае они рассматривали как ссылки.

Надеюсь, это прояснит эту последнюю проблему.

+1

Нет, здесь нет особого случая. В каждом случае значение переменной копируется в анонимный класс - является ли это значение ссылкой или примитивным значением. Я не думаю, что есть какая-либо * польза в дифференциации между классами и примитивными типами здесь. –

+0

Я никогда не говорил что-то вроде этого, пожалуйста, прочитайте все правильно, я указал на примере вопроса – Sorontur

+0

Я читал ваш ответ несколько раз, и я до сих пор не вижу, как он вообще полезен ... в частности «Они лечили подобные ссылки в этом случае "просто неправильно. Создание экземпляра анонимного класса копирует * значения * локальных переменных в новый экземпляр. Являются ли эти значения ссылками или примитивными значениями («база» не является частью стандартной терминологии Java) не имеет значения, и ваше внимание к ней отвлекает, ИМО. –

8

Из-за необходимости копировать переменные из метода в анонимный класс (как описано) было принято решение для определения языка, требующее, чтобы скопированная переменная была окончательной. Таким образом, назначение в любом методе или анонимном классе не даст устаревших значений, а код будет более последовательным.

Но! В Java 8 это требование смягчается: final больше не требуется, если переменная de facto final: присваивания запрещены после того, как переменная «скопирована» в анонимном классе.

Это имеет смысл из-за многих функциональных обозначений.В противном случае кнопка actionPerformed внезапно понадобится, чтобы ее параметр был окончательным, распространяя его на другой функциональный вызов.

+0

Ooh - не заметил, что требование удалено в Java 8 (хотя, как вы заметили, это только " явная "часть, которая удалена.) –

+0

Это связано с тем, что Java 8 поддерживает закрытие, поэтому вам не нужно явно объявлять окончательный, больше не требуется (потому что это закрытие вместо этого). Результат такой же. –

Смежные вопросы