Глава 14. Блоки и операторы
Оглавление
- 14.1. Нормальное и преждевременное завершение операторов
- 14.2. Блоки
- 14.3. Объявления локальных классов
- 14.4. Операторы объявления локальных переменных
- 14.5. Операторы
- 14.6. Пустой оператор
- 14.7. Операторы с метками
- 14.8. Операторы выражений
- 14.9. Оператор
if - 14.10. Оператор
assert - 14.11. Оператор
switch - 14.12. Оператор
while - 14.13. Оператор
do - 14.14. Оператор
for - 14.15. Оператор
break - 14.16. Оператор
continue - 14.17. Оператор
return - 14.18. Оператор
throw - 14.19. Оператор
synchronized - 14.20. Оператор
try - 14.21. Недостижимые операторы
Последовательность выполнения программы контролируется операторами, которые выполняются для своего эффекта и не имеют значений.
Некоторые операторы содержат другие операторы в качестве части своей структуры; такие другие операторы являются под-операторами оператора. Мы говорим, что оператор S непосредственно содержит оператор U, если нет оператора T, отличного от S и U, такого, что S содержит T и T содержит U. Аналогичным образом, некоторые операторы содержат выражения (§15 (Выражения)) в качестве части своей структуры.
В первом разделе этой главы рассматривается различие между нормальным и преждевременным завершением операторов (§14.1). В большинстве оставшихся разделов объясняются различные типы операторов, описывая подробно как их нормальное поведение, так и любое специальное обращение с преждевременным завершением.
Сначала объясняются блоки (§14.2), за которыми следуют объявления локальных классов (§14.3) и операторы объявления локальных переменных (§14.4).
Далее объясняется грамматический приём, который обходит проблему "висящего else" (§14.5).
В последнем разделе (§14.21) этой главы рассматривается требование, чтобы каждый оператор был достижим в определённом техническом смысле.
Каждый оператор имеет нормальный режим выполнения, в котором выполняются определённые вычислительные шаги. В следующих разделах описан нормальный режим выполнения для каждого типа оператора.
Если все шаги выполняются как описано, без указания преждевременного завершения, то оператор считается завершенным нормально. Однако определённые события могут помешать оператору завершиться нормально:
-
Операторы
break(§14.15),continue(§14.16) иreturn(§14.17) вызывают передачу управления, что может помешать нормальному завершению операторов, которые их содержат. -
Вычисление определённых выражений может вызвать исключения из виртуальной машины Java (§15.6). Явное оператор
throw(§14.18) также приводит к исключению. Исключение вызывает передачу управления, что может помешать нормальному завершению операторов.
Если такое событие происходит, то выполнение одного или нескольких операторов может быть прервано до завершения всех шагов их нормального режима выполнения; такие операторы считаются завершенными преждевременно.
Преждевременное завершение всегда имеет связанный причина, которая является одной из следующих:
-
breakбез метки -
breakс заданной меткой -
continueбез метки -
continueс заданной меткой -
returnбез значения -
returnс заданным значением -
throwс заданным значением, включая исключения, брошенные виртуальной машиной Java
Термины "завершить нормально" и "завершить преждевременно" также применяются к вычислению выражений (§15.6). Единственная причина, по которой выражение может завершиться преждевременно, — это выбрасывание исключения из-за либо throw с заданным значением (§14.18), либо исключения или ошибки во время выполнения (§11 (Исключения), §15.6).
Если оператор вычисляет выражение, преждевременное завершение выражения всегда вызывает непосредственное преждевременное завершение оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.
За исключением случаев, указанных в этой главе, преждевременное завершение под-оператора вызывает непосредственное преждевременное завершение самого оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное, оператор завершается нормально, если все выражения, которые он вычисляет, и все под-операторы, которые он выполняет, завершаются нормально.
Блок — это последовательность операторов, объявлений локальных классов и объявлений локальных переменных, заключённых в фигурные скобки.
Блок выполняется путем последовательного выполнения операторов объявления локальных переменных и других операторов в порядке следования (слева направо). Если все эти операторы блока завершаются нормально, то и блок завершается нормально. Если любой из этих операторов блока завершается неожиданно по какой-либо причине, то и весь блок завершается неожиданно по той же причине.
Локальный класс — это вложенный класс (§8 (Классы)), который не является членом какого-либо класса и имеет имя (§6.2, §6.7).
Все локальные классы являются внутренними классами (§8.1.3).
Каждое объявление локального класса непосредственно содержится в блоке (§14.2). Объявления локальных классов могут свободно перемешиваться с другими видами операторов в блоке.
Если объявление локального класса содержит какие-либо модификаторы доступа public, protected или private (§6.6), или модификатор static (§8.1.1), то это является ошибкой компиляции.
Область действия и затенение объявления локального класса определяются в §6.3 и §6.4.
Пример 14.3-1. Локальные объявления классов
Вот пример, который иллюстрирует несколько аспектов вышеизложенных правил:
class Global {
class Cyclic {}
void foo() {
new Cyclic(); // create a Global.Cyclic
class Cyclic extends Cyclic {} // circular definition
{
class Local {}
{
class Local {} // compile-time error
}
class Local {} // compile-time error
class AnotherLocal {
void bar() {
class Local {} // ok
}
}
}
class Local {} // ok, not in scope of prior Local
}
}
Первый оператор метода foo создает экземпляр внутреннего класса Global.Cyclic, а не экземпляр локального класса Cyclic, поскольку оператор появляется до области действия объявления локального класса.
Тот факт, что область действия объявления локального класса охватывает всё объявление (а не только его тело), означает, что определение локального класса Cyclic является циклическим, поскольку оно расширяет само себя, а не Global.Cyclic. Соответственно, объявление локального класса Cyclic отбрасывается во время компиляции.
Поскольку имена локальных классов не могут быть повторно объявлены в рамках одного метода (или конструктора или инициализатора), второе и третье объявления Local приводят к ошибкам компиляции. Однако Local может быть повторно объявлено в контексте другого, более глубоко вложенного класса, такого как AnotherLocal.
Окончательное объявление Local является корректным, поскольку оно происходит вне области действия любого предыдущего объявления Local.
Оператор объявления локальной переменной объявляет одно или несколько имён локальных переменных.
См. §8.3 для Тип. Следующие правила из §4.3, §8.4.1 и §8.3 приведены здесь для удобства:
Каждый оператор объявления локальной переменной непосредственно содержится в блоке. Операторы объявления локальных переменных могут свободно перемежаться с другими операторами в блоке.
Помимо операторов объявления локальных переменных, объявление локальной переменной может появиться в заголовке оператора §14.14 или оператора try-с-ресурсами (§14.20.3). В этих случаях он выполняется так же, как если бы он был частью оператора объявления локальной переменной.
Правила для модификаторов аннотаций при объявлении локальной переменной описаны в §9.7.4 и §9.7.5.
Если final появляется более одного раза как модификатор объявления локальной переменной, это ошибка на этапе компиляции.
Каждое объявление в объявлении локальной переменной объявляет одну локальную переменную, имя которой является Идентификатор, который появляется в объявлении.
Если ключевое слово final появляется в начале объявления, объявляемая переменная является переменной final (§4.12.4).
Объявленный тип локальной переменной обозначается Тип, если в Тип и ИдентификаторПеременной не содержатся парные скобки, в противном случае он указан в §10.2.
Локальная переменная типа float всегда содержит значение, являющееся элементом множества значений с плавающей точкой (§4.2.3); аналогично, локальная переменная типа double всегда содержит значение, являющееся элементом множества значений с двойной точностью. Не разрешается, чтобы локальная переменная типа float содержала элемент множества значений с плавающей точкой с расширенным порядком, который не является также элементом множества значений с плавающей точкой, а также для локальной переменной типа double содержать элемент множества значений с двойной точностью с расширенным порядком, который не является также элементом множества значений с двойной точностью.
Область действия и перекрытие объявления локальной переменной указаны в §6.3 и §6.4.
Оператор объявления локальной переменной является выполняемым оператором. Каждый раз при его выполнении деклараторы обрабатываются в порядке слева направо. Если у декларатора есть инициализатор, инициализатор вычисляется, и его значение присваивается переменной.
Если у декларатора нет инициализатора, то каждое обращение к переменной должно предшествовать выполнению присвоения переменной или возникает ошибка на этапе компиляции по правилам §16 (Определённое присваивание).
Каждый инициализатор (кроме первого) вычисляется только если вычисление предыдущего инициализатора завершается успешно.
Выполнение объявления локальной переменной завершается успешно только если вычисление последнего инициализатора завершается успешно.
Если объявление локальной переменной не содержит инициализаторов, то его выполнение всегда завершается успешно.
В языке программирования Java существует множество видов операторов. Большинство из них соответствуют операторам языков C и C++, но некоторые являются уникальными.
Как и в C и C++, оператор if языка программирования Java страдает от так называемой проблемы "висящего else", проиллюстрированной этим запутанным примером:
if (door.isOpen())
if (resident.isVisible())
resident.greet("Hello!");
else door.bell.ring(); // A "dangling else"
Проблема заключается в том, что как внешний оператор if, так и внутренний оператор if потенциально могут владеть предложением else. В данном примере можно предположить, что программист намеревался отнести предложение else к внешнему оператору if.
Язык программирования Java, как и C, C++ и многие языки программирования до них, произвольно устанавливает, что предложение else принадлежит самому внутреннему оператору if, к которому оно потенциально может относиться. Это правило описывается следующей грамматикой:
Следующие производства из §14.9 показаны здесь для удобства:
Таким образом, операторы грамматически разделены на две категории: те, которые могут заканчиваться оператором if, у которого нет предложения else (оператором "короткий if"), и те, которые определенно не могут.
Только операторы, которые определенно не заканчиваются оператором "короткий if", могут выступать непосредственным подвыражением перед ключевым словом else в операторе if, который имеет предложение else.
Это простое правило предотвращает проблему "висящего else". Поведение оператора с ограничением "без короткого if" идентично поведению такого же оператора без ограничения "без короткого if"; различие делается исключительно для решения синтаксической проблемы.
Пустой оператор ничего не делает.
; Выполнение пустого оператора всегда завершается нормально.
Операторы могут иметь префиксы меток.
Идентификатор объявляется меткой немедленно содержащегося Оператора.
В отличие от C и C++, язык программирования Java не имеет оператора goto; метки идентификаторов операторов используются с операторами break или continue (§14.15, §14.16) появляющимися где угодно внутри помеченного оператора.
Область действия метки помеченного оператора — немедленно содержащийся Оператор.
Если имя метки помеченного оператора используется в области действия метки в качестве метки другого помеченного оператора, то это ошибка компиляции.
Нет ограничений на использование того же идентификатора в качестве метки и в качестве имени пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора в качестве метки оператора не скрывает (§6.4.2) пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же именем. Использование идентификатора как класса, интерфейса, метода, поля, локальной переменной или как параметра обработчика исключений (§14.20) не скрывает метку оператора с тем же именем.
Помеченный оператор выполняется путём выполнения немедленно содержащегося Оператора.
Если оператор помечен Идентификатором, и содержащийся Оператор завершается прерыванием из-за оператора break с тем же Идентификатором, то помеченный оператор завершается нормально. Во всех других случаях прерывания выполнения Оператора, помеченный оператор завершается прерыванием по той же причине.
Пример 14.7-1. Метки и Идентификаторы
Следующий код взят из версии класса String и его метода indexOf, где метка первоначально называлась test. Изменение метки на совпадение с локальной переменной i не скрывает метку в области действия объявления i. Таким образом, код валиден.
class Test {
char[] value;
int offset, count;
int indexOf(TestString str, int fromIndex) {
char[] v1 = value, v2 = str.value;
int max = offset + (count - str.count);
int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
i:
for (int i = start; i <= max; i++) {
int n = str.count, j = i, k = str.offset;
while (n-- != 0) {
if (v1[j++] != v2[k++])
continue i;
}
return i - offset;
}
return -1;
}
}
Идентификатор max также мог быть использован в качестве метки оператора; метка не скрывала бы локальную переменную max в области действия помеченного оператора.
Некоторые виды выражений могут использоваться как операторы, если за ними следует точка с запятой.
Оператор выражения выполняется путём вычисления выражения; если у выражения есть значение, это значение отбрасывается.
Выполнение оператора выражения завершается нормально тогда и только тогда, когда вычисление выражения завершается нормально.
В отличие от C и C++, язык программирования Java допускает использование только определённых форм выражений в качестве операторов выражения. Например, допустимо использовать выражение вызова метода (§15.12):
System.out.println("Hello world"); // OK
но недопустимо использовать выражение в скобках (§15.8.5):
(System.out.println("Hello world")); // illegal
Обратите внимание, что язык программирования Java не допускает «приведения к типу void» - void не является типом - поэтому традиционный приём в C, заключающийся в написании оператора выражения, например:
(void)... ; // incorrect!
не работает. С другой стороны, язык программирования Java допускает все самые полезные виды выражений в операторах выражения, и он не требует, чтобы вызов метода, используемый в качестве оператора выражения, вызывал метод void, поэтому такой приём почти никогда не нужен. Если такой приём необходим, можно использовать оператор присваивания (§15.26) или оператор объявления локальной переменной (§14.4) вместо этого.
Оператор if позволяет условно выполнить оператор или осуществить условный выбор между двумя операторами, выполнив один из них, но не оба.
Выражение должно иметь тип boolean или Boolean, иначе произойдёт ошибка компиляции.
Оператор if-then выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он подвергается приведению к типу без обёртки (§5.1.8).
Если вычисление выражения Выражение или последующее приведение к типу без обёртки (если таковое имеется) завершается с ошибкой, то оператор if-then завершается с той же ошибкой.
В противном случае выполнение продолжается путём принятия решения на основе полученного значения:
-
Если значение равно
true, то выполняется вложенный оператор Оператор; операторif-thenзавершается нормально тогда и только тогда, когда выполнение оператора Оператор завершается нормально. -
Если значение равно
false, то никаких дальнейших действий не предпринимается, и операторif-thenзавершается нормально.
Оператор if-then-else выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он подвергается приведению к типу без обёртки (§5.1.8).
Если вычисление выражения Выражение или последующее приведение к типу без обёртки (если таковое имеется) завершается с ошибкой, то оператор if-then-else завершается с той же ошибкой.
В противном случае выполнение продолжается путём принятия решения на основе полученного значения:
-
Если значение равно
true, то выполняется первый вложенный оператор Оператор (тот, что перед ключевым словомelse); операторif-then-elseзавершается нормально тогда и только тогда, когда выполнение этого оператора завершается нормально. -
Если значение равно
false, то выполняется второй вложенный оператор Оператор (тот, что после ключевого словаelse); операторif-then-elseзавершается нормально тогда и только тогда, когда выполнение этого оператора завершается нормально.
Утверждение (assertion) — это assert утверждение, содержащее булево выражение. Утверждение может быть активировано или деактивировано. Если утверждение активировано, выполнение утверждения приводит к вычислению булева выражения, и при вычислении к значению false, выдаётся ошибка. Если утверждение деактивировано, выполнение утверждения не имеет никакого эффекта.
Для упрощения представления первое Выражение в обоих формах утверждения assert обозначается как Expression1. Во второй форме утверждения assert второе Выражение обозначается как Expression2.
Ошибка компиляции возникает, если Expression1 не имеет тип boolean или Boolean.
Во второй форме утверждения assert, если Expression2 имеет тип void (§15.1), возникает ошибка компиляции.
Утверждение assert, выполняемое после завершения инициализации своего класса или интерфейса, активировано только в том случае, если хост-система определила, что верхнеуровневый класс или интерфейс, лексически содержащий утверждение assert, активирует утверждения.
Активирует ли верхнеуровневый класс или интерфейс утверждения определяется не позднее ранней из следующих операций: i) инициализации верхнеуровневого класса или интерфейса и ii) инициализации любого вложенного класса или интерфейса в верхнеуровневом классе или интерфейсе. После определения, возможность активации утверждений для верхнеуровневого класса или интерфейса изменить нельзя.
Утверждение assert, выполняемое до завершения инициализации своего класса или интерфейса, активировано.
Это правило мотивировано случаем, требующим специального рассмотрения. Напомним, что состояние утверждения класса устанавливается не позднее времени его инициализации. Возможно, хотя и нежелательно в общем случае, выполнение методов или конструкторов до инициализации. Это может произойти, когда в иерархии классов есть цикличность в статической инициализации, как в следующем примере:
public class Foo {
public static void main(String[] args) {
Baz.testAsserts();
// Will execute after Baz is initialized.
}
}
class Bar {
static {
Baz.testAsserts();
// Will execute before Baz is initialized!
}
}
class Baz extends Bar {
static void testAsserts() {
boolean enabled = false;
assert enabled = true;
System.out.println("Asserts " +
(enabled ? "enabled" : "disabled"));
}
}
Вызов Baz.testAsserts() приводит к инициализации Baz. Прежде чем это произойдёт, необходимо инициализировать Bar. Статический инициализатор Bar снова вызывает Baz.testAsserts(). Поскольку инициализация Baz уже происходит в текущей нити, второй вызов выполняется немедленно, хотя Baz не инициализирован (§12.4.2).
Из-за правила выше, если программа выше выполняется без активации утверждений, она должна вывести:
Asserts enabled Asserts disabled
Деактивированное утверждение assert ничего не делает. В частности, ни Expression1, ни Expression2 (если оно присутствует), не вычисляются. Выполнение деактивированного утверждения assert всегда завершается нормально.
Активированное утверждение assert выполняется путём сначала вычисления Expression1. Если результат имеет тип Boolean, он подвергается преобразованию разворачивания (§5.1.8).
Если вычисление Expression1 или последующее преобразование разворачивания (если таковое имеется) завершается прерыванием по какой-либо причине, утверждение assert завершается прерыванием по той же причине.
В противном случае выполнение продолжается, принимая решение на основе значения Expression1:
-
Если значение равно
true, никаких дальнейших действий не предпринимается, и утверждениеassertзавершается нормально. -
Если значение равно
false, поведение выполнения зависит от наличия Expression2:-
Если Expression2 присутствует, оно вычисляется. Затем:
-
Если вычисление завершается прерыванием по какой-либо причине, утверждение
assertзавершается прерыванием по той же причине. -
Если вычисление завершается нормально, создаётся экземпляр
AssertionError, чьё сообщение "подробности" — это результирующее значение Expression2. Затем:-
Если создание экземпляра завершается прерыванием по какой-либо причине, утверждение
assertзавершается прерыванием по той же причине. -
Если создание экземпляра завершается нормально, утверждение
assertзавершается прерыванием путём выбрасывания нового созданного объектаAssertionError.
-
-
-
Если Expression2 отсутствует, создаётся экземпляр
AssertionErrorбез сообщения "подробности". Затем:-
Если создание экземпляра завершается прерыванием по какой-либо причине, утверждение
assertзавершается прерыванием по той же причине. -
Если создание экземпляра завершается нормально, утверждение
assertзавершается прерыванием путём выбрасывания нового созданного объектаAssertionError.
-
-
Как правило, проверка утверждений активирована во время разработки и тестирования программы, и деактивирована во время развертывания, для повышения производительности.
Поскольку утверждения могут быть деактивированы, программы не должны предполагать, что выражения, содержащиеся в утверждениях, будут вычисляться. Поэтому эти булевы выражения, как правило, должны быть свободны от побочных эффектов. Вычисление такого булева выражения не должно влиять на состояние, видимое после завершения вычисления. Не является незаконным, чтобы булево выражение, содержащееся в утверждении, имело побочный эффект, но это, как правило, нежелательно, так как может привести к изменению поведения программы в зависимости от того, были ли активированы утверждения или нет.
С учётом этого, утверждения не следует использовать для проверки аргументов в public методах. Проверка аргументов обычно является частью контракта метода, и этот контракт должен соблюдаться независимо от того, активированы утверждения или нет.
Вторая проблема использования утверждений для проверки аргументов заключается в том, что ошибочные аргументы должны вызывать соответствующее исключение во время выполнения (например, IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Несрабатывание утверждения не вызовет соответствующего исключения. Опять же, использование утверждений для проверки аргументов в public методах не является незаконным, но это, как правило, нежелательно. Предполагается, что AssertionError никогда не будет перехвачен, но это возможно, поэтому правила для утверждений try должны обрабатывать утверждения, появляющиеся в блоке try, аналогично текущему обработке утверждений throw.
Оператор switch передает управление одному из нескольких операторов в зависимости от значения выражения.
Тип Выражения должен быть char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления (§8.9), иначе произойдёт ошибка компиляции.
Тело оператора switch называется блоком switch. Любой оператор, непосредственно содержащийся в блоке switch, может быть помечен одной или несколькими метками switch, которые являются case или default метками. Каждая case метка имеет case константу, которая представляет собой либо константное выражение, либо имя константы перечисления. Метки switch и их case константы считаются связанными с оператором switch.
Для оператора switch все из перечисленного ниже должно быть истинно, иначе произойдёт ошибка компиляции:
-
Каждая
caseконстанта, связанная с операторомswitch, должна быть совместима с присваиванием с типомswitchвыражения оператора switch (§5.2). -
Если тип выражения оператора
switch— тип перечисления, то каждаяcaseконстанта, связанная с операторомswitch, должна быть константой этого типа перечисления. -
Ни две
caseконстанты, связанные с операторомswitch, не должны иметь одинаковое значение. -
Ни одна
caseконстанта, связанная с операторомswitch, не должна бытьnull. -
Максимум одна
defaultметка может быть связана с операторомswitch.
Запрет использования null как case константы предотвращает создание кода, который никогда не будет выполнен. Если выражение оператора switch имеет ссылочный тип, то есть String, упакованный примитивный тип или тип перечисления, то при выполнении будет брошено исключение, если выражение приведёт к значению null. По мнению разработчиков языка программирования Java, это лучше, чем пропустить оператор switch или выполнить операторы (если есть) после метки (если есть) в молчаливом режиме.
Компилятор Java рекомендуется (но не обязан) выдать предупреждение, если оператор switch на выражении типа перечисления не содержит метки для всех констант перечисления. Такой оператор switch без действия выполнит молча, если выражение приведёт к одной из отсутствующих констант.
В C и C++ тело оператора switch может быть оператором, а операторы с метками case не должны обязательно содержаться в этом операторе. Рассмотрим простой цикл:
for (i = 0; i < n; ++i) foo();
где n известно, что положительно. Трюк, известный как устройство Даффа, может использоваться в C или C++ для развертывания цикла, но это не допустимый код на языке программирования Java:
int q = (n+7)/8;
switch (n%8) {
case 0: do { foo(); // Great C hack, Tom,
case 7: foo(); // but it's not valid here.
case 6: foo();
case 5: foo();
case 4: foo();
case 3: foo();
case 2: foo();
case 1: foo();
} while (--q > 0);
}
К счастью, этот трюк, кажется, не широко известен или используется. Кроме того, он менее необходим в наши дни; такой вид преобразования кода надёжно передан современным оптимизирующим компиляторам.
При выполнении оператора switch сначала вычисляется выражение. Если выражение приведёт к значению null, будет брошено исключение, и оператор switch завершится внезапно по этой причине. В противном случае, если результат имеет ссылочный тип, он подвергается приведению к неупакованному типу (§5.1.8).
Если вычисление выражения или последующее преобразование к неупакованному типу (если применимо) завершится внезапно по какой-либо причине, оператор switch завершится внезапно по той же причине.
В противном случае выполнение продолжится сравнением значения выражения с каждой case константой, и есть выбор:
-
Если одна из
caseконстант равна значению выражения, то мы говорим, что меткаcaseсоответствует. Все операторы после соответствующей меткиcaseв блокеswitch, если таковые имеются, выполняются последовательно.Если все эти операторы завершатся нормально или если после соответствующей метки
caseнет операторов, то весь операторswitchзавершится нормально. -
Если ни одна из меток
caseне соответствует, но есть меткаdefault, то все операторы после меткиdefaultв блокеswitch, если таковые имеются, выполняются последовательно.Если все эти операторы завершатся нормально или если после метки
defaultнет операторов, то весь операторswitchзавершится нормально. -
Если ни одна из меток
caseне соответствует и нет меткиdefault, то дальнейших действий не предпринимается, и операторswitchзавершится нормально.
Если любой оператор, непосредственно содержащийся в теле блока Block оператора switch, завершится внезапно, то это обрабатывается следующим образом:
-
Если выполнение оператора Statement завершилось внезапно из-за
breakбез метки, то дальнейших действий не предпринимается, и операторswitchзавершится нормально. -
Если выполнение оператора Statement завершилось внезапно по любой другой причине, то оператор
switchзавершится внезапно по той же причине.Случай внезапного завершения из-за
breakс меткой обрабатывается общим правилом для операторов с метками (§14.7).
Пример 14.11-1. Проход в операторе switch
Как и в C и C++, выполнение операторов в блоке switch "проходит через метки".
Например, программа:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
содержит блок switch, в котором код для каждой case переходит в код для следующей case. В результате программа выводит:
many too many one too many
Если код не должен проходить через case в case таким образом, то нужно использовать break операторы, как в этом примере:
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
}
}
Эта программа выводит:
one two many
Оператор while выполняет Выражение и Оператор многократно до тех пор, пока значение Выражения не станет false.
Выражение должно иметь тип boolean или Boolean, в противном случае возникает ошибка компиляции.
Оператор цикла while выполняется путём сначала вычисления Выражения. Если результат имеет тип Boolean, он преобразуется к базовому типу (§5.1.8).
Если вычисление Выражения или последующее преобразование (если таковое имеется) прерывается по какой-либо причине, то оператор цикла while прерывается по той же причине.
В противном случае выполнение продолжается с выбором, основанным на результирующем значении:
-
Если значение равно
true, то выполняется вложенный Оператор. Затем следует выбор:-
Если выполнение Оператора завершилось нормально, то оператор цикла while выполняется снова, начиная с повторного вычисления Выражения.
-
Если выполнение Оператора завершилось прерыванием, см. §14.12.1.
-
-
Если (возможно, преобразованное) значение Выражения равно
false, дальнейших действий не происходит, и оператор while завершается нормально.Если (возможно, преобразованное) значение Выражения равно
falseпри первом его вычислении, то Оператор не выполняется.
Прерывание выполнения вложенного Оператора обрабатывается следующим образом:
-
Если выполнение Оператора прервалось из-за
breakбез метки, дальнейших действий не происходит, и оператор цикла while завершается нормально. -
Если выполнение Оператора прервалось из-за
continueбез метки, то оператор цикла while выполняется снова. -
Если выполнение Оператора прервалось из-за
continueс меткойL, то следует выбор:-
Если оператор цикла while имеет метку
L, то оператор цикла while выполняется снова. -
Если оператор цикла while не имеет метки
L, оператор цикла while прерывается из-заcontinueс меткойL.
-
-
Если выполнение Оператора прервалось по любой другой причине, то оператор цикла while прерывается по той же причине.
Случай прерывания из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Оператор do выполняет Оператор и Выражение многократно до тех пор, пока значение Выражения не станет false.
Тип Выражения должен быть boolean или Boolean; в противном случае произойдёт ошибка компиляции.
Оператор do выполняется путём выполнения сначала Оператора. Затем происходит выбор:
-
Если выполнение Оператора завершается нормально, то вычисляется Выражение. Если результат имеет тип
Boolean, он преобразуется к своему упакованному виду (§5.1.8).Если вычисление Выражения или последующее преобразование к упакованному виду (если таковое имеется) завершается аварийно по какой-либо причине, то оператор
doтакже завершается аварийно по той же причине.В противном случае, выбор зависит от полученного значения:
-
Если значение равно
true, то операторdoвыполняется повторно. -
Если значение равно
false, то дальнейшие действия не выполняются, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершается аварийно, см. §14.13.1.
Выполнение оператора do всегда выполняет содержащийся в нём Оператор по меньшей мере один раз.
Аварийное завершение содержащегося Оператора обрабатывается следующим образом:
-
Если выполнение Оператора завершается аварийно из-за
breakбез метки, то дальнейшие действия не выполняются, и операторdoзавершается нормально. -
Если выполнение Оператора завершается аварийно из-за
continueбез метки, то вычисляется Выражение. Затем происходит выбор, основанный на полученном значении:-
Если значение равно
true, то операторdoвыполняется повторно. -
Если значение равно
false, то дальнейшие действия не выполняются, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершается аварийно из-за
continueс меткойL, то происходит выбор:-
Если оператор
doимеет меткуL, то вычисляется Выражение. Затем происходит выбор:-
Если значение Выражения равно
true, то операторdoвыполняется повторно. -
Если значение Выражения равно
false, то дальнейшие действия не выполняются, и операторdoзавершается нормально.
-
-
Если оператор
doне имеет меткиL, операторdoзавершается аварийно из-заcontinueс меткойL.
-
-
Если выполнение Оператора завершается аварийно по любой другой причине, оператор
doзавершается аварийно по той же причине.Случай аварийного завершения из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Пример 14.13-1. Оператор do
Следующий код представляет собой один из возможных вариантов реализации метода toHexString класса Integer:
public static String toHexString(int i) {
StringBuffer buf = new StringBuffer(8);
do {
buf.append(Character.forDigit(i & 0xF, 16));
i >>>= 4;
} while (i != 0);
return buf.reverse().toString();
}
Поскольку необходимо сгенерировать по крайней мере одну цифру, оператор do является подходящей управляющей структурой.
Оператор for имеет две формы:
-
Базовый оператор
for. -
Расширенный оператор
for
Базовый оператор цикла for выполняет некоторый код инициализации, затем выполняет выражение, оператор и некоторый код обновления многократно, пока значение выражения не станет false.
У выражения должен быть тип boolean или Boolean, в противном случае произойдёт ошибка компиляции.
Область видимости и перекрытие локальной переменной, объявленной в части ForInit оператора цикла, указаны в §6.3 и §6.4.
Оператор цикла for выполняется путём выполнения кода ForInit:
-
Если код ForInit представляет собой список операторных выражений (§14.8), выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются.
Если вычисление любого выражения завершается прерыванием по какой-либо причине, оператор цикла
forзавершается прерыванием по той же причине; все операторные выражения ForInit справа от выражения, завершившегося прерыванием, не вычисляются. -
Если код ForInit представляет собой объявление локальной переменной (§14.4), он выполняется так, как если бы это был оператор объявления локальной переменной, появившийся в блоке.
Если выполнение объявления локальной переменной завершается прерыванием по какой-либо причине, оператор цикла
forзавершается прерыванием по той же причине. -
Если часть ForInit отсутствует, никаких действий не производится.
Далее выполняется шаг итерации оператора for:
-
Если Выражение присутствует, оно вычисляется. Если результат имеет тип
Boolean, он преобразуется в базовое представление (§5.1.8).Если вычисление Выражения или последующего преобразования в базовое представление (если таковое есть) завершается прерыванием, оператор цикла
forзавершается прерыванием по той же причине.В противном случае, дальнейшее действие зависит от наличия или отсутствия Выражения и полученного значения, если Выражение присутствует; см. следующий пункт.
-
Если Выражение отсутствует или присутствует, но его значение (включая возможное преобразование в базовое представление) равно
true, то содержимый Оператор выполняется. Далее выбирается действие:-
Если выполнение Оператора завершается нормально, то выполняются следующие два шага последовательно:
-
Сначала, если часть ForUpdate присутствует, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются. Если вычисление любого выражения завершается прерыванием по какой-либо причине, оператор цикла
forзавершается прерыванием по той же причине; все операторные выражения ForUpdate справа от выражения, завершившегося прерыванием, не вычисляются.Если часть ForUpdate отсутствует, никаких действий не производится.
-
Во-вторых, выполняется ещё один шаг итерации.
-
-
Если выполнение Оператора завершается прерыванием, см. §14.14.1.3.
-
-
Если Выражение присутствует и значение результата его вычисления (включая возможное преобразование в базовое представление) равно
false, то никаких дальнейших действий не выполняется и оператор циклаforзавершается нормально.Если (возможно, преобразованное) значение Выражения равно
falseв первый раз, то Оператор не выполняется.
Если Выражение отсутствует, то единственный способ, которым оператор цикла for может завершиться нормально, — это использование оператора break.
Прерывание выполнения содержащегося выражения обрабатывается следующим образом:
-
Если выполнение выражения прерывается из-за
breakбез метки, никаких дальнейших действий не выполняется, и операторforзавершается нормально. -
Если выполнение выражения прерывается из-за
continueбез метки, последовательно выполняются следующие два шага:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется еще один шаг итерации
for.
-
-
Если выполнение выражения прерывается из-за
continueс меткойL, есть выбор:-
Если оператор
forимеет меткуL, последовательно выполняются следующие два шага:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется еще один шаг итерации
for.
-
-
Если оператор
forне имеет меткиL, операторforзавершается прерыванием из-заcontinueс меткойL.
-
-
Если выполнение выражения прерывается по любой другой причине, оператор
forзавершается прерыванием по той же причине.Обратите внимание, что случай прерывания выполнения из-за
breakс меткой обрабатывается общим правилом для меченых операторов (§14.7).
Улучшенный оператор for имеет вид:
См. §8.3 для UnannType. Следующие производства из §4.3, §8.4.1 и §8.3 приведены здесь для удобства:
Тип выражения должен быть Iterable или типом массива (§10.1), в противном случае возникает ошибка компиляции.
Объявленный тип локальной переменной в заголовке улучшенного оператора for обозначается UnannType, если в UnannType и VariableDeclaratorId отсутствуют скобки, и определяется в §10.2 в противном случае.
Область видимости и перекрытие локальной переменной, объявленной в заголовке улучшенного оператора for, определяются в §6.3 и §6.4.
Значение улучшенного оператора for задается переводом в базовый оператор for, как следует:
-
Если тип выражения является подтипом
Iterable, перевод выполняется следующим образом.Если тип выражения является подтипом
Iterable<X>для некоторого типа аргумента X, пусть I будет типомjava.util.Iterator<X>; в противном случае, пусть I будет исходным типомjava.util.Iterator.Улучшенный оператор for эквивалентен базовому оператору for вида:
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} TargetType Identifier = (TargetType) #i.next(); Statement }
#i— это автоматически сгенерированный идентификатор, отличный от любых других идентификаторов (автоматически сгенерированных или других), находящихся в области видимости (§6.3) в точке, где находится улучшенный оператор for.Если объявленный тип локальной переменной в заголовке улучшенного оператора for является ссылочным типом, то TargetType — это этот объявленный тип; в противном случае TargetType является верхней границей преобразования захвата (§5.1.10) типа аргумента I или
Object, если I является исходным.Например, этот код:
List<? extends Integer> l = ... for (float i : l) ...будет преобразован в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); ... -
В противном случае, выражение обязательно имеет тип массива T
[].Пусть
L1...Lm— это (возможно, пустая) последовательность меток, непосредственно предшествующих улучшенному оператору for.Улучшенный оператор for эквивалентен базовому оператору for вида:
T
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} TargetType Identifier = #a[#i]; Statement }#aи#i— это автоматически сгенерированные идентификаторы, отличные от любых других идентификаторов (автоматически сгенерированных или других), находящихся в области видимости в точке, где находится улучшенный оператор for.TargetType — это объявленный тип локальной переменной в заголовке улучшенного оператора for.
Пример 14.14-1. Улучшенный оператор for и массивы
Следующая программа, вычисляющая сумму целочисленного массива, демонстрирует, как работает улучшенный оператор for для массивов:
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
Пример 14.14-2. Улучшенный оператор for и преобразование авто-распаковки
Следующая программа сочетает улучшенный оператор for с авто-распаковкой, чтобы преобразовать гистограмму в таблицу частот:
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
Оператор break передает управление из окружающего оператора.
Оператор break без метки пытается передать управление ближайшему вложенному оператору цикла, оператору switch, оператору for или оператору try-with-resources в немедленно окружающем методе или инициализаторе; этот оператор, который называется целью break, затем немедленно завершается нормально.
Точнее, оператор break без метки всегда завершается прерыванием, причина которого — оператор break без метки.
Если ни оператор цикла, ни оператор switch, ни оператор for, ни оператор try-with-resources в непосредственно окружающем методе, конструкторе или инициализаторе не содержит оператор break, возникает ошибка компиляции.
Оператор break с меткой Идентификатор пытается передать управление окружающему оператору с той же меткой Идентификатор (п. §14.7); этот оператор, который называется целью break, затем немедленно завершается нормально. В этом случае цель break не обязательно должна быть оператором цикла, оператором switch, оператором for или оператором try-with-resources.
Точнее, оператор break с меткой Идентификатор всегда завершается прерыванием, причина которого — оператор break с меткой Идентификатор.
Оператор break должен ссылаться на метку в непосредственно окружающем методе, конструкторе, инициализаторе или блоке lambda. Переходов за пределы блока нет. Если в непосредственно окружающем методе, конструкторе, инициализаторе или блоке lambda нет оператора с меткой Идентификатор, содержащего оператор break, возникает ошибка компиляции.
Таким образом, оператор break всегда завершается прерыванием.
В предыдущих описаниях используется фраза «пытается передать управление», а не просто «передает управление», потому что если есть операторы try-with-resources (§14.20) внутри цели break, блоки try или блоки catch которых содержат оператор break, то все блоки finally этих операторов try-with-resources выполняются в порядке от вложенных к наружным, прежде чем управление передается цели break. Прерывание блока finally может нарушить передачу управления, начатую оператором break.
Пример 14.15-1. Оператор break
В приведенном ниже примере математический граф представлен массивом массивов. Граф состоит из набора узлов и набора ребер; каждое ребро — стрелка, указывающая от некоторого узла к другому узлу или от узла к самому себе. В этом примере предполагается, что нет избыточных ребер; то есть для любых двух узлов P и Q, где Q может быть таким же, как P, не более одного ребра от P до Q.
Узлы представлены целыми числами, и существует ребро от узла i к узлу edges[ для каждого i][j]i и j, для которых ссылка на массив edges[ не вызывает исключение i][j]ArrayIndexOutOfBoundsException.
Задача метода loseEdges, учитывая целые числа i и j, состоит в построении нового графа путем копирования данного графа, но с исключением ребра от узла i к узлу j, если оно есть, и ребра от узла j к узлу i, если оно есть:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
for (int k = 0; k < n; ++k) {
edgelist:
{
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
break edgelist;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelist
}
return new Graph(newedges);
}
}
Обратите внимание на использование двух меток операторов, edgelist и search, и использование операторов break. Это позволяет использовать код, копирующий список, опуская одно ребро, для двух отдельных тестов: теста на ребро от узла i к узлу j и теста на ребро от узла j к узлу i.
Оператор continue может встречаться только в операторе while, операторе do или операторе for; операторы этих трех типов называются итерационными операторами. Управление передается точке продолжения цикла итерационного оператора.
Оператор continue без метки пытается передать управление ближайшему вложенному оператору while, оператору do или оператору for в немедленно окружающем методе, конструкторе или инициализаторе; этот оператор, который называется целью continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, такой оператор continue всегда завершается прерыванием, причина которого — оператор continue без метки.
Если ни оператор while, ни оператор do, ни оператор for в непосредственно окружающем методе, конструкторе или инициализаторе не содержит оператор continue, возникает ошибка компиляции.
Оператор continue с меткой Идентификатор пытается передать управление окружающему оператору с той же меткой Идентификатор (п. §14.7); этот оператор, который называется целью continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, оператор continue с меткой Идентификатор всегда завершается прерыванием, причина которого — оператор continue с меткой Идентификатор.
Целью continue должен быть оператор while, оператор do или оператор for, иначе возникает ошибка компиляции.
Оператор continue должен ссылаться на метку в непосредственно окружающем методе, конструкторе, инициализаторе или блоке lambda. Переходов за пределы блока нет. Если в непосредственно окружающем методе, конструкторе, инициализаторе или блоке lambda нет оператора с меткой Идентификатор, содержащего оператор continue, возникает ошибка компиляции.
Таким образом, оператор continue всегда завершается прерыванием.
См. описания оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки прерывания из-за оператора continue.
В предыдущих описаниях используется фраза «пытается передать управление», а не просто «передает управление», потому что если есть операторы try-with-resources (§14.20) внутри цели continue, блоки try или блоки catch которых содержат оператор continue, то все блоки finally этих операторов try-with-resources выполняются в порядке от вложенных к наружным, прежде чем управление передается цели continue. Прерывание блока finally может нарушить передачу управления, начатую оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла for. Этот break можно заменить оператором continue, если сам цикл for помечен меткой:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Какой из них использовать, если это необходимо, в основном зависит от стиля программирования.
Оператор return возвращает управление вызывающей стороне метода (§8.4), §15.12) или конструктора (§8.8), §15.9).
Оператор return содержится в самом внутреннем конструкторе, методе, инициализаторе или лямбда-выражении, тело которого включает в себя оператор return.
Если оператор return содержится в инициализаторе экземпляра или статическом инициализаторе (§8.6, §8.7), то это ошибка компиляции.
Оператор return без Выражения должен быть содержащимся в одном из следующих, в противном случае произойдёт ошибка компиляции:
Оператор return без Выражения пытается передать управление вызывающей стороне метода, конструктора или тела лямбда-выражения, которое его содержит. Точнее, оператор return без Выражения всегда завершается прерывисто, причиной чего является возврат без значения.
Оператор return с Выражением должен быть содержащимся в одном из следующих, в противном случае произойдёт ошибка компиляции:
-
Метод, объявленный для возврата значения
-
Лямбда-выражение
Выражение должно обозначать переменную или значение, в противном случае произойдёт ошибка компиляции.
При появлении оператора return с Выражением в объявлении метода, Выражение должно быть присваиваемым (§5.2) типу возврата объявленного метода, в противном случае произойдёт ошибка компиляции.
Оператор return с Выражением пытается передать управление вызывающей стороне метода или тела лямбда-выражения, которое его содержит; значение Выражения становится значением вызова метода. Более точно, выполнение оператора return с Выражением сначала оценивает Выражение. Если оценка Выражения завершается прерывисто по какой-либо причине, то и оператор return завершается прерывисто по этой причине. Если оценка Выражения завершается нормально, произведя значение V, то оператор return завершается прерывисто, причиной чего является возврат со значением V.
Если выражение имеет тип float и не является FP-строгим (§15.4), то значение может быть элементом либо множества значений с плавающей точкой, либо множества значений с расширенным экспоненциальным форматом с плавающей точкой (§4.2.3). Если выражение имеет тип double и не является FP-строгим, то значение может быть элементом либо множества значений с двойной точностью, либо множества значений с расширенным экспоненциальным форматом с двойной точностью.
Можно видеть, что оператор return всегда завершается прерывисто.
В предыдущих описаниях используется фраза «пытается передать управление», а не просто «передаёт управление», потому что, если есть операторы try (§14.20) внутри метода или конструктора, блоки try или предложения catch которых содержат оператор return, то любые предложения finally этих операторов try будут выполняться по порядку, от внутреннего к внешнему, прежде чем управление будет передано вызывающей стороне метода или конструктора. Прерывистое завершение предложения finally может нарушить передачу управления, инициированную оператором return.
Оператор throw вызывает исключение (§11 (Исключения)) для обработки. Результатом является немедленный переход управления (§11.3), который может завершить выполнение нескольких операторов и вызовов конструкторов, инициализаторов экземпляров, статических инициализаторов и инициализаторов полей, а также вызовов методов, пока не будет найден оператор try (§14.20), который перехватывает сгенерированное значение. Если такой оператор try не найден, то выполнение потока (§17 (Потоки и блокировки)), который выполнил оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.
Выражение Выражение в операторе throw должно обозначать переменную или значение ссылочного типа, присваиваемого (§5.2) типу Throwable, или обозначает нулевую ссылку, в противном случае происходит ошибка компиляции.
Ссылочный тип выражения Выражение всегда будет типом класса (поскольку типы интерфейсов не присваиваются типу Throwable), который не является параметризованным (поскольку подкласс типа Throwable не может быть обобщённым (§8.1.2)).
Должно выполняться хотя бы одно из следующих трёх условий, в противном случае произойдёт ошибка компиляции:
-
Тип выражения Выражение — это класс исключения, которое не проверяется (§11.1.1), или тип null (§4.1).
-
Оператор
throwнаходится внутри блокаtryоператораtry(§14.20), и операторtryне может сгенерировать исключение типа Выражение. (В этом случае говорят, что брошенное значение перехвачено операторомtry). -
Оператор
throwсодержится в методе или объявлении конструктора, и тип выражения Выражение присваивается (§5.2) по крайней мере одному типу, указанному в списке исключений (§8.4.6, §8.8.5) объявления.
Типы исключений, которые может сгенерировать оператор throw, указаны в §11.2.2.
Оператор throw сначала вычисляет выражение Выражение. Затем:
-
Если вычисление выражения Выражение прерывается по какой-либо причине, то оператор
throwпрерывается по той же причине. -
Если вычисление выражения Выражение завершается успешно, генерируя значение, которое не является
null, то операторthrowпрерывается, причиной являетсяthrowсо значениемV. -
Если вычисление выражения Выражение завершается успешно, генерируя
nullзначение, то создаётся экземплярV'классаNullPointerExceptionи выбрасывается вместоnull. Операторthrowпрерывается, причиной являетсяthrowсо значениемV'.
Таким образом, оператор throw всегда завершается прерыванием.
Если есть какие-либо окружающие операторы try (§14.20), блоки try которых содержат оператор throw, то любые блоки finally этих операторов try выполняются по мере передачи управления наружу, пока брошенное значение не будет перехвачено. Обратите внимание, что внезапное завершение блока finally может нарушить передачу управления, инициированную оператором throw.
Если оператор throw находится внутри объявления метода или лямбда-выражения, но его значение не перехватывается каким-либо оператором try, который его содержит, то вызов метода завершается внезапно из-за исключения throw.
Если оператор throw находится внутри объявления конструктора, но его значение не перехватывается каким-либо оператором try, который его содержит, то выражение создания экземпляра класса, вызвавшего конструктор, завершится внезапно из-за исключения throw (§15.9.4).
Если оператор throw находится внутри статического инициализатора (§8.7), то проверка во время компиляции (§11.2.3) гарантирует, что его значение всегда является неконтролируемым исключением или что его значение всегда перехватывается каким-либо оператором try, который его содержит. Если во время выполнения, несмотря на эту проверку, значение не перехватывается каким-либо оператором try, который содержит оператор throw, то значение повторно выбрасывается, если оно является экземпляром класса Error или одного из его подклассов; в противном случае оно упаковывается в объект ExceptionInInitializerError, который затем выбрасывается (§12.4.2).
Если оператор throw находится внутри инициализатора экземпляра (§8.6), то проверка во время компиляции (§11.2.3) гарантирует, что его значение всегда является неконтролируемым исключением или что его значение всегда перехватывается каким-либо оператором try, который его содержит, или тип выброшенного исключения (или один из его суперклассов) содержится в списке исключений throws каждого конструктора класса.
По соглашению, объявленные пользователем типы выбрасываемых исключений обычно объявляются подклассами класса Exception, который является подклассом класса Throwable (§11.1.1).
Оператор synchronized приобретает замок взаимного исключения (§17.1) от имени потока выполнения, выполняет блок, затем освобождает замок. Пока выполняющийся поток владеет замком, никакой другой поток не может приобрести замок.
Тип Выражения должен быть ссылочным типом; в противном случае произойдёт ошибка компиляции.
Оператор synchronized выполняется путём предварительного вычисления Выражения. Затем:
-
Если вычисление Выражения завершается аномально по какой-либо причине, то оператор synchronized завершается аномально по той же причине.
-
В противном случае, если значение Выражения является
null, выбрасывается исключениеNullPointerException. -
В противном случае, пусть не-
nullзначение Выражения будетV. Выполняющийся поток блокирует монитор, связанный сV. Затем выполняется Блок, после чего следует выбор:-
Если выполнение Блока завершается нормально, то монитор разблокируется и оператор synchronized завершается нормально.
-
Если выполнение Блока завершается аномально по любой причине, то монитор разблокируется и оператор synchronized завершается аномально по той же причине.
-
Замки, приобретённые операторами synchronized, идентичны замкам, которые неявно приобретаются синхронизированными методами (§8.4.3.6). Один и тот же поток может приобретать замок более одного раза.
Приобретение замка, связанного с объектом, само по себе не препятствует другим потокам обращаться к полям объекта или вызывать не-synchronized методы на объекте. Другие потоки также могут использовать synchronized методы или оператор synchronized обычным способом для достижения взаимного исключения.
Пример 14.19-1. Оператор synchronized
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
Эта программа выводит на экран:
made it!
Обратите внимание, что эта программа застопорится, если одному потоку не разрешено блокировать монитор более одного раза.
Оператор try выполняет блок кода. Если возникает исключение, и оператор try содержит одну или несколько обработок catch, способных его перехватить, то управление будет передано первой такой обработке catch. Если оператор try содержит блок finally, то другой блок кода будет выполнен независимо от того, завершился ли блок try нормально или неожиданно, а также независимо от того, была ли первой получена обработка catch.
См. §8.3 для ТипКласса. Следующие производные из §4.3, §8.3 и §8.4.1 приведены для удобства:
Блок Блок сразу после ключевого слова try называется блоком try оператора try.
Блок Блок сразу после ключевого слова finally называется блоком finally оператора try.
Оператор try может содержать catch обработок, также называемых обработчиками исключений.
Обработка catch объявляет ровно один параметр, который называется параметром исключения.
Ошибка компиляции, если final появляется более одного раза в качестве модификатора для объявления параметра исключения.
Область действия и перекрытие параметра исключения указаны в §6.3 и §6.4.
Параметр исключения может обозначать свой тип как один тип класса или объединение двух или более типов классов (называемых альтернативами). Альтернативы объединения разделяются синтаксически |.
Обработка catch, параметр исключения которой обозначен как один тип класса, называется однотипной обработкой catch.
Обработка catch, параметр исключения которой обозначен как объединение типов, называется многотипной обработкой catch.
Каждый тип класса, используемый в обозначении типа параметра исключения, должен быть классом Throwable или подклассом Throwable, иначе произойдет ошибка компиляции.
Ошибка компиляции, если переменная типа используется в обозначении типа параметра исключения.
Ошибка компиляции, если объединение типов содержит две альтернативы Di и Dj (i ≠ j), где Di является подтипом Dj (§4.10.2).
Объявленный тип параметра исключения, обозначающего его тип одним типом класса, — это тот тип класса.
Объявленный тип параметра исключения, обозначающего его тип как объединение альтернатив D1 | D2 | ... | Dn, — это lub(D1, D2, ..., Dn).
Параметр исключения многотипной обработки catch неявно объявлен final, если он не объявлен явно final.
Ошибка компиляции, если параметр исключения, неявно или явно объявленный final, присваивается в теле обработки catch.
Параметр исключения однотипной обработки catch никогда не объявляется неявно final, но может быть объявлен явно final или быть фактически final (§4.12.4).
Неявно объявленный final параметр исключения является final по умолчанию, а фактически final параметр исключения — как бы final по способу использования. Параметр исключения многотипной обработки catch неявно объявлен final, поэтому никогда не будет левым операндом оператора присваивания, но не считается фактически final.
Если параметр исключения фактически final (в однотипной обработке catch) или неявно final (в многотипной обработке catch), то добавление явного модификатора final к его объявлению не приведет к ошибкам компиляции. С другой стороны, если параметр исключения однотипной обработки catch явно объявлен final, то удаление модификатора final может привести к ошибкам компиляции, потому что параметр исключения, теперь считающийся фактически final, больше не может быть использован объявлениями анонимных и локальных классов в теле обработки catch. Если ошибок компиляции нет, то можно дополнительно изменить программу так, чтобы параметр исключения был повторно присвоен в теле обработки catch, и таким образом он больше не будет считаться фактически final.
Типы исключений, которые может вызвать оператор try, указаны в §11.2.2.
Взаимосвязь исключений, вызываемых блоком try оператора try и перехватываемых обработками catch (если таковые имеются) оператора try, указана в §11.2.3.
Обработчики исключений рассматриваются слева направо: самая ранняя возможная обработка catch принимает исключение, получая в качестве аргумента объект исключения, как указано в §11.3.
Многоточечный catch оператор можно рассматривать как последовательность одноточечных catch операторов. То есть, оператор catch, где тип параметра исключения обозначен как объединение D1|D2|...|Dn, эквивалентен последовательности из n catch операторов, где типы параметров исключений соответственно равны D1, D2, ..., Dn. В блоке каждого из n catch операторов, объявленный тип параметра исключения равен lub(D1, D2, ..., Dn). Например, следующий код:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
семантически эквивалентен следующему коду:
try {
... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
final ReflectiveOperationException ex = ex1;
... body ...
}
catch (final IllegalAccessException ex2) {
final ReflectiveOperationException ex = ex2;
... body ...
}
где многоточечный catch оператор с двумя альтернативами был преобразован в два одноточечных catch оператора, по одному для каждой альтернативы. Компилятор Java не обязан и не рекомендуется компилировать многоточечный catch оператор, дублируя код таким образом, поскольку возможно представить многоточечный catch оператор в файле class без дублирования.
Оператор finally гарантирует, что блок finally выполняется после блока try и любого блока catch, который мог быть выполнен, независимо от того, как управление покидает блок try или блок catch. Обработка блока finally довольно сложная, поэтому два случая оператора try с блоком finally и без него описываются отдельно (§14.20.1, §14.20.2).
Оператор try разрешается пропускать операторы catch и finally, если это оператор try-с-ресурсами (§14.20.3).
Оператор try без блока finally выполняется путем сначала выполнения блока try. Затем возникает выбор:
-
Если выполнение блока
tryзавершается нормально, то никаких дальнейших действий не предпринимается, и операторtryзавершается нормально. -
Если выполнение блока
tryзавершается прерывисто из-заthrowзначенияV, то возникает выбор:-
Если тип времени выполнения
Vсовместим с присваиванием (§5.2) с любым классом исключения, обрабатываемым операторомcatchоператораtry, то выбирается первый (левый) такой операторcatch. ЗначениеVприсваивается параметру выбранного оператораcatch, и выполняется Блок этого оператораcatch, после чего возникает выбор:-
Если этот блок завершается нормально, то оператор
tryзавершается нормально. -
Если этот блок завершается прерывисто по любой причине, то оператор
tryзавершается прерывисто по той же причине.
-
-
Если тип времени выполнения
Vне совместим с присваиванием с классом исключения, обрабатываемым любым операторомcatchоператораtry, то операторtryзавершается прерывисто из-заthrowзначенияV.
-
-
Если выполнение блока
tryзавершается прерывисто по любой другой причине, то операторtryзавершается прерывисто по той же причине.
Пример 14.20.1-1. Перехват Исключение
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет два оператора catch. Тип времени выполнения исключения является BlewIt, который нельзя присвоить переменной типа RuntimeException, но можно присвоить переменной типа BlewIt, поэтому вывод примера:
Caught BlewIt
Оператор try с блоком finally выполняется путём предварительного выполнения блока try. Затем следует выбор:
-
Если выполнение блока
tryзавершается нормально, то выполняется блокfinally, и снова следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается внезапно по причинеS, то операторtryзавершается внезапно по причинеS.
-
-
Если выполнение блока
tryзавершается внезапно из-заthrowзначенияV, то следует выбор:-
Если тип
Vсовместим по присваиванию с каким-либо из типов исключений, которые можно перехватить в блокеcatchоператораtry, то выбирается первый (самый левый) такой блокcatch. ЗначениеVприсваивается параметру выбранного блокаcatch, и выполняется блок Block этого блокаcatch. Затем следует выбор:-
Если блок
catchзавершается нормально, то выполняется блокfinally. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается внезапно по любой причине, то операторtryзавершается внезапно по той же причине.
-
-
Если блок
catchзавершается внезапно по причинеR, то выполняется блокfinally. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается внезапно по причинеR. -
Если блок
finallyзавершается внезапно по причинеS, то операторtryзавершается внезапно по причинеS(и причинаRотбрасывается).
-
-
-
Если тип
Vне совместим по присваиванию с каким-либо из типов исключений, которые можно перехватить в блокеcatchоператораtry, то выполняется блокfinally. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается внезапно из-заthrowзначенияV. -
Если блок
finallyзавершается внезапно по причинеS, то операторtryзавершается внезапно по причинеS(иthrowзначенияVотбрасывается и забывается).
-
-
-
Если выполнение блока
tryзавершается внезапно по любой другой причинеR, то выполняется блокfinally, и снова следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается внезапно по причинеR. -
Если блок
finallyзавершается внезапно по причинеS, то операторtryзавершается внезапно по причинеS(и причинаRотбрасывается).
-
Пример 14.20.2-1. Обработка неперехваченного исключения с finally
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt {
throw new NullPointerException();
}
public static void main(String[] args) {
try {
blowUp();
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
} finally {
System.out.println("Uncaught Exception");
}
}
}
Эта программа выводит:
Uncaught Exception
Exception in thread "main" java.lang.NullPointerException
at Test.blowUp(Test.java:7)
at Test.main(Test.java:11)
Исключение NullPointerException (которое является типом RuntimeException), брошенное методом blowUp, не перехватывается оператором try в main, так как NullPointerException не может быть присвоено переменной типа BlewIt. Это приводит к выполнению блока finally, после чего поток, выполняющий main, единственный поток в программе, завершается из-за неперехваченного исключения, что обычно приводит к выводу имени исключения и простому стеку вызовов. Однако этот стандарт не требует вывода стека вызовов.
Проблема с требованием вывода стека вызовов заключается в том, что исключение может быть создано в одной точке программы и брошено в другой. Сохранение стека вызовов в исключении, если оно не было брошено, чрезвычайно затратно. Поэтому этот стандарт не требует вывода стека вызовов при каждом исключении.
Оператор try-with-resources параметризуется переменными (известными как ресурсы), которые инициализируются перед выполнением блока try и автоматически закрываются в обратном порядке инициализации после выполнения блока try. Обычно, при автоматическом закрытии ресурсов, необязательны разделы catch и оператор finally.
См. §8.3 для ТипБезАннотаций. Ниже приведены следующие производные правила из §4.3, §8.3 и §8.4.1 для удобства:
Спецификация ресурсов объявляет одну или несколько локальных переменных с инициализирующими выражениями, которые действуют как ресурсы для оператора try.
Ошибка компиляции возникает, если спецификация ресурсов объявляет две переменные с одинаковым именем.
Ошибка компиляции возникает, если final указан более одного раза в качестве модификатора для каждой переменной, объявленной в спецификации ресурсов.
Переменная, объявленная в спецификации ресурсов, неявно объявляется final (§4.12.4), если она не объявлена явно final.
Тип переменной, объявленной в спецификации ресурсов, должен быть подтипом AutoCloseable; в противном случае возникает ошибка компиляции.
Область действия и перекрытие переменной, объявленной в спецификации ресурсов, задаются в §6.3 и §6.4.
Ресурсы инициализируются слева направо. Если инициализация ресурса терпит неудачу (т.е., выражение инициализатора генерирует исключение), то все ресурсы, инициализированные до сих пор оператором try-with-resources, закрываются. Если все ресурсы инициализируются успешно, то блок try выполняется в обычном режиме, а затем закрываются все непустые ресурсы оператора try-with-resources.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только в случае, если он инициализирован ненулевым значением. Исключение при закрытии одного ресурса не предотвращает закрытие других ресурсов. Такое исключение подавляется, если ранее было сгенерировано исключение инициализатором, блоком try или при закрытии ресурса.
Оператор try-with-resources, спецификация ресурсов которого объявляет несколько ресурсов, рассматривается так, как будто это несколько операторов try-with-resources, каждый из которых имеет спецификацию ресурсов, объявляющую один ресурс. Когда оператор try-with-resources с n ресурсами (n > 1) переводится, результат – это оператор try-with-resources с n-1 ресурсом. После n таких переводов получается n вложенных операторов try-catch-finally, и весь перевод завершен.
Оператор try-with-resources без блоков catch или finally называется базовым оператором try-with-resources.
Значение базового оператора try-with-resources:
try ({VariableModifier} R Identifier = Expression ...) Block
определяется следующим преобразованием в объявление локальной переменной и оператор try-catch-finally:
{
final {VariableModifierNoFinal} R Identifier = Expression;
Throwable #primaryExc = null;
try ResourceSpecification_tail
Block
catch (Throwable #t) {
#primaryExc = #t;
throw #t;
} finally {
if (Identifier != null) {
if (#primaryExc != null) {
try {
Identifier.close();
} catch (Throwable #suppressedExc) {
#primaryExc.addSuppressed(#suppressedExc);
}
} else {
Identifier.close();
}
}
}
}
{VariableModifierNoFinal} определяется как {VariableModifier} без ключевого слова final, если оно присутствует.
#t, #primaryExc и #suppressedExc — автоматически сгенерированные идентификаторы, отличные от других идентификаторов (автоматически сгенерированных или других), имеющихся в области видимости в точке расположения оператора try-with-resources.
Если в спецификации ресурсов объявлен один ресурс, то ResourceSpecification_tail пуста (и оператор try-catch-finally сам по себе не является оператором try-with-resources).
Если в спецификации ресурсов объявлены более 1 ресурса, то ResourceSpecification_tail содержит 2-ой, 3-ий и т.д. ресурсы, объявленные в спецификации ресурсов в том же порядке (и оператор try-catch-finally сам по себе является оператором try-with-resources).
Правила достижимости и определенного присваивания для базового оператора try-with-resources неявно задаются вышеприведенным переводом.
В базовом операторе try-with-resources, управляющем одним ресурсом:
-
Если инициализация ресурса завершается неожиданно из-за исключения значения
V, то оператор try-with-resources завершается неожиданно из-за исключения значенияV. -
Если инициализация ресурса завершается нормально, и блок try завершается неожиданно из-за исключения значения
V, то:-
Если автоматическое закрытие ресурса завершается нормально, то оператор try-with-resources завершается неожиданно из-за исключения значения
V. -
Если автоматическое закрытие ресурса завершается неожиданно из-за исключения значения
V2, то оператор try-with-resources завершается неожиданно из-за исключения значенияV, с добавленными в список подавленных исключенийVисключенияV2.
-
-
Если инициализация ресурса завершается нормально, блок try завершается нормально, а автоматическое закрытие ресурса завершается неожиданно из-за исключения значения
V, то оператор try-with-resources завершается неожиданно из-за исключения значенияV.
В базовом операторе try-with-resources, управляющем множеством ресурсов:
-
Если инициализация ресурса завершается неожиданно из-за исключения значения
V, то:-
Если автоматическое закрытие всех успешно инициализированных ресурсов (возможно, нулевое число) завершается нормально, то оператор try-with-resources завершается неожиданно из-за исключения значения
V. -
Если автоматическое закрытие всех успешно инициализированных ресурсов (возможно, нулевое число) завершается неожиданно из-за исключений значений
V1...Vn, то оператор try-with-resources завершается неожиданно из-за исключения значенияVс оставшимися значениямиV1...Vn, добавленными в список подавленных исключенийV.
-
-
Если инициализация всех ресурсов завершается нормально, и блок try завершается неожиданно из-за исключения значения
V, то:-
Если автоматическое закрытие всех инициализированных ресурсов завершается нормально, то оператор try-with-resources завершается неожиданно из-за исключения значения
V. -
Если автоматическое закрытие одного или нескольких инициализированных ресурсов завершается неожиданно из-за исключений значений
V1...Vn, то оператор try-with-resources завершается неожиданно из-за исключения значенияVс оставшимися значениямиV1...Vn, добавленными в список подавленных исключенийV.
-
-
Если инициализация всех ресурсов завершается нормально, и блок try завершается нормально, то:
-
Если одно автоматическое закрытие инициализированного ресурса завершается неожиданно из-за исключения значения
V, а все остальные автоматические закрытия инициализированных ресурсов завершаются нормально, то оператор try-with-resources завершается неожиданно из-за исключения значенияV. -
Если более одного автоматического закрытия инициализированного ресурса завершается неожиданно из-за исключений значений
V1...Vn, то оператор try-with-resources завершается неожиданно из-за исключения значенияV1с оставшимися значениямиV2...Vn, добавленными в список подавленных исключенийV1(гдеV1— исключение от последнего ресурса, не закрывшегося, аVn— исключение от первого ресурса, не закрывшегося).
-
Оператор try-with-resources с хотя бы одним блоком catch и/или блоком finally называется расширенным оператором try-with-resources.
Значение расширенного оператора try-with-resources:
try ResourceSpecification Block [Catches] [Finally]
определяется следующим преобразованием в базовый оператор try-with-resources, вложенный в оператор try-catch-finally или оператор try-catch или оператор try-finally-catch:
try {
try ResourceSpecification
Block
}
[Catches]
[Finally]
Эффект преобразования заключается в том, что спецификация ресурсов помещается "внутри" оператора try. Это позволяет блоку catch расширенного оператора try-with-resources перехватывать исключения из-за автоматической инициализации или закрытия любого ресурса.
Кроме того, все ресурсы будут закрыты (или будут попытки их закрыть) к моменту выполнения блока try, в соответствии с назначением ключевого слова try.
Если оператор не может быть выполнен, потому что он недостижим, это ошибка времени компиляции.
Этот раздел посвящен точному объяснению слова "достижимый". Идея заключается в том, что должен существовать некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, к самому оператору. Анализ учитывает структуру операторов. За исключением специального обращения с операторами while, do и for, условное выражение которых имеет констанное значение true, значения выражений не учитываются в анализе потока.
Например, компилятор Java примет код:
{
int n = 5;
while (n > 7) k = 2;
}
даже если значение n известно во время компиляции и, в принципе, можно узнать во время компиляции, что присвоение переменной k никогда не будет выполнено.
Правила в этом разделе определяют два технических термина:
-
является ли оператор достижимым
-
может ли оператор нормально завершиться
Данные определения позволяют оператору нормально завершиться только в том случае, если он достижим.
Для сокращения описания правил используется обычное сокращение "iff" - "если и только если".
Достижимый break оператор выходит из оператора, если внутри целевого оператора break либо нет операторов try, чьи блоки try содержат оператор break, либо есть операторы try, чьи блоки try содержат оператор break, и все finally фрагменты этих операторов try могут завершиться нормально.
Это определение основано на логике "попыток передачи управления" в §14.15.
Оператор continue продолжает оператор do, если внутри оператора do либо нет операторов try, чьи блоки try содержат оператор continue, либо есть операторы try, чьи блоки try содержат оператор continue, и все finally фрагменты этих операторов try могут завершиться нормально.
Правила следующие:
-
Блок, который является телом конструктора, метода, инициализатора экземпляра или статического инициализатора, достижим.
-
Пустой блок, который не является блоком switch, может завершиться нормально, если он достижим.
Ненулевой блок, который не является блоком switch, может завершиться нормально, если последнее утверждение в нем может завершиться нормально.
Первое утверждение в ненулевом блоке, который не является блоком switch, достижимо, если блок достижим.
Каждое другое утверждение
Sв ненулевом блоке, который не является блоком switch, достижимо, если утверждение, предшествующееS, может завершиться нормально. -
Объявление локального класса может завершиться нормально, если оно достижимо.
-
Объявление локальной переменной может завершиться нормально, если оно достижимо.
-
Пустое утверждение может завершиться нормально, если оно достижимо.
-
Помеченное утверждение может завершиться нормально, если верно хотя бы одно из следующих утверждений:
-
Содержащееся утверждение может завершиться нормально.
-
Существует достижимое утверждение
break, которое выходит из помеченного утверждения.
Содержащееся утверждение достижимо, если помеченное утверждение достижимо.
-
-
Выражение утверждения может завершиться нормально, если оно достижимо.
-
Утверждение
if-thenможет завершиться нормально, если оно достижимо.then-утверждение достижимо, если утверждениеif-thenдостижимо.Утверждение
if-then-elseможет завершиться нормально, если утверждениеthenможет завершиться нормально или утверждениеelseможет завершиться нормально.then-утверждение достижимо, если утверждениеif-then-elseдостижимо.else-утверждение достижимо, если утверждениеif-then-elseдостижимо.Эта обработка утверждения
if, имеет ли оно частьelseили нет, довольно необычна. Обоснование приведено в конце этого раздела. -
Утверждение
assertможет завершиться нормально, если оно достижимо. -
Утверждение
switchможет завершиться нормально, если верно хотя бы одно из следующих утверждений:-
Блок switch пуст или содержит только метки switch.
-
Последнее утверждение в блоке switch может завершиться нормально.
-
Существует по крайней мере одна метка switch после последней группы утверждений блока switch.
-
Блок switch не содержит метку
default. -
Существует достижимое утверждение
break, которое выходит из утвержденияswitch.
-
-
Блок switch достижим, если достижимо его утверждение
switch. -
Утверждение в блоке switch достижимо, если его утверждение
switchдостижимо и верно хотя бы одно из следующих утверждений:-
Оно имеет метку
caseилиdefault. -
Перед ним в блоке switch находится утверждение, и это предшествующее утверждение может завершиться нормально.
-
-
Утверждение
whileможет завершиться нормально, если верно хотя бы одно из следующих утверждений:-
Утверждение
whileдостижимо, и выражение условия не является константным выражением (§15.28) со значениемtrue. -
Существует достижимое утверждение
break, которое выходит из утвержденияwhile.
Содержащееся утверждение достижимо, если утверждение
whileдостижимо, а выражение условия не является константным выражением, значение которого равноfalse. -
-
Утверждение
doможет завершиться нормально, если верно хотя бы одно из следующих утверждений:-
Содержащееся утверждение может завершиться нормально, и выражение условия не является константным выражением (§15.28) со значением
true. -
Утверждение
doсодержит достижимое утверждениеcontinueбез метки, и утверждениеdoявляется самым внутренним утверждениемwhile,doилиfor, которое содержит это утверждениеcontinue, и утверждениеcontinueпродолжает это утверждениеdo, а выражение условия не является константным выражением со значениемtrue. -
Утверждение
doсодержит достижимое утверждениеcontinueс меткойL, и утверждениеdoимеет меткуL, и утверждениеcontinueпродолжает это утверждениеdo, а выражение условия не является константным выражением со значениемtrue. -
Существует достижимое утверждение
break, которое выходит из утвержденияdo.
Содержащееся утверждение достижимо, если утверждение
doдостижимо. -
-
Основное утверждение
forможет завершиться нормально, если верно хотя бы одно из следующих утверждений:-
Утверждение
forдостижимо, есть выражение условия, и выражение условия не является константным выражением (§15.28) со значениемtrue. -
Существует достижимое утверждение
break, которое выходит из утвержденияfor.
Содержащееся утверждение достижимо, если утверждение
forдостижимо, а выражение условия не является константным выражением, значение которого равноfalse. -
-
Улучшенное утверждение
forможет завершиться нормально, если оно достижимо. -
Утверждение
break,continue,returnилиthrowне может завершиться нормально. -
Утверждение
synchronizedможет завершиться нормально, если содержащееся утверждение может завершиться нормально.Содержащееся утверждение достижимо, если утверждение
synchronizedдостижимо. -
Утверждение
tryможет завершиться нормально, если верно следующее:-
Блок
tryможет завершиться нормально или любой блокcatchможет завершиться нормально. -
Если утверждение
tryимеет блокfinally, то блокfinallyможет завершиться нормально.
-
-
Блок
tryдостижим, если достижимо утверждениеtry. -
Блок
catchCдостижим, если верно следующее:-
Либо тип параметра
Cявляется типом непроверенного исключения илиExceptionили суперклассомException, либо некоторое выражение или утверждениеthrowв блокеtryдостижимо и может вызвать проверенное исключение, тип которого присваивается типу параметраC. (Выражение достижимо, если самое внутреннее утверждение, содержащее его, достижимо.)См. §15.6 для нормального и внезапного завершения выражений.
-
В утверждении
tryнет более раннего блокаcatchA, такого, что тип параметраCсовпадает с типом параметраAили является подклассом.
-
-
Блок блока
catchдостижим, если достижим блокcatch. -
Если присутствует блок
finally, он достижим, если достижимо утверждениеtry.
Один может ожидать, что утверждение if будет обработано следующим образом:
-
Оператор
if-thenможет завершиться нормально, только если выполняется хотя бы одно из следующих условий:-
Оператор
if-thenдостижим, а выражение условия не является константным выражением, значение которого равноtrue. -
Оператор
thenможет завершиться нормально.
Оператор
thenдостижим, если операторif-thenдостижим, а выражение условия не является константным выражением, значение которого равноfalse. -
-
Оператор
if-then-elseможет завершиться нормально, только если операторthenможет завершиться нормально или операторelseможет завершиться нормально.Оператор
thenдостижим, если операторif-then-elseдостижим, а выражение условия не является константным выражением, значение которого равноfalse.Оператор
elseдостижим, если операторif-then-elseдостижим, а выражение условия не является константным выражением, значение которого равноtrue.
Этот подход был бы согласован с обработкой других управляющих структур. Однако, чтобы позволить оператору if удобно использоваться для целей «условной компиляции», фактические правила отличаются.
Например, следующий оператор приводит к ошибке времени компиляции:
while (false) { x=3; }
потому что оператор x=3; недостижим; но аналогичный случай:
if (false) { x=3; }
не приводит к ошибке времени компиляции. Оптимизирующая компилятор может понять, что оператор x=3; никогда не будет выполнен, и может выбрать исключение кода этого оператора из сгенерированного файла class, но оператор x=3; не считается «недостижимым» в техническом смысле, указанном здесь.
Обоснование этого различия в обработке заключается в возможности программистов определять «флаговые переменные», такие как:
static final boolean DEBUG = false;
а затем писать код, такой как:
if (DEBUG) { x=3; }
Идея состоит в том, что должно быть возможным изменить значение DEBUG с false на true или с true на false, а затем корректно скомпилировать код без внесения других изменений в текст программы.
Эта возможность «условной компиляции» не имеет отношения к бинарной совместимости (§13 (Бинарная совместимость)). Если набор классов, использующих такую «флаговую» переменную, скомпилирован, и условный код опущен, то позже недостаточно просто распространить новую версию класса или интерфейса, содержащего определение флага. Классы, использующие флаг, не увидят его нового значения, поэтому их поведение может быть неожиданным, но никаких LinkageError не произойдёт. Изменение значения флага поэтому бинарно совместимо с существующими двоичными файлами, но не поведенчески совместимо.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.