Подтвердить что ты не робот

Использование неинициализированного конечного поля - с/без "этого". спецификатор

Может кто-нибудь объяснить мне, почему первый из следующих двух образцов компилируется, а второй - нет? Обратите внимание, что единственное отличие состоит в том, что первый явно указывает ссылку на x с помощью ".this", а второй - нет. В обоих случаях конечное поле x явно пыталось использовать до инициализации.

Я бы подумал, что оба образца будут обрабатываться совершенно одинаково, что приведет к ошибке компиляции для обоих.

1)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * this.x;
        x = 5;
    }
}

2)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * x;
        x = 5;
    }
}
4b9b3361

Ответ 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 обычно очень хорошо относятся к спецификации точно (в отличие от некоторых языков, где каждый компилятор имеет свой собственный диалект) — но, ну, что-то случилось, и выше это мои только две догадки.

Ответ 2

Когда вы используете this в конструкторе, компилятор видит x как атрибут-член объекта this (по умолчанию инициализирован). Поскольку x - int, он по умолчанию инициализируется 0. Это делает компилятор счастливым, и он отлично работает во время работы.

Если вы не используете this, то компилятор использует объявление x непосредственно в лексическом анализе и, следовательно, жалуется на его инициализацию (время компиляции).

Итак, это определение this, что заставляет компилятор анализировать x как членную переменную объекта по сравнению с прямым атрибутом во время лексического анализа в компиляции и приводить к различному поведению компиляции.

При использовании в качестве основного выражения ключевое слово this обозначает значение, которое является ссылкой на объект, для которого был вызван метод экземпляра (§15.12), или для объекта, который создается.

Ответ 3

Я думаю, что компилятор оценивает, что запись this.x подразумевает 'this' существует, поэтому был вызван конструктор (и окончательная переменная была инициализирована). Но вы должны получить RuntimeException при попытке запустить его

Ответ 4

Я предполагаю, что вы ссылаетесь на поведение в Eclipse. (Как указано в комментарии, компиляция с javac работает).

Я думаю, что это проблема Eclipse. Он имеет свой собственный компилятор и собственный набор правил. Один из них заключается в том, что вы не можете получить доступ к полю, которое не инициализировано, хотя Java-commpiler инициализирует переменные для вас.