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

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