Почему анонимный класс может получить доступ к члену класса non-final класса
Мы знаем, что только конечные локальные переменные могут быть доступны в анонимном классе, и здесь есть веская причина: Почему только конечные переменные доступны в анонимном классе?.
Однако я обнаружил, что анонимный класс может получить доступ к переменной, отличной от конечной, если переменная является полем-членом класса-оболочки: Как я могу получить доступ к переменным экземпляра класса класса из внутри анонимного класса?
Я смущен. Мы гарантируем доступ только к конечной локальной переменной в анонимном классе, потому что мы не хотим, чтобы переменная была несинхронизирована между анонимным классом и локальной функцией. Та же причина должна быть применима к этому случаю, если мы попытаемся получить доступ к члену класса, не входящему в класс, в анонимном классе.
Почему это не вызывает беспокойства?
Ответы
Ответ 1
В случае локальной переменной копия переменной - это то, что получает экземпляр анонимного класса. По этой причине локальная переменная должна быть сделана final
, прежде чем ее можно будет использовать в анонимном классе, чтобы ее значение не изменилось позже.
В случае поля члена охватывающего класса копия отсутствует. Скорее, анонимный класс получает ссылку на охватывающий класс и тем самым обращается к любым/всем полям и методам внешнего класса. Таким образом, даже если значение поля изменяется, это изменение отражается и в анонимном классе, поскольку оно является той же ссылкой.
Я смущен. Мы гарантируем, что только конечная локальная переменная может быть доступ в анонимный класс, потому что мы не хотим, чтобы переменная должен быть несинхронизирован между анонимным классом и локальной функцией. та же причина должна применяться к делу, если мы попытаемся получить доступ к нефиналу включение класса в анонимный класс.
Как вы видите, это не так. Копирование происходит только для локальных переменных, а не для полей-членов охватывающего класса. Разумеется, причина в том, что анонимный класс содержит неявную ссылку на охватывающий класс, и через эту ссылку он может обращаться к любым/всем полям и методам внешнего класса.
Чтобы процитировать ссылку ниже:
Членская переменная существует во время жизни объекта-объекта, поэтому на него может ссылаться внутренний экземпляр класса. Однако локальная переменная существует только во время вызова метода и по-разному обрабатывается компилятором тем, что неявная копия его генерируется как член внутреннего класса. Не объявляя конечную локальную переменную, ее можно было бы изменить, что привело к тонким ошибкам из-за того, что внутренний класс все еще ссылался на исходное значение этой переменной.
Литература:
1. Почему не конечная "локальная" переменная не может быть использована внутри внутреннего класса, а вместо этого не конечное поле охватывающий класс может?.
Ответ 2
Нестатические/Внутренние классы имеют ссылку на прилагаемый экземпляр. Поэтому они могут косвенно ссылаться на переменные экземпляра и методы.
Если это параметр, даже охватывающий класс ничего не знает об этом, потому что он доступен только из метода, который определил эту переменную.
Подобно Y.S. уже указано:
В случае локальной переменной копия переменной - это то, что экземпляр анонимного класса получает
Ответ 3
Совсем наоборот. Локальная переменная, например x
вне анонимного экземпляра, живет до тех пор, пока вызов метода. Его ссылка на объект хранится в стеке вызовов; x == адрес в стеке, содержащий ссылку на объект. x
внутри анонимного экземпляра - это копия, поскольку ее время жизни отличается, длиннее или даже короче.
Так как теперь есть две переменные, было решено не разрешать присваивание x
(странно реализовать) и требовать, чтобы переменная была "фактически окончательной".
Доступ к внешнему элементу - это потому, что анонимный экземпляр имеет внутренний класс, также содержащий ссылку OuterClass.this.
Ответ 4
Рассмотрим следующий пример
class InnerSuper{
void mInner(){}
}
class Outer{
int aOuter=10;
InnerSuper mOuter(){
int aLocal=3999;
class Inner extends InnerSuper{
int aInner=20;
void mInner(){
System.out.println("a Inner : "+aInner);
System.out.println("a local : "+aLocal);
}
}
Inner iob=new Inner();
return iob;
}
}
class Demo{
public static void main(String args[]){
Outer ob=new Outer();
InnerSuper iob=ob.mOuter();
iob.mInner();
}
}
Это не приведет к возникновению ошибок в Java 1.8 или выше. Но в предыдущей версии это порождает ошибку с запросом явно объявить локальную переменную, доступную во внутреннем классе, как final.
Поскольку то, что делает компилятор, это сохранение копии локальной переменной, доступ к которой осуществляется внутренним классом, чтобы копия существовала, даже если метод/блок заканчивается, а локальная переменная выходит за пределы области видимости. Он просит нас объявить его final, потому что если переменная изменяет свое значение динамически позже в программе после объявления локального внутреннего класса или анонимного класса, копия, созданная компилятором, не изменится на новое значение и может вызвать проблемы во внутреннем классе, не создавая ожидаемого результата. Поэтому он советует нам объявить его явно окончательным.
Но в Java 1.8 он не будет генерировать ошибку, поскольку компилятор объявляет, что доступная локальная переменная неявно окончательна.
В документе Java Docs указано следующее:
Анонимный класс не может получить доступ к локальным переменным в своем приложении которые не объявлены окончательными или фактически окончательными.
Позвольте мне объяснить, что подразумевается под эффективным финалом. Рассмотрим следующую измененную версию вышеуказанной программы
class Outer{
int aOuter=10;
InnerSuper mOuter(){
int aLocal=3999;
class Inner extends InnerSuper{
int aInner=20;
void mInner(){
System.out.println("a Inner : "+aInner);
System.out.println("a local : "+aLocal);
}
}
aLocal=4000;
Inner iob=new Inner();
return iob;
}
}
Даже в Java 1.8 это приведет к ошибке. Это связано с тем, что aLocal динамически назначается внутри программы. Это означает, что переменная не может считаться компилятором эффективно окончательной. Как я понял, компилятор объявляет переменные, которые не изменяются динамически как окончательные. Это называется переменной эффективно окончательной.
Следовательно, рекомендуется объявлять локальные переменные, к которым обращается локальный внутренний класс или анонимный класс, явно окончательный, чтобы избежать ошибок.