В первую очередь, речь идет только о локальных переменных . Фактически final не применяется к полям. Это важно, поскольку семантика для final
полей очень различается и зависит от серьезных оптимизаций компилятора и обещаний модели памяти, см. $ 17.5.1 о семантике конечных полей.
На поверхностном уровне final
и effectively final
для локальных переменных действительно идентичны. Однако JLS проводит четкое различие между ними, что на самом деле имеет широкий спектр эффектов в особых ситуациях, подобных этой.
Посылка
Из JLS§4.12.4 о final
переменных:
Переменная константа является final
переменной примитивного типа или типа String , который инициализируется с постоянным выражением ( §15.29 ). Независимо от того, является ли переменная постоянной переменной или нет, может иметь значение в отношении инициализации класса ( §12.4.1 ), двоичной совместимости ( §13.1 ), достижимости ( §14.22 ) и определенного присваивания ( §16.1.1 ).
Поскольку int
она примитивна, переменная a
является такой постоянной переменной .
Далее из той же главы о effectively final
:
Некоторые переменные, которые не объявлены окончательными, вместо этого считаются фактически окончательными: ...
Таким образом, из того, как это сформулировано, ясно, что в другом примере a
это не считается постоянной переменной, поскольку она не является окончательной , а только фактически окончательной.
Поведение
Теперь, когда у нас есть различие, давайте посмотрим, что происходит и почему результат отличается.
Здесь вы используете условный оператор ? :
, поэтому мы должны проверить его определение. Из JLS§15.25 :
Существует три вида условных выражений, классифицируемых в соответствии со вторым и третьим выражениями операндов: логические условные выражения , числовые условные выражения и ссылочные условные выражения .
В этом случае мы говорим о числовых условных выражениях из JLS§15.25.2 :
Тип числового условного выражения определяется следующим образом:
И это та часть, где два дела классифицируются по-разному.
фактически окончательный
Версия, которая effectively final
соответствует этому правилу:
В противном случае ко второму и третьему операндам применяется общее числовое продвижение ( §5.6 ), а тип условного выражения - это повышенный тип второго и третьего операндов.
Это такое же поведение, как если бы вы это сделали 5 + 'd'
, т. int + char
Е. В результате int
. См. JLS§5.6.
Числовое продвижение определяет продвигаемый тип всех выражений в числовом контексте. Повышенный тип выбирается таким образом, чтобы каждое выражение можно было преобразовать в повышенный тип, и в случае арифметической операции операция определяется для значений повышенного типа. Порядок выражений в числовом контексте не имеет значения для числового продвижения. Правила следующие:
[...]
Затем расширяющееся примитивное преобразование ( §5.1.2 ) и сужающее примитивное преобразование ( §5.1.3) ) применяются к некоторым выражениям в соответствии со следующими правилами:
В контексте числового выбора применяются следующие правила:
Если какое-либо выражение относится к типу int
и не является константным выражением ( §15.29 ), тогда повышенный тип имеет тип int
, а другие выражения, не принадлежащие к типу, int
подвергаются расширяющемуся примитивному преобразованию в int
.
Так что все продвигается в int
качестве a
это int
уже. Это объясняет вывод 97
.
окончательный
Версия с final
переменной соответствует этому правилу:
Если один из операндов имеет типа , T
где T
находится byte
, short
или char
, а другой операндом является постоянным выражением ( §15.29 ) типа int
, значение которого представимо в типе T
, то тип условного выражения T
.
Последняя переменная a
имеет тип int
и постоянное выражение (потому что это так final
). Его можно представить как char
, следовательно, результат имеет тип char
. На этом вывод завершен a
.
Пример строки
Пример с равенством строк основан на той же основной разнице, final
переменные обрабатываются как постоянное выражение / переменная, а effectively final
не как.
В Java интернирование строк основано на постоянных выражениях, поэтому
"a" + "b" + "c" == "abc"
является true
также (не использовать эту конструкцию в реальном коде).
См. JLS§3.10.5 :
Более того, строковый литерал всегда относится к одному и тому же экземпляру класса String. Это связано с тем, что строковые литералы - или, в более общем смысле , строки, которые являются значениями константных выражений ( §15.29 ) - «интернированы», чтобы совместно использовать уникальные экземпляры с использованием метода String.intern
( §12.5 ).
Легко упустить из виду, поскольку в основном речь идет о литералах, но на самом деле это применимо и к постоянным выражениям.