1. Введение
Одна из самых интересных функций, представленных в Java 8, фактически финальная. Это позволяет нам не писать модификатор final
для переменных, полей и параметров, которые эффективно обрабатываются и используются как final.
В этом руководстве мы рассмотрим происхождение этой функции и то, как она обрабатывается компилятором по сравнению с ключевым словом final
. Кроме того, мы рассмотрим решение, которое можно использовать в отношении проблемного варианта использования эффективных конечных переменных.
2. Эффективно окончательное происхождение
Проще говоря, объекты или примитивные значения фактически являются окончательными, если мы не меняем их значения после инициализации . В случае объектов, если мы не меняем ссылку на объект, то она фактически является окончательной, даже если происходит изменение состояния объекта, на который делается ссылка.
До его введения мы не могли использовать нефинальную локальную переменную в анонимном классе . Мы по-прежнему не можем использовать переменные, которым присвоено более одного значения, внутри анонимных классов, внутренних классов и лямбда-выражений. Введение этой функции позволяет нам не использовать модификатор final
для переменных, которые фактически являются final, что экономит нам несколько нажатий клавиш.
Анонимные классы являются внутренними классами, и они не могут получить доступ к переменным, не являющимся конечными или неэффективно конечными, или изменить их в своих объемлющих областях, как указано в JLS 8.1.3. То же ограничение применяется к лямбда-выражениям, так как доступ может привести к проблемам параллелизма.
3. Финал против фактически финала
Самый простой способ понять, действительно ли final переменная является final, — это подумать, позволит ли удаление ключевого слова final
коду скомпилироваться и запуститься:
@FunctionalInterface
public interface FunctionalInterface {
void testEffectivelyFinal();
default void test() {
int effectivelyFinalInt = 10;
FunctionalInterface functionalInterface
= () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt);
}
}
Переназначение значения или изменение приведенной выше эффективной переменной final сделает код недействительным независимо от того, где это происходит.
3.1. Обработка компилятора
В JLS 4.12.4 указано, что если мы удалим модификатор final
из параметра метода или локальной переменной без внесения ошибок времени компиляции, то он станет фактически окончательным. Более того, если мы добавим ключевое слово final
к объявлению переменной в корректной программе, то оно станет фактически окончательным.
Компилятор Java не выполняет дополнительную оптимизацию для переменных final, в отличие от переменных final
.
Давайте рассмотрим простой пример, в котором объявляются две переменные final String
, но используются они только для конкатенации:
public static void main(String[] args) {
final String hello = "hello";
final String world = "world";
String test = hello + " " + world;
System.out.println(test);
}
Компилятор изменит код, выполняемый в основном
методе выше, на:
public static void main(String[] var0) {
String var1 = "hello world";
System.out.println(var1);
}
С другой стороны, если мы удалим модификаторы final
, переменные будут считаться фактически окончательными, но компилятор не удалит их , поскольку они используются только для конкатенации.
4. Атомная модификация
Как правило, изменять переменные, используемые в лямбда-выражениях и анонимных классах, не рекомендуется . Мы не можем знать, как эти переменные будут использоваться внутри блоков методов. Их изменение может привести к неожиданным результатам в многопоточных средах.
У нас уже есть учебник, в котором объясняются лучшие практики использования лямбда-выражений , и еще один, в котором объясняются распространенные анти-шаблоны при их изменении . Но есть альтернативный подход, который позволяет нам изменять переменные в таких случаях, что обеспечивает потокобезопасность за счет атомарности.
Пакет java.util.concurrent.atomic
предлагает такие классы, как AtomicReference
и AtomicInteger
. Мы можем использовать их для атомарного изменения переменных внутри лямбда-выражений:
public static void main(String[] args) {
AtomicInteger effectivelyFinalInt = new AtomicInteger(10);
FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet;
}
5. Вывод
В этом уроке мы узнали о наиболее заметных различиях между final
и эффективно final переменными. Кроме того, мы предоставили безопасную альтернативу, позволяющую изменять переменные внутри лямбда-функций.