effectively final java что это
Финал против эффективного финала на Java
Узнайте разницу между Final и Эффективно Final в Java.
1. введение
Одна из самых интересных функций, введенных в Java 8, фактически является окончательной. Это позволяет нам не писать окончательный модификатор для переменных, полей и параметров, которые эффективно обрабатываются и используются как окончательные.
2. Эффективное Конечное Происхождение
Анонимные классы являются внутренними классами, и они не могут получить доступ к неконечным или неконечным переменным или мутировать их в своих охватывающих областях, как указано в JLS 8.1.3. То же ограничение применяется к лямбда-выражениям, поскольку наличие доступа потенциально может привести к проблемам параллелизма.
3. Финал против эффективного Финала
Самый простой способ понять, является ли конечная переменная фактически окончательной, – это подумать, позволит ли удаление ключевого слова final скомпилировать и запустить код:
Переназначение значения или изменение указанной выше конечной переменной сделает код недействительным независимо от того, где он происходит.
3.1. Обработка компилятора
JLS 4.12.4 утверждает, что если мы удалим модификатор final из параметра метода или локальной переменной без ошибок во время компиляции, то он фактически является окончательным. Более того, если мы добавим ключевое слово final к объявлению переменной в допустимой программе, то она фактически будет окончательной.
Компилятор изменит код, выполняемый в главный метод выше, чтобы:
4. Атомная модификация
5. Заключение
В этом уроке мы узнали о наиболее заметных различиях между final и фактически конечными переменными. Кроме того, мы предоставили безопасную альтернативу, которая позволяет нам изменять переменные внутри лямбда-функций.
Лямбда-выражения Java 8 — это замыкания?
Развернутый ответ на вопрос, вынесенный в заглавие поста, приводится в статье Брюса Эккеля в редакции от 25 ноября 2015 года. Мы решили разместить здесь перевод этой статьи и поинтересоваться, что вы думаете о функциональном программировании в Java, а также об актуальности такой книги:
Если кратко – конечно да.
Чтобы дать более развернутый ответ, давайте все-таки разберемся: а зачем мы с ними работаем?
В сущности, лямбда-выражения нужны потому, что они описывают, какие вычисления должны быть выполнены, а не как их выполнять. Традиционно мы работали с внешней итерацией, при которой четко указывали всю последовательность операций, а также как они делаются.
Такая мотивация “что, а не как” в случае лямбда-выражений является основной. Но, чтобы понять замыкания, нужно подробнее рассмотреть мотивацию функционального программирования как такового.
Лямбда-вырражения/замыкания призваны упростить функциональное программирование. Java 8 – конечно, не функциональный язык, но в нем (как и в Python) теперь обеспечивается некоторая поддержка функционального программирования, эти возможности надстроены над базисной объектно-ориентированной парадигмой.
Основная идея функционального программирования заключается в том, что можно создавать функции и манипулировать ими, в частности, создавать функции во время исполнения. Соответственно, ваша программа может оперировать не только данными, но и функциями. Представьте, какие возможности открываются перед программистом.
В чисто функциональном языке программирования есть и другие ограничения, в частности — инвариантность данных. То есть, у вас нет переменных, только неизменяемые значения. На первый взгляд это ограничение кажется чрезмерным (как вообще работать без переменных?), но оказывается, что, в сущности, при помощи значений достижимо все то же самое, что и с переменными (хотите убедиться – попробуйте Scala, этот язык не является чисто функциональным, но предусматривает возможность везде пользоваться значениями). Инвариантные функции принимают аргументы и выдают результат, не изменяя окружения; поэтому ими значительно проще пользоваться при параллельном программировании, ведь инвариантная функция не блокирует разделяемые ресурсы.
До выхода Java 8 можно было создавать функции во время выполнения только одним способом: генерировать и загружать байт-код (это довольно запутанная и сложная работа).
Для лямбда-выражений характерны две следующие черты:
Замыкания касаются именно второй возможности
Что такое замыкание?
Лямбда-выражения в Java 8
Рассмотрим тот же пример на Java с использованием лямбда-выражений:
Неоднозначная штука: мы действительно можем обратиться к n, но как только попытаемся изменить n, начнутся проблемы. Сообщение об ошибке таково: local variables referenced from a lambda expression must be final or effectively final (локальные переменные, на которые ставится ссылка из лямбда-выражения, должны быть финальными или фактически финальными).
Оказывается, что лямбда-выражения в Java замыкаются только вокруг значений, но не вокруг переменных. Java требует, чтобы эти значения были неизменны, как если бы мы объявили их final. Итак, они должны быть final независимо от того, объявляли вы их таким образом или нет. То есть, «фактически финальными». Поэтому в Java есть «замыкания с ограничениями», а не «полноценные» замыкания, которые, тем не менее, довольно полезны.
Если мы создаем объекты, расположенные не в куче, то можем изменять такие объекты, поскольку компилятор следит лишь за тем, чтобы не изменялась сама ссылка. Например:
Лямбда-выражения – как минимум, отчасти – позволяют достичь желаемой цели: теперь можно создавать функции динамически. Если вы выйдете за границы, то получите сообщение об ошибке, но обычно подобные проблемы решаемы. Выход будет не таким прямолинейным, как на Python, но ведь это все-таки Java. А конечный результат, не лишенный некоторых ограничений (давайте признаем, любой результат в Java не лишен некоторых ограничений) не так уж и плох.
Я поинтересовался, почему же эти структуры назвали «лямбдами», а не просто «замыканиями» — ведь по всем признакам это чистые замыкания. Мне ответили, что «замыкание» — неудачный и перегруженный термин. Когда кто-то говорит «настоящее замыкание», то зачастую имеет в виду такие «замыкания», которые попались ему в первом освоенном языке программирования, где имелись сущности, именуемые «замыканиями».
Я не усматриваю здесь спора «ООП против ФП», впрочем, и не собирался его устраивать. Более того, я даже «против» здесь не вижу. ООП хорошо подходит для абстрагирования данных (и пусть даже Java вынуждает вас работать с объектами, это еще не означает, что любая задача решаема при помощи объектов), а ФП — для абстрагирования поведений. Обе парадигмы полезны, и, на мой взгляд, тем более полезны, если их смешивать — и в Python, и в Java 8. Недавно мне довелось поработать с Pandoc — конвертером, написанном на чисто функциональном языке Haskell, причем у меня остались от этого самые положительные впечатления. Итак, чисто функциональные языки, также заслуживают места под солнцем.
Final vs Effectively Final in Java
Last modified: January 30, 2020
1. Introduction
One of the most interesting features introduced in Java 8 is effectively final. It allows us to not write the final modifier for variables, fields, and parameters that are effectively treated and used like final ones.
In this tutorial, we’ll explore this feature’s origin and how it’s treated by the compiler compared to the final keyword. Furthermore, we’ll explore a solution to use regarding a problematic use-case of effectively final variables.
2. Effectively Final Origin
In simple terms, objects or primitive values are effectively final if we do not change their values after initialization. In the case of objects, if we do not change the reference of an object, then it is effectively final — even if a change occurs in the state of the referenced object.
Prior to its introduction, we could not use a non-final local variable in an anonymous class. We still cannot use variables that have more than one value assigned to them inside anonymous classes, inner classes, and lambda expressions. The introduction of this feature allows us to not have to use the final modifier on variables that are effectively final, saving us a few keystrokes.
Anonymous classes are inner classes and they cannot access non-final or non-effectively-final variables or mutate them in their enclosing scopes as specified by JLS 8.1.3. The same limitation applies to lambda expressions, as having access can potentially produce concurrency issues.
3. Final vs Effectively Final
The simplest way to understand whether a final variable is effectively final is to think whether removing the final keyword would allow the code to compile and run:
Reassigning a value or mutating the above effectively final variable would make the code invalid regardless of where it occurs.
3.1. Compiler Treatment
JLS 4.12.4 states that if we remove the final modifier from a method parameter or a local variable without introducing compile-time errors, then it’s effectively final. Moreover, if we add the final keyword to a variable’s declaration in a valid program, then it’s effectively final.
The Java compiler doesn’t do additional optimization for effectively final variables, unlike it does for final variables.
Let’s consider a simple example that declares two final String variables but only uses them for concatenation:
The compiler would change the code executed in the main method above to:
On the other hand, if we remove the final modifiers, the variables would be considered effectively final, but the compiler won’t remove them since they’re only used for concatenation.
4. Atomic Modification
Generally, it’s not a good practice to modify variables used in lambda expressions and anonymous classes. We cannot know how these variables are going to be used inside method blocks. Mutating them might lead to unexpected results in multithreading environments.
We already have a tutorial explaining the best practices when using lambda expressions and another that explains common anti-patterns when we modify them. But there’s an alternative approach that allows us to modify variables in such cases that achieves thread-safety through atomicity.
The package java.util.concurrent.atomic offers classes such as AtomicReference and AtomicInteger. We can use them to atomically modify variables inside lambda expressions:
5. Conclusion
In this tutorial, we learned about the most notable differences between final and effectively final variables. In addition, we provided a safe alternative that allows us to modify variables inside lambda functions.
Почему локальные переменные, используемые в Лямбдах, должны быть окончательными или Фактически окончательными?
Узнайте, почему Java требует, чтобы локальные переменные были эффективно окончательными при использовании в лямбда-формуле.
1. введение
Java 8 дает нам лямбды и по ассоциации понятие фактически конечных переменных. Вы когда-нибудь задумывались, почему локальные переменные, записанные в лямбдах, должны быть окончательными или фактически окончательными?
Ну, JLS дает нам небольшой намек, когда говорит: “Ограничение на конечные переменные запрещает доступ к динамически изменяющимся локальным переменным, захват которых, вероятно, приведет к проблемам параллелизма.” Но что это значит?
2. Захват Лямбд
В более ранних версиях Java мы столкнулись с этим, когда анонимный внутренний класс захватил переменную, локальную для метода, который ее окружал – нам нужно было добавить ключевое слово final перед локальной переменной, чтобы компилятор был доволен.
Как немного синтаксического сахара, теперь компилятор может распознавать ситуации, когда, хотя ключевое слово final отсутствует, ссылка вообще не меняется, что означает, что это эффективно final. Мы могли бы сказать, что переменная фактически является окончательной, если бы компилятор не жаловался, если бы мы объявили ее окончательной.
3. Локальные переменные при захвате лямбд
Проще говоря, это не будет компилироваться:
start – это локальная переменная, и мы пытаемся изменить ее внутри лямбда-выражения.
3.1. Проблемы параллелизма
Для забавы давайте на мгновение представим, что Java позволила локальным переменным каким-то образом оставаться связанными с их захваченными значениями.
Что нам здесь делать:
Однако, поскольку Java накладывает фактически окончательное ограничение, нам не нужно беспокоиться о подобных сложностях.
4. Статические переменные или переменные экземпляра при захвате лямбд
Приведенные выше примеры могут вызвать некоторые вопросы, если мы сравним их с использованием статических переменных или переменных экземпляра в лямбда-выражении.
Мы можем скомпилировать наш первый пример, просто преобразовав вашу переменную start в переменную экземпляра:
Но почему мы можем изменить значение start здесь?
Проще говоря, речь идет о том, где хранятся переменные-члены. Локальные переменные находятся в стеке, но переменные-члены находятся в куче. Поскольку мы имеем дело с памятью кучи, компилятор может гарантировать, что лямбда-код будет иметь доступ к последнему значению start.
Мы можем исправить наш второй пример, сделав то же самое:
5. Избегайте Обходных Путей
Чтобы обойти ограничение на локальные переменные, кто-то может подумать об использовании держателей переменных для изменения значения локальной переменной.
Давайте рассмотрим пример, который использует массив для хранения переменной в однопоточном приложении:
Мы могли бы подумать, что поток суммирует 2 для каждого значения, но на самом деле он суммирует 0, так как это последнее значение, доступное при выполнении лямбды.
Давайте сделаем еще один шаг вперед и выполним сумму в другом потоке:
Какую ценность мы здесь суммируем? Это зависит от того, сколько времени займет наша имитационная обработка. Если он достаточно короткий, чтобы позволить выполнению метода завершиться до выполнения другого потока, он напечатает 6, в противном случае он напечатает 12.
В общем, такие обходные пути подвержены ошибкам и могут привести к непредсказуемым результатам, поэтому мы всегда должны избегать их.
6. Заключение
В этой статье мы объяснили, почему лямбда-выражения могут использовать только конечные или фактически конечные локальные переменные. Как мы уже видели, это ограничение связано с различной природой этих переменных и тем, как Java хранит их в памяти. Мы также показали опасность использования общего обходного пути.
Разница между final и effectively final
11 ответов
. начиная с Java SE 8, локальный класс может получить доступ к локальным переменным и параметрам заключительного блока, которые являются окончательными или фактически окончательными. переменная или параметр, значение которого никогда не изменяется после инициализации, является фактически окончательным.
например, предположим, что переменная numberLength Не объявлено окончательным, и вы добавляете отмеченный оператор присваивания в PhoneNumber конструктора:
из-за этого оператор присваивания, переменная numberLength больше не является фактически окончательной. в результате компилятор Java генерирует сообщение об ошибке, подобное «локальные переменные, на которые ссылается внутренний класс, должны быть окончательными или фактически окончательными» где внутренний класс PhoneNumber пытается получить доступ к numberLength переменная:
переменная или параметр, значение которого никогда не изменяется после инициализации, является фактически окончательным.
в принципе, если компилятор обнаруживает, что переменная не появляется в назначениях вне ее инициализации, то переменная считается эффективно окончательной.
например, рассмотрим некоторый класс:
из статьи Брайана Гетца,
эта переменная ниже финал, так что мы не можем изменить его значение после инициализации. Если мы попытаемся мы получим ошибку компиляции.
но если мы создадим такую переменную, мы можем изменить ее значение.
а в Java 8 все переменные являются финал по умолчанию. Но наличие 2-й строки в коде делает его на. Поэтому, если мы удалим 2-ю строку из вышеуказанного кода, наша переменная сейчас «окончательным».
Так.. любая переменная, назначенная один раз и только один раз, является «фактически окончательной».
переменная финал или эффективно окончательной, когда инициализации после и никогда мутировал в своем классе владельца. И мы!—3—>не удалось инициализировать его в петли или внутренние классы.
финал:
Эффективно Окончательной:
эффективное окончательной тема описана в JLS 4.12.4 и последний абзац состоит из ясного объяснения:
Если переменная является фактически окончательной, добавление окончательного модификатора в ее объявление не приведет к ошибкам во время компиляции. И наоборот, локальная переменная или параметр, объявленный final в допустимой программе, становится final, если удалить окончательный модификатор.
как уже говорили другие, переменная или параметр, значение которого не изменяется после инициализации является окончательным. В приведенном выше коде, если вы измените значение x во внутреннем классе FirstLevel тогда компилятор выдаст вам сообщение об ошибке:
локальные переменные, на которые ссылается лямбда-выражение, должны быть окончательными или фактически окончательными.
если бы вы могли добавить final модификатор для локальной переменной, это было эффективно окончательной.
лямбда-выражения могут получить доступ
эффективно окончательной параметры метода и
эффективно окончательной локальная переменная.
An effectively final переменная-это переменная, значение которой не изменено, но не объявлено с final ключевое слово.
кроме того, не забывайте, что смысл final что он инициализируется ровно один раз, прежде чем он будет использоваться в первый раз.
однако, начиная с Java SE 8, локальный класс может получить доступ к локальным переменным и параметрам блока >enclosing, которые являются окончательными или фактически окончательными.
это не началось на Java 8, я использую это с давних пор. Этот код, используемый (до java 8), чтобы быть законным: