До сих пор я думал, что фактически final и final более или менее эквивалентны и что JLS будет рассматривать их одинаково, если не идентично в реальном поведении. Затем я нашел этот надуманный сценарий:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
По-видимому, JLS делает здесь важное различие между ними, и я не уверен, почему.
Я читаю другие темы вроде
- Разница между final и фактически final
- Фактически конечная переменная против конечной переменной
- Что означает «фактически окончательная» переменная?
но они не вдавались в подробности. В конце концов, на более широком уровне они кажутся в значительной степени эквивалентными. Но копнув глубже, они явно различаются.
Что вызывает такое поведение, может ли кто-нибудь предоставить некоторые определения JLS, которые это объясняют?
Изменить: я нашел еще один связанный сценарий:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
Таким образом, интернирование строк здесь также ведет себя по-другому (я не хочу использовать этот фрагмент в реальном коде, просто интересно узнать о другом поведении).
источник
byte
,short
илиchar
, а другой операнд является постоянным выражением типint
, значение которого может быть представлено в типе T , то тип условного выражения - T. " --- Даже нашел в Беркли документ Java 1.0. Тот же текст . --- Да, так было всегда.Ответы:
В первую очередь, речь идет только о локальных переменных . Фактически final не применяется к полям. Это важно, поскольку семантика для
final
полей очень различается и зависит от серьезных оптимизаций компилятора и обещаний модели памяти, см. $ 17.5.1 о семантике конечных полей.На поверхностном уровне
final
иeffectively final
для локальных переменных действительно идентичны. Однако JLS проводит четкое различие между ними, что на самом деле имеет широкий спектр эффектов в особых ситуациях, подобных этой.Посылка
Из JLS§4.12.4 о
final
переменных:Поскольку
int
она примитивна, переменнаяa
является такой постоянной переменной .Далее из той же главы о
effectively final
:Таким образом, из того, как это сформулировано, ясно, что в другом примере
a
это не считается постоянной переменной, поскольку она не является окончательной , а только фактически окончательной.Поведение
Теперь, когда у нас есть различие, давайте посмотрим, что происходит и почему результат отличается.
Здесь вы используете условный оператор
? :
, поэтому мы должны проверить его определение. Из JLS§15.25 :В этом случае мы говорим о числовых условных выражениях из JLS§15.25.2 :
И это та часть, где два дела классифицируются по-разному.
фактически окончательный
Версия, которая
effectively final
соответствует этому правилу:Это такое же поведение, как если бы вы это сделали
5 + 'd'
, т.int + char
Е. В результатеint
. См. JLS§5.6.Так что все продвигается в
int
качествеa
этоint
уже. Это объясняет вывод97
.окончательный
Версия с
final
переменной соответствует этому правилу:Последняя переменная
a
имеет типint
и постоянное выражение (потому что это такfinal
). Его можно представить какchar
, следовательно, результат имеет типchar
. На этом вывод завершенa
.Пример строки
Пример с равенством строк основан на той же основной разнице,
final
переменные обрабатываются как постоянное выражение / переменная, аeffectively final
не как.В Java интернирование строк основано на постоянных выражениях, поэтому
"a" + "b" + "c" == "abc"
является
true
также (не использовать эту конструкцию в реальном коде).См. JLS§3.10.5 :
Легко упустить из виду, поскольку в основном речь идет о литералах, но на самом деле это применимо и к постоянным выражениям.
источник
... ? a : 'c'
вести себя одинаково, будьa
то переменная или константа . В этом выражении нет ничего плохого. --- Напротив,a + "b" == "ab"
это плохое выражение , потому что строки нужно сравнивать с помощьюequals()
( Как мне сравнивать строки в Java? ). Тот факт, что он «случайно» срабатывает, когдаa
является константой , - это просто причуда интернирования строковых литералов."a" + "b" + "c" == "abc"
должен бытьtrue
в любой допустимой реализации Java.a + "b" == "ab"
все же неправильное выражение . Даже если вы знаете, чтоa
это константа , она слишком подвержена ошибкам, чтобы не вызывать ееequals()
. Или, может быть, « хрупкий» - лучшее слово, то есть слишком вероятно, что он развалится, когда код будет сохранен в будущем.(final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);
меняет свой результат, когдаstr
есть (нет)final
.Другой аспект заключается в том, что если переменная объявлена как final в теле метода, ее поведение отличается от поведения переменной final, переданной как параметр.
public void testFinalParameters(final String a, final String b) { System.out.println(a + b == "ab"); } ... testFinalParameters("a", "b"); // Prints false
пока
public void testFinalVariable() { final String a = "a"; final String b = "b"; System.out.println(a + b == "ab"); // Prints true } ... testFinalVariable();
это происходит потому , что компилятор знает , что использование
final String a = "a"
вa
переменном всегда будет иметь"a"
значение так , чтоa
и"a"
может быть взаимозаменяемым без проблем. Иными словами, ifa
не определенfinal
или определен,final
но его значение присваивается во время выполнения (как в примере выше, где final являетсяa
параметром), компилятор ничего не знает до его использования. Таким образом, объединение происходит во время выполнения, и создается новая строка без использования внутреннего пула.В основном поведение таково: если компилятор знает, что переменная является константой, он может использовать ее так же, как использование константы.
Если переменная не определена как final (или она окончательная, но ее значение определяется во время выполнения), компилятор не должен обрабатывать ее как константу, даже если ее значение равно константе, а ее значение никогда не изменяется.
источник
finel
ключевое слово, примененное к параметру, имеет другую семантику, чемfinal
применено к локальной переменной и т. д.final String a; a = "a";
и добиться того же поведения