Spec-Zone.ru › Java Language Specification 8

Глава 14. Блоки и операторы

Оглавление

14.1. Нормальное и преждевременное завершение операторов
14.2. Блоки
14.3. Объявления локальных классов
14.4. Операторы объявления локальных переменных
14.4.1. Локальные деклараторы переменных и типы
14.4.2. Выполнение операторов объявления локальных переменных
14.5. Операторы
14.6. Пустой оператор
14.7. Операторы с метками
14.8. Операторы выражений
14.9. Оператор if
14.9.1. Оператор if-then
14.9.2. Оператор if-then-else
14.10. Оператор assert
14.11. Оператор switch
14.12. Оператор while
14.12.1. Преждевременное завершение оператора while
14.13. Оператор do
14.13.1. Преждевременное завершение оператора do
14.14. Оператор for
14.14.1. Базовый оператор for
14.14.1.1. Инициализация оператора for
14.14.1.2. Итерация оператора for
14.14.1.3. Преждевременное завершение оператора for
14.14.2. Расширенный оператор for
14.15. Оператор break
14.16. Оператор continue
14.17. Оператор return
14.18. Оператор throw
14.19. Оператор synchronized
14.20. Оператор try
14.20.1. Выполнение try-catch
14.20.2. Выполнение try-finally и try-catch-finally
14.20.3. try с ресурсами
14.20.3.1. Базовый try с ресурсами
14.20.3.2. Расширенный 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) этой главы рассматривается требование, чтобы каждый оператор был достижим в определённом техническом смысле.

14.1. Нормальное и преждевременное завершение операторов

Каждый оператор имеет нормальный режим выполнения, в котором выполняются определённые вычислительные шаги. В следующих разделах описан нормальный режим выполнения для каждого типа оператора.

Если все шаги выполняются как описано, без указания преждевременного завершения, то оператор считается завершенным нормально. Однако определённые события могут помешать оператору завершиться нормально:

  • Операторы 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).

Если оператор вычисляет выражение, преждевременное завершение выражения всегда вызывает непосредственное преждевременное завершение оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.

За исключением случаев, указанных в этой главе, преждевременное завершение под-оператора вызывает непосредственное преждевременное завершение самого оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.

Если не указано иное, оператор завершается нормально, если все выражения, которые он вычисляет, и все под-операторы, которые он выполняет, завершаются нормально.

END_OF_DOCUMENT_MARKER

14.2. Блоки

Блок — это последовательность операторов, объявлений локальных классов и объявлений локальных переменных, заключённых в фигурные скобки.

Блок:
{ [BlockStatements] }
BlockStatements:
BlockStatement {BlockStatement}
BlockStatement:
Оператор объявления локальной переменной
Объявление класса
Оператор

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

14.3. Локальные объявления классов

Локальный класс — это вложенный класс (§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.


14.4. Объявление локальных переменных

Оператор объявления локальной переменной объявляет одно или несколько имён локальных переменных.

LocalVariableDeclarationStatement:
LocalVariableDeclaration ;
LocalVariableDeclaration:
{МодификаторПеременной} Тип СписокОбъявленийПеременных

См. §8.3 для Тип. Следующие правила из §4.3, §8.4.1 и §8.3 приведены здесь для удобства:

МодификаторПеременной:
(один из)
Аннотация final
СписокОбъявленийПеременных:
ОбъявлениеПеременной {, ОбъявлениеПеременной}
ОбъявлениеПеременной:
ИдентификаторПеременной [= ИнициализаторПеременной]
ИдентификаторПеременной:
Идентификатор [Размеры]
Размеры:
{Аннотация} [ ] {{Аннотация} [ ]}
ИнициализаторПеременной:
Выражение
ИнициализаторМассива

Каждый оператор объявления локальной переменной непосредственно содержится в блоке. Операторы объявления локальных переменных могут свободно перемежаться с другими операторами в блоке.

Помимо операторов объявления локальных переменных, объявление локальной переменной может появиться в заголовке оператора §14.14 или оператора try-с-ресурсами (§14.20.3). В этих случаях он выполняется так же, как если бы он был частью оператора объявления локальной переменной.

Правила для модификаторов аннотаций при объявлении локальной переменной описаны в §9.7.4 и §9.7.5.

Если final появляется более одного раза как модификатор объявления локальной переменной, это ошибка на этапе компиляции.

14.4.1. Объявления локальных переменных и типы

Каждое объявление в объявлении локальной переменной объявляет одну локальную переменную, имя которой является Идентификатор, который появляется в объявлении.

Если ключевое слово final появляется в начале объявления, объявляемая переменная является переменной final (§4.12.4).

Объявленный тип локальной переменной обозначается Тип, если в Тип и ИдентификаторПеременной не содержатся парные скобки, в противном случае он указан в §10.2.

Локальная переменная типа float всегда содержит значение, являющееся элементом множества значений с плавающей точкой (§4.2.3); аналогично, локальная переменная типа double всегда содержит значение, являющееся элементом множества значений с двойной точностью. Не разрешается, чтобы локальная переменная типа float содержала элемент множества значений с плавающей точкой с расширенным порядком, который не является также элементом множества значений с плавающей точкой, а также для локальной переменной типа double содержать элемент множества значений с двойной точностью с расширенным порядком, который не является также элементом множества значений с двойной точностью.

Область действия и перекрытие объявления локальной переменной указаны в §6.3 и §6.4.

14.4.2. Выполнение объявлений локальных переменных

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

Если у декларатора нет инициализатора, то каждое обращение к переменной должно предшествовать выполнению присвоения переменной или возникает ошибка на этапе компиляции по правилам §16 (Определённое присваивание).

Каждый инициализатор (кроме первого) вычисляется только если вычисление предыдущего инициализатора завершается успешно.

Выполнение объявления локальной переменной завершается успешно только если вычисление последнего инициализатора завершается успешно.

Если объявление локальной переменной не содержит инициализаторов, то его выполнение всегда завершается успешно.

14.5. Операторы

В языке программирования 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, к которому оно потенциально может относиться. Это правило описывается следующей грамматикой:

Оператор:
ОператорБезЗавершающегоПодвыражения
МеткаОператора
ОператорЕсли
ОператорЕслиИначе
ОператорПока
ОператорДля
ОператорБезКороткогоIf:
ОператорБезЗавершающегоПодвыражения
МеткаОператораБезКороткогоIf
ОператорЕслиИначеБезКороткогоIf
ОператорПокаБезКороткогоIf
ОператорДляБезКороткогоIf
ОператорБезЗавершающегоПодвыражения:
Блок
ПустойОператор
ОператорВыражения
ОператорУтверждения
ОператорПереключателя
ОператорПовторять
ОператорПрервать
ОператорПродолжить
ОператорВозврат
ОператорСинхронизации
ОператорВыбросить
ОператорПопытка

Следующие производства из §14.9 показаны здесь для удобства:

ОператорЕсли:
if ( Выражение ) Оператор
ОператорЕслиИначе:
if ( Выражение ) ОператорБезКороткогоIf else Оператор
ОператорЕслиИначеБезКороткогоIf:
if ( Выражение ) ОператорБезКороткогоIf else ОператорБезКороткогоIf

Таким образом, операторы грамматически разделены на две категории: те, которые могут заканчиваться оператором if, у которого нет предложения else (оператором "короткий if"), и те, которые определенно не могут.

Только операторы, которые определенно не заканчиваются оператором "короткий if", могут выступать непосредственным подвыражением перед ключевым словом else в операторе if, который имеет предложение else.

Это простое правило предотвращает проблему "висящего else". Поведение оператора с ограничением "без короткого if" идентично поведению такого же оператора без ограничения "без короткого if"; различие делается исключительно для решения синтаксической проблемы.

14.6. Пустой оператор

Пустой оператор ничего не делает.

ПустойОператор:
;

Выполнение пустого оператора всегда завершается нормально.

14.7. Операторы с метками

Операторы могут иметь префиксы меток.

МеткаОператора:
Идентификатор : Оператор
МеткаОператораБезКороткого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 в области действия помеченного оператора.


14.8. Операторы выражений

Некоторые виды выражений могут использоваться как операторы, если за ними следует точка с запятой.

ExpressionStatement:
StatementExpression ;
StatementExpression:
Присваивание
Префиксный инкремент
Префиксный декремент
Постфиксный инкремент
Постфиксный декремент
Вызов метода
Выражение создания экземпляра класса

Оператор выражения выполняется путём вычисления выражения; если у выражения есть значение, это значение отбрасывается.

Выполнение оператора выражения завершается нормально тогда и только тогда, когда вычисление выражения завершается нормально.

В отличие от 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) вместо этого.

14.9. Оператор if

Оператор if позволяет условно выполнить оператор или осуществить условный выбор между двумя операторами, выполнив один из них, но не оба.

IfThenStatement:
if ( Выражение ) Оператор
IfThenElseStatement:
if ( Выражение ) ОператорWithoutShortIf else Оператор
IfThenElseStatementNoShortIf:
if ( Выражение ) ОператорWithoutShortIf else ОператорWithoutShortIf

Выражение должно иметь тип boolean или Boolean, иначе произойдёт ошибка компиляции.

14.9.1. Оператор if-then

Оператор if-then выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он подвергается приведению к типу без обёртки (§5.1.8).

Если вычисление выражения Выражение или последующее приведение к типу без обёртки (если таковое имеется) завершается с ошибкой, то оператор if-then завершается с той же ошибкой.

В противном случае выполнение продолжается путём принятия решения на основе полученного значения:

  • Если значение равно true, то выполняется вложенный оператор Оператор; оператор if-then завершается нормально тогда и только тогда, когда выполнение оператора Оператор завершается нормально.

  • Если значение равно false, то никаких дальнейших действий не предпринимается, и оператор if-then завершается нормально.

14.9.2. Оператор if-then-else

Оператор if-then-else выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он подвергается приведению к типу без обёртки (§5.1.8).

Если вычисление выражения Выражение или последующее приведение к типу без обёртки (если таковое имеется) завершается с ошибкой, то оператор if-then-else завершается с той же ошибкой.

В противном случае выполнение продолжается путём принятия решения на основе полученного значения:

  • Если значение равно true, то выполняется первый вложенный оператор Оператор (тот, что перед ключевым словом else); оператор if-then-else завершается нормально тогда и только тогда, когда выполнение этого оператора завершается нормально.

  • Если значение равно false, то выполняется второй вложенный оператор Оператор (тот, что после ключевого слова else); оператор if-then-else завершается нормально тогда и только тогда, когда выполнение этого оператора завершается нормально.

14.10. Утверждение assert

Утверждение (assertion) — это assert утверждение, содержащее булево выражение. Утверждение может быть активировано или деактивировано. Если утверждение активировано, выполнение утверждения приводит к вычислению булева выражения, и при вычислении к значению false, выдаётся ошибка. Если утверждение деактивировано, выполнение утверждения не имеет никакого эффекта.

AssertStatement:
assert Выражение ;
assert Выражение : Выражение ;

Для упрощения представления первое Выражение в обоих формах утверждения 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.

14.11. Оператор switch

Оператор switch передает управление одному из нескольких операторов в зависимости от значения выражения.

SwitchStatement:
switch ( Выражение ) SwitchBlock
SwitchBlock:
{ {SwitchBlockStatementGroup} {МеткаSwitch} }
SwitchBlockStatementGroup:
SwitchLabels Блочные операторы
SwitchLabels:
МеткаSwitch {МеткаSwitch}
SwitchLabel:
case Константное выражение :
case Имя константы перечисления :
default :
EnumConstantName:
Идентификатор

Тип Выражения должен быть 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

14.12. Оператор while

Оператор while выполняет Выражение и Оператор многократно до тех пор, пока значение Выражения не станет false.

WhileStatement:
while ( Выражение ) Оператор
WhileStatementNoShortIf:
while ( Выражение ) ОператорNoShortIf

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

Оператор цикла while выполняется путём сначала вычисления Выражения. Если результат имеет тип Boolean, он преобразуется к базовому типу (§5.1.8).

Если вычисление Выражения или последующее преобразование (если таковое имеется) прерывается по какой-либо причине, то оператор цикла while прерывается по той же причине.

В противном случае выполнение продолжается с выбором, основанным на результирующем значении:

  • Если значение равно true, то выполняется вложенный Оператор. Затем следует выбор:

    • Если выполнение Оператора завершилось нормально, то оператор цикла while выполняется снова, начиная с повторного вычисления Выражения.

    • Если выполнение Оператора завершилось прерыванием, см. §14.12.1.

  • Если (возможно, преобразованное) значение Выражения равно false, дальнейших действий не происходит, и оператор while завершается нормально.

    Если (возможно, преобразованное) значение Выражения равно false при первом его вычислении, то Оператор не выполняется.

14.12.1. Прерывание оператора while

Прерывание выполнения вложенного Оператора обрабатывается следующим образом:

  • Если выполнение Оператора прервалось из-за break без метки, дальнейших действий не происходит, и оператор цикла while завершается нормально.

  • Если выполнение Оператора прервалось из-за continue без метки, то оператор цикла while выполняется снова.

  • Если выполнение Оператора прервалось из-за continue с меткой L, то следует выбор:

    • Если оператор цикла while имеет метку L, то оператор цикла while выполняется снова.

    • Если оператор цикла while не имеет метки L, оператор цикла while прерывается из-за continue с меткой L.

  • Если выполнение Оператора прервалось по любой другой причине, то оператор цикла while прерывается по той же причине.

    Случай прерывания из-за break с меткой обрабатывается общим правилом для помеченных операторов (§14.7).

14.13. Оператор do

Оператор do выполняет Оператор и Выражение многократно до тех пор, пока значение Выражения не станет false.

DoStatement:
do Оператор while ( Выражение ) ;

Тип Выражения должен быть boolean или Boolean; в противном случае произойдёт ошибка компиляции.

Оператор do выполняется путём выполнения сначала Оператора. Затем происходит выбор:

  • Если выполнение Оператора завершается нормально, то вычисляется Выражение. Если результат имеет тип Boolean, он преобразуется к своему упакованному виду (§5.1.8).

    Если вычисление Выражения или последующее преобразование к упакованному виду (если таковое имеется) завершается аварийно по какой-либо причине, то оператор do также завершается аварийно по той же причине.

    В противном случае, выбор зависит от полученного значения:

    • Если значение равно true, то оператор do выполняется повторно.

    • Если значение равно false, то дальнейшие действия не выполняются, и оператор do завершается нормально.

  • Если выполнение Оператора завершается аварийно, см. §14.13.1.

Выполнение оператора 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 является подходящей управляющей структурой.


14.14. Оператор for

Оператор for имеет две формы:

  • Базовый оператор for.

  • Расширенный оператор for

ForStatement:
Базовый цикл for
Расширенный цикл for
ForStatementNoShortIf:
Базовый цикл for без оператора short if
Расширенный цикл for без оператора short if

14.14.1. Базовый оператор цикла for

Базовый оператор цикла for выполняет некоторый код инициализации, затем выполняет выражение, оператор и некоторый код обновления многократно, пока значение выражения не станет false.

BasicForStatement:
for ( [Инициализация] ; [Выражение] ; [Обновление] ) Оператор
BasicForStatementNoShortIf:
for ( [Инициализация] ; [Выражение] ; [Обновление] ) Оператор без short if
ForInit:
Список операторских выражений
Объявление локальной переменной
ForUpdate:
Список операторских выражений
StatementExpressionList:
Операторское выражение {, Операторское выражение}

У выражения должен быть тип boolean или Boolean, в противном случае произойдёт ошибка компиляции.

Область видимости и перекрытие локальной переменной, объявленной в части ForInit оператора цикла, указаны в §6.3 и §6.4.

14.14.1.1. Инициализация оператора цикла for

Оператор цикла for выполняется путём выполнения кода ForInit:

  • Если код ForInit представляет собой список операторных выражений (§14.8), выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются.

    Если вычисление любого выражения завершается прерыванием по какой-либо причине, оператор цикла for завершается прерыванием по той же причине; все операторные выражения ForInit справа от выражения, завершившегося прерыванием, не вычисляются.

  • Если код ForInit представляет собой объявление локальной переменной (§14.4), он выполняется так, как если бы это был оператор объявления локальной переменной, появившийся в блоке.

    Если выполнение объявления локальной переменной завершается прерыванием по какой-либо причине, оператор цикла for завершается прерыванием по той же причине.

  • Если часть ForInit отсутствует, никаких действий не производится.

14.14.1.2. Итерация оператора цикла for

Далее выполняется шаг итерации оператора for:

  • Если Выражение присутствует, оно вычисляется. Если результат имеет тип Boolean, он преобразуется в базовое представление (§5.1.8).

    Если вычисление Выражения или последующего преобразования в базовое представление (если таковое есть) завершается прерыванием, оператор цикла for завершается прерыванием по той же причине.

    В противном случае, дальнейшее действие зависит от наличия или отсутствия Выражения и полученного значения, если Выражение присутствует; см. следующий пункт.

  • Если Выражение отсутствует или присутствует, но его значение (включая возможное преобразование в базовое представление) равно true, то содержимый Оператор выполняется. Далее выбирается действие:

    • Если выполнение Оператора завершается нормально, то выполняются следующие два шага последовательно:

      1. Сначала, если часть ForUpdate присутствует, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются. Если вычисление любого выражения завершается прерыванием по какой-либо причине, оператор цикла for завершается прерыванием по той же причине; все операторные выражения ForUpdate справа от выражения, завершившегося прерыванием, не вычисляются.

        Если часть ForUpdate отсутствует, никаких действий не производится.

      2. Во-вторых, выполняется ещё один шаг итерации.

    • Если выполнение Оператора завершается прерыванием, см. §14.14.1.3.

  • Если Выражение присутствует и значение результата его вычисления (включая возможное преобразование в базовое представление) равно false, то никаких дальнейших действий не выполняется и оператор цикла for завершается нормально.

    Если (возможно, преобразованное) значение Выражения равно false в первый раз, то Оператор не выполняется.

Если Выражение отсутствует, то единственный способ, которым оператор цикла for может завершиться нормально, — это использование оператора break.

14.14.1.3. Прерывание выполнения цикла for

Прерывание выполнения содержащегося выражения обрабатывается следующим образом:

  • Если выполнение выражения прерывается из-за break без метки, никаких дальнейших действий не выполняется, и оператор for завершается нормально.

  • Если выполнение выражения прерывается из-за continue без метки, последовательно выполняются следующие два шага:

    1. Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.

      Если часть ForUpdate отсутствует, никаких действий не выполняется.

    2. Во-вторых, выполняется еще один шаг итерации for.

  • Если выполнение выражения прерывается из-за continue с меткой L, есть выбор:

    • Если оператор for имеет метку L, последовательно выполняются следующие два шага:

      1. Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.

        Если часть ForUpdate отсутствует, никаких действий не выполняется.

      2. Во-вторых, выполняется еще один шаг итерации for.

    • Если оператор for не имеет метки L, оператор for завершается прерыванием из-за continue с меткой L.

  • Если выполнение выражения прерывается по любой другой причине, оператор for завершается прерыванием по той же причине.

    Обратите внимание, что случай прерывания выполнения из-за break с меткой обрабатывается общим правилом для меченых операторов (§14.7).

14.14.2. Улучшенный оператор for

Улучшенный оператор for имеет вид:

EnhancedForStatement:
for ( {VariableModifier} UnannType VariableDeclaratorId : Expression ) Statement
EnhancedForStatementNoShortIf:
for ( {VariableModifier} UnannType VariableDeclaratorId : Expression ) StatementNoShortIf

См. §8.3 для UnannType. Следующие производства из §4.3, §8.4.1 и §8.3 приведены здесь для удобства:

VariableModifier:
(один из)
Annotation final
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Annotation} [ ] {{Annotation} [ ]}

Тип выражения должен быть 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);
}


14.15. Оператор break

Оператор break передает управление из окружающего оператора.

BreakStatement:
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.


14.16. Оператор continue

Оператор continue может встречаться только в операторе while, операторе do или операторе for; операторы этих трех типов называются итерационными операторами. Управление передается точке продолжения цикла итерационного оператора.

ContinueStatement:
continue [Идентификатор] ;

Оператор 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);
    }
}

Какой из них использовать, если это необходимо, в основном зависит от стиля программирования.


14.17. Оператор return

Оператор return возвращает управление вызывающей стороне метода (§8.4), §15.12) или конструктора (§8.8), §15.9).

ReturnStatement:
return [Выражение] ;

Оператор return содержится в самом внутреннем конструкторе, методе, инициализаторе или лямбда-выражении, тело которого включает в себя оператор return.

Если оператор return содержится в инициализаторе экземпляра или статическом инициализаторе (§8.6, §8.7), то это ошибка компиляции.

Оператор return без Выражения должен быть содержащимся в одном из следующих, в противном случае произойдёт ошибка компиляции:

  • Метод, объявленный с использованием ключевого слова void, не возвращающий значение (§8.4.5)

  • Конструктор (§8.8.7)

  • Лямбда-выражение (§15.27)

Оператор 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.

14.18. Оператор throw

Оператор throw вызывает исключение (§11 (Исключения)) для обработки. Результатом является немедленный переход управления (§11.3), который может завершить выполнение нескольких операторов и вызовов конструкторов, инициализаторов экземпляров, статических инициализаторов и инициализаторов полей, а также вызовов методов, пока не будет найден оператор try (§14.20), который перехватывает сгенерированное значение. Если такой оператор try не найден, то выполнение потока (§17 (Потоки и блокировки)), который выполнил оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.

ThrowStatement:
throw Выражение ;

Выражение Выражение в операторе 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).

14.19. Оператор synchronized

Оператор synchronized приобретает замок взаимного исключения (§17.1) от имени потока выполнения, выполняет блок, затем освобождает замок. Пока выполняющийся поток владеет замком, никакой другой поток не может приобрести замок.

SynchronizedStatement:
synchronized ( Выражение ) Блок

Тип Выражения должен быть ссылочным типом; в противном случае произойдёт ошибка компиляции.

Оператор 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!

Обратите внимание, что эта программа застопорится, если одному потоку не разрешено блокировать монитор более одного раза.


14.20. Оператор try

Оператор try выполняет блок кода. Если возникает исключение, и оператор try содержит одну или несколько обработок catch, способных его перехватить, то управление будет передано первой такой обработке catch. Если оператор try содержит блок finally, то другой блок кода будет выполнен независимо от того, завершился ли блок try нормально или неожиданно, а также независимо от того, была ли первой получена обработка catch.

TryStatement:
try Блок ОбработкиИсключений
try Блок [ОбработкиИсключений] БлокFinally
TryWithResourcesStatement
Catches:
ОбработкаИсключения {ОбработкаИсключения}
CatchClause:
catch ( ПараметрОбработкиИсключения ) Блок
CatchFormalParameter:
{МодификаторПеременной} ТипИсключения ИдентификаторПеременной
CatchType:
ТипКласса {| ТипКласса}
Finally:
finally Блок

См. §8.3 для ТипКласса. Следующие производные из §4.3, §8.3 и §8.4.1 приведены для удобства:

VariableModifier:
(один из)
Аннотация final
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Аннотация} [ ] {{Аннотация} [ ]}

Блок Блок сразу после ключевого слова 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).

14.20.1. Выполнение оператора try-catch

Оператор 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

14.20.2. Выполнение try-finally и try-catch-finally

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

Проблема с требованием вывода стека вызовов заключается в том, что исключение может быть создано в одной точке программы и брошено в другой. Сохранение стека вызовов в исключении, если оно не было брошено, чрезвычайно затратно. Поэтому этот стандарт не требует вывода стека вызовов при каждом исключении.


14.20.3. try-with-resources

Оператор try-with-resources параметризуется переменными (известными как ресурсы), которые инициализируются перед выполнением блока try и автоматически закрываются в обратном порядке инициализации после выполнения блока try. Обычно, при автоматическом закрытии ресурсов, необязательны разделы catch и оператор finally.

TryWithResourcesStatement:
try ResourceSpecification Block [Catches] [Finally]
ResourceSpecification:
( СписокРесурсов [;] )
ResourceList:
Ресурс {; Ресурс}
Ресурс:
{МодификаторПеременной} ТипБезАннотаций ИдентификаторПеременной = Выражение

См. §8.3 для ТипБезАннотаций. Ниже приведены следующие производные правила из §4.3, §8.3 и §8.4.1 для удобства:

МодификаторПеременной:
(один из)
Аннотация final
ИдентификаторПеременной:
Идентификатор [Измерения]
Измерения:
{Аннотация} [ ] {{Аннотация} [ ]}

Спецификация ресурсов объявляет одну или несколько локальных переменных с инициализирующими выражениями, которые действуют как ресурсы для оператора 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, и весь перевод завершен.

14.20.3.1. Базовый оператор try-with-resources

Оператор 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 — исключение от первого ресурса, не закрывшегося).

14.20.3.2. Расширенный оператор try-with-resources

Оператор 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.

14.21. Недостижимые операторы

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

Этот раздел посвящен точному объяснению слова "достижимый". Идея заключается в том, что должен существовать некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, к самому оператору. Анализ учитывает структуру операторов. За исключением специального обращения с операторами 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.

  • Блок catch C достижим, если верно следующее:

    • Либо тип параметра C является типом непроверенного исключения или Exception или суперклассом Exception, либо некоторое выражение или утверждение throw в блоке try достижимо и может вызвать проверенное исключение, тип которого присваивается типу параметра C. (Выражение достижимо, если самое внутреннее утверждение, содержащее его, достижимо.)

      См. §15.6 для нормального и внезапного завершения выражений.

    • В утверждении try нет более раннего блока catch A, такого, что тип параметра 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API