final String someString = "Hello World";
try {
someString = "Hello";
} catch (RuntimeException e) {
someString = "World";
}
Если мы скомпилируем этот код, мы получим две ошибки:
Невозможно присвоить значение конечной переменной 'someString' // Строка 3
Невозможно присвоить значение конечной переменной 'someString' // Строка 5
Это я понимаю. Это конечная переменная, вы уже присвоили ей ссылку и больше не можете ее изменить. Хороший.
final String someString;
try {
someString = "Hello";
} catch (RuntimeException e) {
someString = "World";
}
Если мы скомпилируем этот код, мы получим только одну ошибку:
Переменная 'someString' могла быть уже присвоена // строке 5
Вот этого я не понимаю. Нам разрешено инициализироваться в блоке try. Тем не менее, если что-то пошло не так, даже до фактической инициализации нам не разрешается инициализировать конечную переменную в блоке catch.
Почему это так? Я ожидал, что мы сможем инициализировать переменную в блоке catch, так как блок try
все равно потерпел неудачу.
После назначения переменные final
не могут быть изменены
@Sweeper разработал мой вопрос.
Вкратце: когда мы достигаем блока catch, блок try не работает, но мы не знаем, где именно. Возможно, произошел сбой до, во время или после присваивания переменной. Поэтому компилятор не может допустить возможного переназначения переменной final
.
Длинный ответ: переменная final
должна быть назначена ровно один раз, прежде чем ее можно будет прочитать. Не менее (т.е. никогда не присваивался), не более (т.е. присваивался дважды). Ровно один раз.
По этой причине следующий код тривиально неверен:
final String someString;
someString = "Hello";
someString = "World";
Второе присваивание приведет к ошибке времени компиляции, так как someString
определенно уже было присвоено.
Теперь давайте посмотрим на этот код:
final String someString;
try {
callSomeMethod();
someString = "Hello";
callSomeOtherMethod();
} catch (Exception e) {
someString = "World";
}
Теперь мы столкнулись с проблемой: компилятор не может знать, когда выброшено исключение, вызывающее выполнение блока catch: оно может быть выброшено в callSomeMethod()
или в callSomeOtherMethod()
(или через какую-то чудесную внутреннюю проблему даже при попытке присвоить константу).
Таким образом, компилятор просто не знает, было ли уже назначено someString
внутри блока catch или нет (и оба варианта теоретически возможны во время выполнения!). Если переменная была присвоена, то присваивание должно быть ошибкой времени компиляции. Если бы это было не так, было бы хорошо. Но поскольку статический анализ не может сказать вам, какой именно, компилятор должен пометить все присваивание как ошибку.
Теоретически в некоторых крайних случаях компилятор теоретически может знать, что переменная определенно была назначена в блоке try
, даже когда возникает исключение. В частности, если это первое действие, и оно может доказать, что действие присваивания не может вызвать исключение. Но если это так, то программист может просто переместить это присваивание из блока try и избежать всей проблемы. Вероятно, поэтому в JLS такого правила нет.
В Java конечная переменная — это переменная, которая инициализируется один раз и не может быть переназначена. Причина, по которой невозможно инициализировать конечную переменную в блоке catch, заключается в том, что блок catch выполняется только при возникновении исключения, а компилятор Java не может гарантировать, что инициализация будет происходить всегда.