a
может быть только окончательным здесь. Почему? Как я могу передатьa
вonClick()
метод , не держа его в качестве частного члена?private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
Как я могу вернуть,
5 * a
когда он нажал? Я имею в виду,private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); }
java
event-handling
anonymous-class
Андрей Абрамов
источник
источник
Ответы:
Как отмечено в комментариях, часть этого становится неактуальной в Java 8, где это
final
может быть неявным. Однако в анонимном внутреннем классе или лямбда-выражении можно использовать только эффективно окончательную переменную.В основном это связано с тем, как Java управляет замыканиями .
Когда вы создаете экземпляр анонимного внутреннего класса, любые переменные, которые используются в этом классе, копируются в свои значения через автоматически сгенерированный конструктор. Это избавляет компилятор от необходимости автоматически генерировать различные дополнительные типы для хранения логического состояния «локальных переменных», как, например, это делает компилятор C # ... (Когда C # захватывает переменную в анонимной функции, он действительно захватывает переменную - Закрытие может обновить переменную таким образом, который виден основной частью метода, и наоборот.)
Поскольку значение было скопировано в экземпляр анонимного внутреннего класса, выглядело бы странно, если бы переменная могла быть изменена остальной частью метода - у вас мог бы быть код, который, казалось, работал с устаревшей переменной ( потому что именно так и происходит ... вы работаете с копией, сделанной в другое время). Аналогично, если бы вы могли вносить изменения в анонимном внутреннем классе, разработчики могли бы ожидать, что эти изменения будут видны в теле включающего метода.
Если сделать окончательную переменную, все эти возможности будут удалены - поскольку значение не может быть изменено вообще, вам не нужно беспокоиться о том, будут ли такие изменения видны. Единственный способ позволить методу и анонимному внутреннему классу видеть изменения друг друга - это использовать изменяемый тип некоторого описания. Это может быть сам класс включения, массив, изменяемый тип-обертка ... что-нибудь в этом роде. По сути, это немного похоже на обмен данными между одним методом и другим: изменения, внесенные в параметры одного метода, не отображаются его вызывающей стороной, но видны изменения, внесенные в объекты, на которые ссылаются параметры.
Если вас интересует более подробное сравнение между замыканиями Java и C #, у меня есть статья, которая углубляется в это. Я хотел сосредоточиться на стороне Java в этом ответе :)
источник
final
(но он все еще должен быть эффективно окончательным).Есть хитрость, которая позволяет анонимному классу обновлять данные во внешней области.
Однако этот прием не очень хорош из-за проблем с синхронизацией. Если обработчик вызывается позже, вам необходимо: 1) синхронизировать доступ к res, если обработчик был вызван из другого потока; 2) необходимо иметь какой-либо флаг или указатель того, что res был обновлен.
Этот прием работает нормально, если анонимный класс вызывается в том же потоке немедленно. Подобно:
источник
List.forEach
код безопасен.Анонимный класс - это внутренний класс, и к внутренним классам применяется строгое правило (JLS 8.1.3) :
Я еще не нашел причину или объяснение для jls или jvms, но мы знаем, что компилятор создает отдельный файл класса для каждого внутреннего класса, и он должен убедиться, что методы объявлены в этом файле класса ( на уровне байтового кода) по крайней мере, иметь доступ к значениям локальных переменных.
(У Джона есть полный ответ - я держу этот ответ без восстановления, потому что кто-то может заинтересоваться правилом JLS)
источник
Вы можете создать переменную уровня класса, чтобы получить возвращаемое значение. я имею в виду
Теперь вы можете получить значение K и использовать его там, где хотите.
Ответ вашего почему:
Локальный экземпляр внутреннего класса привязан к классу Main и может обращаться к последним локальным переменным содержащего его метода. Когда экземпляр использует последний локальный элемент содержащего его метода, переменная сохраняет значение, которое оно содержало во время создания экземпляра, даже если переменная вышла из области видимости (это, по сути, грубая ограниченная версия замыканий Java).
Поскольку локальный внутренний класс не является ни членом класса, ни пакета, он не объявлен с уровнем доступа. (Однако, имейте в виду, что его собственные члены имеют уровни доступа, как в обычном классе.)
источник
Ну, в Java переменная может быть конечной не только как параметр, но и как поле уровня класса, например
или как локальная переменная, как
Если вы хотите получить доступ и изменить переменную из анонимного класса, вы можете сделать переменную переменной уровня класса во включающем классе.
Вы не можете иметь переменную как final и дать ей новое значение.
final
означает только это: значение является неизменным и окончательным.И поскольку он окончательный, Java может безопасно скопировать его в локальные анонимные классы. Вы не получаете некоторую ссылку на int (тем более что у вас не может быть ссылок на примитивы типа int в Java, только ссылки на объекты ).
Он просто копирует значение a в неявный int, называемый a в вашем анонимном классе.
источник
static
. Может быть, это будет более понятно, если вместо этого использовать «переменную экземпляра».Причина, по которой доступ был ограничен только локальными конечными переменными, заключается в том, что если бы все локальные переменные были сделаны доступными, то сначала их нужно было бы скопировать в отдельный раздел, где внутренние классы могли бы иметь к ним доступ и поддерживать несколько копий изменяемые локальные переменные могут привести к противоречивым данным. Принимая во внимание, что конечные переменные являются неизменяемыми, и, следовательно, любое количество копий в них не окажет влияния на согласованность данных.
источник
Int
переназначается внутри замыкания, измените эту переменную на экземплярIntRef
(по сути, изменяемыйInteger
упаковщик). Каждый доступ к переменной затем переписывается соответственно.Чтобы понять причину этого ограничения, рассмотрите следующую программу:
InterfaceInstance остается в памяти после инициализации метод возвращает, но параметр Вэл не делает. JVM не может получить доступ к локальной переменной за пределами своей области, поэтому Java заставляет последующий вызов printInteger работать, копируя значение val в неявное поле с тем же именем в interfaceInstance . InterfaceInstance , как говорят, захватили значение локального параметра. Если параметр не был окончательным (или фактически окончательным), его значение могло бы измениться, не синхронизироваться с зафиксированным значением, что может привести к неинтуитивному поведению.
источник
Методы внутри анонимного внутреннего класса могут вызываться задолго после того, как поток, который его породил, завершился. В вашем примере внутренний класс будет вызываться в потоке диспетчеризации событий, а не в том же потоке, в котором он был создан. Следовательно, область действия переменных будет другой. Поэтому для защиты таких проблем области назначения переменных вы должны объявить их окончательными.
источник
Когда в теле метода определен анонимный внутренний класс, все переменные, объявленные как final в области действия этого метода, доступны из внутреннего класса. Для скалярных значений, после того, как оно было назначено, значение конечной переменной не может измениться. Для значений объекта ссылка не может быть изменена. Это позволяет компилятору Java «захватывать» значение переменной во время выполнения и сохранять копию как поле во внутреннем классе. После завершения внешнего метода и удаления его стекового фрейма исходная переменная исчезает, но личная копия внутреннего класса сохраняется в собственной памяти класса.
( http://en.wikipedia.org/wiki/Final_%28Java%29 )
источник
источник
Поскольку у Джона есть подробные сведения о реализации, другой возможный ответ будет состоять в том, что JVM не хочет обрабатывать запись в записи, которая закончила его активацию.
Рассмотрим вариант использования, когда ваши лямбды вместо того, чтобы их применять, хранятся в каком-то месте и запускаются позже.
Я помню, что в Smalltalk вы бы подняли нелегальный магазин, когда вы сделаете такую модификацию.
источник
Попробуйте этот код,
Создайте Array List, поместите в него значение и верните его:
источник
Java анонимный класс очень похож на закрытие Javascript, но Java реализует это по-другому. (проверьте ответ Андерсена)
Поэтому, чтобы не путать Java-разработчика со странным поведением, которое может возникнуть у тех, кто пришел из Javascript. Я думаю, именно поэтому они заставляют нас использовать
final
, это не ограничение JVM.Давайте посмотрим на пример Javascript ниже:
В Javascript
counter
значение будет 100, потому что есть только однаcounter
переменная от начала до конца.Но в Java, если его нет
final
, он будет распечатан0
, поскольку во время создания внутреннего объекта0
значение копируется в скрытые свойства объекта внутреннего класса. (здесь есть две целочисленные переменные, одна в локальном методе, другая в скрытых свойствах внутреннего класса)Поэтому любые изменения после создания внутреннего объекта (например, строка 1) не будут влиять на внутренний объект. Таким образом, это приведет к путанице между двумя различными результатами и поведением (между Java и Javascript).
Я считаю, что именно поэтому Java решает сделать его окончательным, поэтому данные «согласованы» от начала до конца.
источник
Конечная переменная Java внутри внутреннего класса
внутренний класс может использовать только
Object
...)int
Тип value (примитив) (например ...) может быть заключен в окончательный ссылочный тип.IntelliJ IDEA
может помочь вам преобразовать его в один массив элементовКогда a
non static nested
(inner class
) [About] генерируется компилятором - создается новый класс -<OuterClass>$<InnerClass>.class
и связанные параметры передаются в конструктор [Local variable on stack] . Это похоже на закрытиеПоследняя переменная - это переменная, которую нельзя переназначить. окончательная ссылочная переменная все еще может быть изменена путем изменения состояния
Это было бы возможно, это было бы странно, потому что, как программист, вы могли бы сделать так
источник
Может быть, этот трюк дает вам идею
источник