Ответ 1
Интересно, что код скомпилирует, будет ли поле помечено static
- и в IntelliJ оно будет жаловаться (но компилировать) на статическое поле и не говорить слово с нестатическим полем.
Вы правы в том, что в JLS §8.1.3.2 есть определенные правила относительно [статических] окончательных полей. Тем не менее, есть несколько других правил вокруг финальных полей, которые здесь играют большую роль, исходя из спецификации языка Java §4.12.4 - которые определяют семантика компиляции поля final
.
Но прежде чем мы сможем попасть в этот шар воска, нам нужно определить, что происходит, когда мы видим throws
-, который дается нам §14.18, акцент мой:
Оператор throw вызывает исключение (§11). Результатом является немедленная передача управления (§11.3) , которая может выходить из нескольких операторов и нескольких конструкторов, инициализаторов экземпляра, статических инициализаторов и оценок инициализатора поля и вызовов методов, пока оператор try (§14.20) не будет обнаружил, что улавливает выброшенное значение. Если такой оператор try не найден, то выполнение потока (§17), выполняющего бросок, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.
В условиях непрофессионала - во время выполнения, если мы сталкиваемся с оператором throws
, он может прервать выполнение конструктора (формально, "завершается внезапно" ), в результате чего объект не будет создан или построен в неполное состояние. Это может быть дыра в безопасности, в зависимости от платформы и частичной полноты конструктора.
Что ожидает JVM, данное в §4.5, состоит в том, что поле с ACC_FINAL
установлено никогда не имеет значения после построения объекта:
Объявлено окончательное; никогда не назначаемый непосредственно после построения объекта (JLS § 17.5).
Итак, у нас немного рассол - мы ожидаем поведения этого во время выполнения, но не во время компиляции. И почему IntelliJ поднимает мягкую суету, когда у меня есть static
в этом поле, но не тогда, когда я этого не делаю?
Сначала вернемся к throws
- там только ошибка времени компиляции с этим утверждением, если одна из этих трех частей не удовлетворена
- Выбрасываемое выражение не отмечено или равно null,
- Вы
try
доcatch
исключение, и выcatch
введите его с правильным типом или - Выбрасываемое выражение - это то, что действительно может быть брошено, согласно §8.4.6 и §8.8.5.
Таким образом, компиляция конструктора с throws
является законной. Так получилось, что во время выполнения оно всегда будет завершаться внезапно.
Если оператор throw содержится в объявлении конструктора, но его значение не попадает в какой-либо оператор try, который его содержит, то выражение создания экземпляра класса, вызывающее конструктор, будет завершено внезапно из-за броска (§15.9.4).
Теперь, в это пустое поле final
. Там любопытная пьеса для них - их назначение имеет значение только после завершения конструктора, подчеркивая их.
Чистая конечная переменная экземпляра должна быть определенно назначена (§16.9) в конце каждого конструктора (§8.8) класса, в котором она объявлена; в противном случае возникает ошибка времени компиляции.
Что делать, если мы никогда не дойдем до конца конструктора?
Первая программа: обычное создание поля static final
, декомпилированное:
// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {
// compiled from: DecompileThis.java
// access flags 0x1A
private final static I i = 10
// access flags 0x1
public <init>()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 9 L1
RETURN // <- Pay close attention here.
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
}
Обратите внимание, что мы действительно вызываем инструкцию RETURN
после успешного вызова нашего <init>
. Имеет смысл и совершенно законна.
Вторая программа: выбрасывает конструктор и пустое поле static final
, декомпилируется:
// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {
// compiled from: DecompileThis.java
// access flags 0x1A
private final static I i
// access flags 0x1
public <init>()V throws java/lang/InstantiationException
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 8 L1
NEW java/lang/InstantiationException
DUP
LDC "Nothin' doin'."
INVOKESPECIAL java/lang/InstantiationException.<init> (Ljava/lang/String;)V
ATHROW // <-- Eeek, where'd my RETURN instruction go?!
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
Правила ATHROW
показывают, что ссылка выставляется, и если там есть обработчик исключений, который будет содержать адрес инструкция по обработке исключения. В противном случае он удаляется из стека.
Мы никогда явно не возвращаем, что подразумевает, что мы никогда не завершаем построение объекта. Таким образом, объект можно считать находящимся в выигрышном полуинициализированном состоянии, все время подчиняется правилам компиляции - то есть все утверждения достижимы.
В случае статического поля, поскольку это не считается переменной экземпляра, но переменной класса, кажется неправильным, что этот вид вызова допустим. Возможно, стоит указать ошибку.
Возвращаясь к этому, это имеет смысл в контексте, поскольку следующее объявление в Java является законным, а тела методов конгруэнтны к телам-конструкторам:
public boolean trueOrDie(int val) {
if(val > 0) {
return true;
} else {
throw new IllegalStateException("Non-natural number!?");
}
}