Ответ 1
После кучу spec-чтения и мысли я пришел к выводу, что
В компиляторе Java 5 или Java 6 это правильное поведение. Глава 16 "Определенное назначение спецификации языка Java, третье издание" гласит:
Каждая локальная переменная (§14.4) и каждый пробел
final
(§4.12.4) поле (§8.3.1.2) должно иметь определенно присвоенное значение, когда происходит любой доступ к его значению. Доступ к его значению состоит из простого имени переменной, встречающегося где угодно в выражении, кроме как левого операнда простого оператора присваивания=
.
(акцент мой). Таким образом, в выражении 2 * this.x
часть this.x
не рассматривается как "доступ к значению [x
" ] (и поэтому не подчиняется правилам определенного назначения), поскольку this.x
не является простое имя переменной экземпляра x
. (NB правило, когда определенное задание происходит в абзаце после вышеприведенного текста, разрешает что-то вроде this.x = 3
и считает, что x
будет определенно назначено после этого, это только правило для доступа, которое не считается this.x
.) Обратите внимание, что значение this.x
в этом случае будет равно нулю, за & sect; 17.5.2.
В компиляторе Java 7 это ошибка компилятора, но понятная. Глава 16 "Определенное присвоение" спецификации языка Java, версия Java 7 SE гласит:
Каждая локальная переменная (§14.4) и каждое пустое поле
final
(§4.12.4, §8.3.1.2) должно иметь определенно присвоенное значение, когда имеет место любой доступ к его значению.Доступ к его значению состоит из простого имени переменной (или для поля, простого имени поля, соответствующего
this
), встречающегося в любом месте выражения, кроме как левый операнд простого оператора присваивания=
(§15.26.1).
(акцент мой). Поэтому в выражении 2 * this.x
часть this.x
следует рассматривать как "доступ к значению [x
]" и должна давать ошибку компиляции.
Но вы не спросили, должен ли первый компилироваться, вы спрашивали, почему он компилируется (в некоторых компиляторах). Это обязательно умозрительно, но я сделаю две догадки:
- Большинство компиляторов Java 7 были написаны путем изменения компиляторов Java 6. Возможно, некоторые компиляторы не заметили этого изменения. Кроме того, многие компиляторы Java-7 и IDE по-прежнему поддерживают Java 6, и некоторые разработчики компиляторов, возможно, не чувствовали мотивации специально отклонять что-то в режиме Java-7, которые они принимают в режиме Java-6.
- Новое поведение Java 7 странно непоследовательно. Что-то вроде
(false ? null : this).x
по-прежнему разрешено, и, несмотря на это, даже(this).x
все еще разрешено; это только конкретная последовательность токеновthis
плюс.
плюс имя поля, на которое повлияло это изменение. Конечно, такая несогласованность уже существовала в левой части оператора присваивания (мы можем написатьthis.x = 3
, но не(this).x = 3
), но это более понятно: оно принимаетthis.x = 3
как особый разрешенный случай иначе запрещено строительствоobj.x = 3
. Имеет смысл разрешить это. Но я не думаю, что имеет смысл отклонить2 * this.x
как особый запретный случай иначе разрешенной конструкции2 * obj.x
, учитывая, что (1) этот специальный запрещенный случай легко обрабатывается путем добавления круглых скобок, что (2) это специальный запрещенный случай был разрешен в предыдущих версиях языка и что (3) нам все еще нужно специальное правило, в котором поляfinal
имеют свои значения по умолчанию (например,0
дляint
) до тех пор, пока они не будут инициализированы, оба из-за таких случаев, как(this).x
, и из-за таких случаев, какthis.foo()
, гдеfoo()
- это метод, который обращается кx
. Таким образом, некоторые составители-компиляторы, возможно, не чувствовали себя мотивированными, чтобы сделать это непоследовательное изменение.
Любое из них было бы удивительным; Я предполагаю, что у составителей-компиляторов была подробная информация о каждом изменении спецификации, и, по моему опыту, компиляторы Java обычно очень хорошо относятся к спецификации точно (в отличие от некоторых языков, где каждый компилятор имеет свой собственный диалект) — но, ну, что-то случилось, и выше это мои только две догадки.