Java — финал в анонимных классах, так же, как макросы в C ++?

Работая с анонимными внутренними классами в Java, вы должны объявить переменные включающего класса, которые вы используете в анонимном внутреннем классе, как final, Хорошо, я понял, почему это должно быть сделано из

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

Это заставило меня бродить, что это final ключевое слово работает как макросы в C \ C ++. До сих пор я использовал final для переменных таким образом, что всякий раз, когда я буду пытаться изменить их (случайно), я получу ошибку, которую нельзя изменить, так как она объявлена ​​как final.Но эта замена мне не понятна.

Вопрос: Согласно выбранному ответу по вышеуказанной ссылке ответчик говорит

Вот почему это не работает:

Переменные lastPrice и price являются локальными переменными в main ()
метод. Объект, который вы создаете с помощью анонимного класса, может сохраняться
до тех пор, пока метод main () не вернется.

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

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

**

Где это хранилище будет заменено позже? Кто о нем заботится? Окончательные переменные просто заменяются значениями?

**

-1

Решение

Нет, это не похоже на макросы в C ++. Разница в том, что макросы оцениваются во время компиляции, а препроцессор заменяет макрос его определением.

final С другой стороны, переменные могут быть вычислены во время выполнения. Однако после установки значение не может быть изменено позднее. Это ограничение позволяет использовать значение во внутреннем классе.

Давайте рассмотрим пример, чтобы сделать это более понятным:

public void func(final int param) {
InnerClass inner = new InnerClass() {
public void innerFunc() {
System.out.println(param);
}
}

inner.innerFunc();
}

Обратите внимание, что param может быть установлен во время выполнения, передавая ему различные значения. Но каждый раз func() называется, новый InnerClass Объект создан и захватывает ток ценность param который гарантированно никогда не изменится, потому что он объявлен как final,

В другой ситуации, когда переменная постоянна, компилятор Можно замените значение во время компиляции. Однако это не является особенным для внутренних классов, потому что константы заменяются во время компиляции, независимо от того, где Они используются.

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

1

Другие решения

@Butterflow от Брайана Гетца:

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

Вы можете найти здесь полную статью о ключевом слове окончательный

1

С анонимными классами вы фактически объявляете «безымянный» вложенный класс. Для вложенных классов компилятор генерирует новый автономный открытый класс с конструктором, который будет принимать все переменные, которые он использует в качестве аргументов (для «именованных» вложенных классов это всегда экземпляр исходного / включающего класса). Это сделано потому, что среда выполнения не имеет понятия о вложенных классах, поэтому необходимо выполнить (автоматическое) преобразование из вложенного в автономный класс.

Возьмите этот код для примера:

public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();

// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}

Это не сработает, потому что это то, что компилятор делает под капотом:

public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();

// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}

Исходный анонимный класс заменяется некоторым отдельным классом, который генерирует компилятор (код не точный, но должен дать вам хорошее представление):

public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}

public void run() {
System.out.println(shared);
}
}

Как вы можете видеть, автономный класс содержит ссылку на общий объект, помните, что все в java передаются по значению, поэтому даже если переменная ссылки ‘shared’ в EnclosingClass изменяется, экземпляр, на который он указывает, не изменяется и все другие ссылочные переменные, указывающие на него (как, например, в анонимном классе: с вложением $ 1), не будут знать об этом. Это основная причина, по которой компилятор вынуждает вас объявлять эти «общие» переменные как окончательные, чтобы этот тип поведения не превращался в уже запущенный код.

Вот что происходит, когда вы используете переменную экземпляра внутри анонимного класса (это то, что вы должны сделать, чтобы решить свою проблему, переместить свою логику в метод «экземпляра» или конструктор класса):

public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();

// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}

Это прекрасно скомпилируется, потому что компилятор изменит код, так что новый сгенерированный класс Enclosing $ 1 будет содержать ссылку на экземпляр EnclosingClass, в котором он был создан (это только представление, но должно помочь вам):

public void someMethod() {
new EnclosingClass$1(this).start();

// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}

public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}

public void run() {
System.out.println(enclosing.shared);
}
}

Подобным образом, когда ссылочная переменная ‘shared’ в EnclosingClass переназначается, и это происходит до вызова Thread # run (), вы увидите, что «другой привет» напечатан дважды, потому что теперь вложенная переменная EnclosingClass $ 1 # будет сохранять ссылку объекту класса, в котором он был объявлен, поэтому изменения любого атрибута этого объекта будут видны экземплярам EnclosingClass $ 1.

Для получения дополнительной информации по этому вопросу, вы можете увидеть это отличное сообщение в блоге (не написано мной): http://kevinboone.net/java_inner.html

1