В первую очередь, речь идет только о локальных переменных . Фактически 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 ).
Легко упустить из виду, поскольку в основном речь идет о литералах, но на самом деле это применимо и к постоянным выражениям.