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

Может кто-нибудь объяснить мне, почему первый из следующих двух примеров компилируется, а второй нет? Обратите внимание, единственное отличие состоит в том, что первый явно квалифицирует ссылку на 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;
    }
}
53 голоса | спросил weiresr 13 ThuEurope/Moscow2012-12-13T20:44:29+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 20:44:29 +0400 2012, 20:44:29

4 ответа


0

После нескольких чтений и размышлений я пришел к выводу, что:

В компиляторе 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. (Обратите внимание: правило, когда происходит определенное назначение, в абзаце после вышеприведенного текста разрешает разрешать что-то вроде this.x = 3 и считает, что x будет определенно назначен после этого; это только правило для обращений, которое не учитывается this.x.) Обратите внимание, что значение this.x в этом случае будет быть нулевым, согласно §17.5.2 .

В компиляторе Java 7 это ошибка компилятора, но понятная. Глава 16 «Определенное назначение» языка Java Спецификация , Java 7 SE Edition гласит:

  

Каждая локальная переменная ( §14.4 ) и каждое пустое поле final ( §4.12.4 , §8.3.1.2 ) должно иметь однозначно назначенное значение, когда любой доступ к его значению происходит.

     

Доступ к его значению состоит из простого имени переменной (или, для поля, простого имени поля, квалифицированного как this) в любом месте выражения, кроме как в качестве левого операнда оператора простого присваивания = ( §15.26.1 ).

(выделение мое). Так что в выражении 2 * this.x, this.x часть должна рассматриваться как «доступ к значению [x», а должна выдает ошибку компиляции.

Но вы не спросили , должен ли первый компилироваться, вы спросили почему он компилирует (в некоторых компиляторах). Это обязательно умозрительно, но я сделаю два предположения:

  1. Большинство компиляторов Java 7 были написаны путем модификации компиляторов Java 6. Некоторые авторы компиляторов, возможно, не заметили это изменение. Кроме того, многие компиляторы и IDE Java-7 по-прежнему поддерживают Java 6, и некоторые авторы компиляторов, возможно, не чувствовали мотивации специально отклонять что-то в режиме Java-7, которое они принимают вРежим Java-6.
  2. Новое поведение 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), пока они не будут инициализированы, оба из-за случаев, подобных ---- +: = 30 =: + ----, и из-за таких случаев, как (this).x где this.foo() - это метод, который обращается к foo(). Так что некоторые авторы компиляторов, возможно, не чувствовали мотивации сделать это непоследовательное изменение.

Любой из этих вариантов был бы удивительным - я предполагаю, что создатели компиляторов имели подробную информацию о каждом отдельном изменении спецификации, и по моему опыту компиляторы Java обычно довольно хорошо придерживаются спецификации (в отличие от некоторых языков, где каждый у компилятора есть свой собственный диалект) - но, что-то произошло, и вышеизложенное - мои только две догадки.

ответил ruakh 13 ThuEurope/Moscow2012-12-13T23:29:33+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 23:29:33 +0400 2012, 23:29:33
0

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

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

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

  

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

ответил Yogendra Singh 13 ThuEurope/Moscow2012-12-13T20:53:14+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 20:53:14 +0400 2012, 20:53:14
0

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

ответил Slauster 13 ThuEurope/Moscow2012-12-13T20:52:38+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 20:52:38 +0400 2012, 20:52:38
0

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

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

ответил Andy 13 ThuEurope/Moscow2012-12-13T20:56:55+04:00Europe/Moscow12bEurope/MoscowThu, 13 Dec 2012 20:56:55 +0400 2012, 20:56:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132