Spec-Zone.ru › Java Language Specification 17

Глава 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.11.1. Блоки switch
14.11.2. Блок switch оператора switch
14.11.3. Выполнение оператора 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. Оператор yield
14.22. Недостижимые операторы
14.30. Шаблоны
14.30.1. Виды шаблонов
14.30.2. Сопоставление с шаблоном

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

Некоторые операторы содержат другие операторы в качестве части своей структуры; такие другие операторы являются подчиненными операторами оператора. Мы говорим, что оператор S непосредственно содержит оператор U, если нет оператора T, отличного от S и U, такого, что S содержит T, а T содержит U. Точно так же некоторые операторы содержат выражения (§15 (Выражения)) в качестве части своей структуры.

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

Сначала объясняются блоки (§14.2), как потому, что они могут появляться в определенных местах, где не разрешены операторы, так и потому, что один тип оператора, оператор объявления локальной переменной (§14.4.2), должен быть непосредственно содержащимся в блоке. Объявления локальных классов и интерфейсов (§14.3) не являются операторами, но также должны быть непосредственно содержащимися в блоке.

Далее объясняется грамматический прием, который позволяет обойти проблему «висящего else» (§14.5).

Каждый оператор должен быть достижимым в определенном техническом смысле (§14.22).

Разделы 14.23-14.29 не используются, чтобы обеспечить возможность введения новых типов операторов в будущем.

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

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

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

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

  • Операторы break, yield, continue и return (§14.15, §14.21, §14.16, §14.17) вызывают передачу управления, что может помешать нормальному завершению выражений, операторов и блоков, содержащих их.

  • Оценка некоторых выражений может вызвать исключения из Java Virtual Machine (§15.6). Явный оператор throw (§14.18) также приводит к исключению. Исключение вызывает передачу управления, что может помешать нормальному завершению операторов.

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

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

  • break без метки

  • break с заданной меткой

  • continue без метки

  • continue с заданной меткой

  • return без значения

  • return с заданным значением

  • throw со значением, включая исключения, вызываемые Java Virtual Machine

  • yield с заданным значением

Термины «завершиться нормально» и «завершиться прерывисто» также применяются к оценке выражений (§15.6). Единственная причина, по которой выражение может завершиться прерывисто, — это возникновение исключения, из-за оператора throw с заданным значением (§14.18) или исключения или ошибки во время выполнения (§11 (Исключения), §15.6).

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

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

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

14.2. Блоки

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

Block:
{ [BlockStatements] }
BlockStatements:
BlockStatement {BlockStatement}
BlockStatement:
LocalClassOrInterfaceDeclaration
LocalVariableDeclarationStatement
Statement

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

14.3. Объявления локальных классов и интерфейсов

Локальный класс — это вложенный класс (§8 (Классы)), объявление которого непосредственно содержится в блоке (§14.2).

Локальный интерфейс — это вложенный интерфейс (§9 (Интерфейсы)), объявление которого непосредственно содержится в блоке.

LocalClassOrInterfaceDeclaration:
ClassDeclaration
NormalInterfaceDeclaration

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

ClassDeclaration:
NormalClassDeclaration
EnumDeclaration
RecordDeclaration
NormalClassDeclaration:
{ClassModifier} class TypeIdentifier [TypeParameters] [ClassExtends] [ClassImplements] [ClassPermits] ClassBody
EnumDeclaration:
{ClassModifier} enum TypeIdentifier [ClassImplements] EnumBody
NormalInterfaceDeclaration:
{InterfaceModifier} interface TypeIdentifier [TypeParameters] [InterfaceExtends] [InterfacePermits] InterfaceBody

Объявления локальных классов и интерфейсов могут быть произвольно перемешаны с операторами (включая операторы объявления локальных переменных) в содержащем блоке.

Если объявление локального класса или интерфейса имеет какие-либо из модификаторов доступа public, protected или private (§6.6), это является ошибкой компиляции.

Если объявление локального класса или интерфейса имеет модификатор static (§8.1.1.4), sealed или non-sealed (§8.1.1.2, §9.1.1.4), это является ошибкой компиляции.

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

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

Локальный класс может быть обычным классом (§8.1), классом перечисления (§8.9) или классом записи (§8.10). Каждый локальный обычный класс является внутренним классом (§8.1.3). Каждый локальный класс перечисления и каждый локальный класс записи неявно static (§8.1.1.4), и поэтому не является внутренним классом.

Локальный интерфейс может быть обычным интерфейсом (§9.1), но не интерфейсом аннотаций (§9.6). Каждый локальный интерфейс неявно static (§9.1.1.3).

Как и анонимный класс (§15.9.5), локальный класс или интерфейс не является членом никакого пакета, класса или интерфейса (§7.1, §8.5). В отличие от анонимного класса, локальный класс или интерфейс имеет простое имя (§6.2, §6.7).

Область действия и перекрытие объявления локального класса или интерфейса определены в §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. Объявления локальных переменных

Объявление локальной переменной объявляет и, при необходимости, инициализирует одну или несколько локальных переменных (§4.12.3).

LocalVariableDeclaration:
{МодификаторПеременной} ТипЛокальнойПеременной СписокДеклараторовПеременной
LocalVariableType:
ТипБезАннотаций
var

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

VariableModifier:
Аннотация
final
VariableDeclaratorList:
ДеклараторПеременной {, ДеклараторПеременной}
VariableDeclarator:
ИдентификаторПеременной [= ИнициализаторПеременной]
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Аннотация} [ ] {{Аннотация} [ ]}
VariableInitializer:
Выражение
ИнициализаторМассива

Объявление локальной переменной может появиться в следующих местах:

  • оператор объявления локальной переменной в блоке (§14.4.2)

  • в заголовке оператора базового цикла for (§14.14.1)

  • в заголовке оператора расширенного цикла for (§14.14.2)

  • в спецификации ресурсов оператора try-with-resources (§14.20.3)

  • в шаблоне (§14.30.1)

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

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

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

Ошибка компиляции, если ТипЛокальнойПеременной является var, и выполняются следующие условия:

  • Указано более одного ДекларатораПеременной.

  • ИдентификаторПеременной содержит одну или несколько пар скобок.

  • ДеклараторПеременной не имеет инициализатора.

  • Инициализатор ДекларатораПеременной является ИнициализаторомМассива.

  • Инициализатор ДекларатораПеременной содержит ссылку на переменную.

Пример 14.4-1. Локальные переменные, объявленные с var

Следующий код демонстрирует эти правила, ограничивающие использование var:


var a = 1;            // Legal
var b = 2, c = 3.0;   // Illegal: multiple declarators
var d[] = new int[4]; // Illegal: extra bracket pairs
var e;                // Illegal: no initializer
var f = { 6 };        // Illegal: array initializer
var g = (g = 7);      // Illegal: self reference in initializer

Эти ограничения помогают избежать путаницы с типом, представленным var.


14.4.1. Деклараторы и типы локальных переменных

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

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

Тип объявленной локальной переменной определяется следующим образом:

  • Если ТипЛокальнойПеременной — ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторПеременной нет пар скобок, то тип локальной переменной обозначается ТипБезАннотаций.

  • Если ТипЛокальнойПеременной — ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторПеременной есть пары скобок, то тип локальной переменной определяется по §10.2.

  • Если ТипЛокальнойПеременной — var, то пусть T — тип выражения инициализатора, когда оно рассматривается как самостоятельное выражение, а не в контексте присваивания (§15.2). Тип локальной переменной — это проекция T вверх относительно всех синтетических переменных типов, упоминаемых в T (§4.10.5).

    Ошибка компиляции, если T — тип null.

    Так как инициализатор рассматривается как самостоятельное выражение, ошибка возникает, если это лямбда-выражение (§15.27) или выражение ссылки на метод (§15.13).

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

Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения, ограничены, как указано в §6.5.6.1.

Пример 14.4.1-1. Тип локальных переменных, объявленных с var

Следующий код демонстрирует типизацию переменных, объявленных с var:


var a = 1;                // a has type 'int'
var b = java.util.List.of(1, 2);  // b has type 'List<Integer>'
var c = "x".getClass();   // c has type 'Class<? extends String>' 
                          // (see JLS 15.12.2.6)
var d = new Object() {};  // d has the type of the anonymous class
var e = (CharSequence & Comparable<String>) "x";
                          // e has type CharSequence & Comparable<String>
var f = () -> "hello";    // Illegal: lambda not in an assignment context
var g = null;             // Illegal: null type

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


Проекция вверх применяется к типу инициализатора при определении типа переменной. Если тип инициализатора содержит переменные захвата, эта проекция отображает тип инициализатора на супертип, не содержащий переменных захвата.

Хотя было бы возможно разрешить указание переменных захвата в типе переменной, удаляя их, мы обеспечиваем привлекательную инвариантность: область видимости переменной захвата никогда не превышает оператор, содержащий выражение, тип которого захвачен. Неформально, переменные захвата не могут «вытекать» в последующие операторы.

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

Заявление о объявлении локальной переменной состоит из объявления локальной переменной.

LocalVariableDeclarationStatement:
LocalVariableDeclaration ;

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

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

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

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

Statement:
StatementWithoutTrailingSubstatement
LabeledStatement
IfThenStatement
IfThenElseStatement
WhileStatement
ForStatement
StatementNoShortIf:
StatementWithoutTrailingSubstatement
LabeledStatementNoShortIf
IfThenElseStatementNoShortIf
WhileStatementNoShortIf
ForStatementNoShortIf
StatementWithoutTrailingSubstatement:
Block
EmptyStatement
ExpressionStatement
AssertStatement
SwitchStatement
DoStatement
BreakStatement
ContinueStatement
ReturnStatement
SynchronizedStatement
ThrowStatement
TryStatement
YieldStatement

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

IfThenStatement:
if ( Expression ) Statement
IfThenElseStatement:
if ( Expression ) StatementNoShortIf else Statement
IfThenElseStatementNoShortIf:
if ( Expression ) StatementNoShortIf else StatementNoShortIf

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

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

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

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

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

EmptyStatement:
;

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

14.7. Меткированные операторы

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

МеткарованныйОператор:
Идентификатор : Оператор
МеткарованныйОператорБезКороткогоIf:
Идентификатор : StatementNoShortIf

Идентификатор объявляется меткой немедленно содержащегося оператора.

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

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

ОператорВыражения:
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 ( Выражение ) ОператорБезКороткогоIf else Оператор
IfThenElseStatementNoShortIf:
if ( Выражение ) ОператорБезКороткогоIf else ОператорБезКороткогоIf

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

Утверждение — это оператор 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 ( Выражение ) Блок_switch

Выражение Выражение называется выражением-селектором. Тип выражения-селектора должен быть char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления (§8.9), в противном случае произойдет ошибка компиляции.

14.11.1. Блоки switch

Тело как оператора switch, так и выражения switch (§15.28) называется блоком switch. В этом подразделе представлены общие правила, которые применяются ко всем блокам switch, независимо от того, появляются ли они в операторах switch или выражениях switch. Другие подразделы содержат дополнительные правила, которые применяются к блокам switch в операторах switch (§14.11.2) или в выражениях switch (§15.28.1).

SwitchBlock:
{ Правило_switch {Правило_switch} }
{ {Группа_операторов_switch} {Метка_switch :} }
SwitchRule:
Метка_switch -> Выражение ;
Метка_switch -> Блок
Метка_switch -> Оператор_броска_исключения
SwitchBlockStatementGroup:
Метка_switch : {Метка_switch :} Операторы_блока
SwitchLabel:
case Константа_case {, Константа_case}
default
CaseConstant:
Условное_выражение

Блок switch может состоять из:

  • Правила switch, которые используют -> для введения либо выражения правила switch, либо блока правила switch, либо оператора правила switch throw; или

  • Группы операторов с метками switch, которые используют : для введения операторов блоков с метками switch.

Каждое правило switch и каждая группа операторов с метками switch начинаются с метки switch, которая является либо меткой case, либо меткой default. Допускается несколько меток switch для одной группы операторов.

Метка case имеет одну или несколько case констант. Каждая case константа должна быть либо константным выражением (§15.29), либо именем константы перечисления (§8.9.1), в противном случае произойдет ошибка компиляции.

Метки switch и их case константы называются связанными с блоком switch. Ни две из case констант, связанных с блоком switch, не могут иметь одинаковое значение, в противном случае произойдет ошибка компиляции.

Блок switch оператора switch или выражения switch совместим с типом выражения-селектора T, если оба следующих утверждения верны:

  • Если T не является типом перечисления, то каждая case константа, связанная с блоком switch, совместима по присваиванию с T (§5.2).

  • Если T является типом перечисления, то каждая case константа, связанная с блоком switch, является константой перечисления типа T.

Блок switch оператора switch или выражения switch должен быть совместим с типом выражения-селектора, в противном случае произойдет ошибка компиляции.

Как выполнение оператора switch (§14.11.3), так и вычисление выражения switch (§15.28.2) требуют определения, соответствует ли метка switch значению выражения-селектора. Чтобы определить, соответствует ли метка switch в блоке switch заданному значению, значение сравнивается с case константами, связанными с блоком switch. Затем:

  • Если одна из case констант равна значению, то мы говорим, что метка case, содержащая case константу, соответствует.

    Равенство определяется с помощью оператора == (§15.21), за исключением случая, когда значение является String, в этом случае равенство определяется методом equals класса String.

  • Если ни одна метка case не соответствует, но есть метка default, то мы говорим, что метка default соответствует.

Метка case может содержать несколько case констант. Метка соответствует значению выражения-селектора, если любая из ее констант соответствует значению выражения-селектора. Например, в следующем коде метка case соответствует, если переменная перечисления day является одной из показанных констант перечисления:

switch (day) {
    ...
    case SATURDAY, SUNDAY :
        System.out.println("It's the weekend!");
        break;
    ...
}

null не может быть использована в качестве case константы, потому что она не является константным выражением. Даже если бы case null было разрешено, это было бы нежелательно, так как код в этом case никогда не выполнялся бы. Это связано с тем, что при заданном выражении-селекторе ссылочного типа (то есть String или упакованного примитивного типа или типа перечисления) произойдет исключение, если выражение-селектор вычисляет null во время выполнения. По мнению разработчиков языка программирования Java, распространение исключения является лучшим результатом, чем отсутствие соответствия ни одной метки case или соответствие метке default.

Компилятор Java рекомендуется (но не обязательно) выдать предупреждение, если оператор switch с выражением-селектором типа перечисления не содержит метки default и не содержит меток case для одной или нескольких констант перечисления. Такой оператор 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);
}

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

14.11.2. Блок оператора switch оператора switch

В дополнение к общим правилам для блоков оператора switch (§14.11.1), существуют дополнительные правила для блоков оператора switch в операторах switch. А именно, для блока оператора switch оператора switch должны выполняться все следующие условия, в противном случае возникает ошибка компиляции:

  • Не более одной метки default ассоциирована с блоком switch.

  • Каждый выражение правила оператора switch в блоке оператора switch является выражением-оператором (§14.8).

Операторы switch отличаются от выражений switch тем, какие выражения могут находиться справа от стрелки (->) в блоке оператора switch, то есть, какие выражения могут использоваться как выражения правил оператора switch. В операторе switch в качестве выражения правила оператора switch может использоваться только выражение-оператор, а в выражении switch может использоваться любое выражение (§15.28.1).

14.11.3. Выполнение оператора switch

Оператор switch выполняется путём сначала вычисления выражения-селектора. Затем:

  • Если вычисление выражения-селектора завершается внезапно, то весь оператор switch завершается внезапно по той же причине.

  • В противном случае, если результат вычисления выражения-селектора равен null, то генерируется исключение NullPointerException и весь оператор switch завершается внезапно по этой причине.

  • В противном случае, если результат вычисления выражения-селектора имеет тип Character, Byte, Short или Integer, он преобразуется к примитивному типу (§5.1.8). Если это преобразование завершается внезапно, то весь оператор switch завершается внезапно по той же причине.

Если вычисление выражения-селектора завершается нормально и результат является не-null, а последующее преобразование к примитивному типу (если таковое имеется) завершается нормально, то выполнение оператора switch продолжается путём определения, соответствует ли метка оператора switch, связанная с блоком оператора switch, значению выражения-селектора (§14.11.1). Тогда:

  • Если ни одна метка оператора switch не совпадает, то весь оператор switch завершается нормально.

  • Если метка оператора switch совпадает, то выполняется одно из следующих действий:

    • Если это метка для выражения правила оператора switch, то выражение правила оператора switch обязательно является выражением-оператором (§14.11.2). Вычисляется выражение-оператор. Если вычисление завершается нормально, то оператор switch завершается нормально. Если результат вычисления имеет значение, оно игнорируется.

    • Если это метка для блока правила оператора switch, то выполняется блок. Если этот блок завершается нормально, то оператор switch завершается нормально.

    • Если это метка для оператора правила оператора switch throw, то выполняется оператор throw.

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

    • В противном случае, операторов, следующих за совпавшей меткой оператора switch в блоке оператора switch, нет, и оператор switch завершается нормально.

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

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

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

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

    Внезапное завершение из-за оператора yield обрабатывается по общему правилу для выражений switch (§15.28.2).

Пример 14.11.3-1. Пропуск в операторе switch

Когда выражение-селектор соответствует метке оператора switch для правила оператора switch, вычисляется выражение или оператор, введенный этой меткой, и ничего больше. В случае метки оператора switch для группы операторов выполняются все операторы в блоке оператора switch, следующие за этой меткой, включая те, которые появляются после последующих меток оператора switch. Эффект аналогичен C и C++, когда выполнение операторов может "проваливаться" через метки.

Например, программа:

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, чтобы указать, когда управление должно передаваться, или можно использовать правила оператора switch, как в программе:

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
        }
    }
    static void howManyAgain(int k) {
        switch (k) {
            case 1 -> System.out.println("one");
            case 2 -> System.out.println("two");
            case 3 -> System.out.println("many");
        }
    }
    public static void main(String[] args) {
        howMany(1);
        howMany(2);
        howMany(3);
        howManyAgain(1);
        howManyAgain(2);
        howManyAgain(3);	
    }
}

Эта программа выводит:

one
two
many
one
two
many

14.12. Оператор while

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

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

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

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

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

    Случай прерывания выполнения из-за оператора continue с меткой обрабатывается общим правилом для помеченных операторов (§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:
BasicForStatement
EnhancedForStatement
ForStatementNoShortIf:
BasicForStatementNoShortIf
EnhancedForStatementNoShortIf

14.14.1. Основной оператор for

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

BasicForStatement:
for ( [ForInit] ; [Expression] ; [ForUpdate] ) Statement
BasicForStatementNoShortIf:
for ( [ForInit] ; [Expression] ; [ForUpdate] ) StatementNoShortIf
ForInit:
StatementExpressionList
LocalVariableDeclaration
ForUpdate:
StatementExpressionList
StatementExpressionList:
StatementExpression {, StatementExpression}

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

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

Ссылки на локальную переменную, объявленную в части ForInit основного оператора for из вложенного класса или интерфейса, или лямбда-выражения, ограничены, как указано в §6.5.6.1.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Если значение (возможно, развёрнутое) выражения является false в первый раз, когда оно вычисляется, то оператор не выполняется.

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

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

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

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

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

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

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

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

  • Если выполнение оператора прервано из-за исключения с меткой L, то существует выбор:

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

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

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

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

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

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

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

14.14.2. Улучшенное оператор цикла

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

EnhancedForStatement:
for ( ОбъявлениеЛокальнойПеременной : Выражение ) Оператор
EnhancedForStatementNoShortIf:
for ( ОбъявлениеЛокальнойПеременной : Выражение ) ОператорБезКороткогоIf

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

LocalVariableDeclaration:
{МодификаторПеременной} ТипЛокальнойПеременной СписокОбъявленийПеременных
VariableModifier:
Аннотация
final
LocalVariableType:
НетипизированныйТип
var
VariableDeclaratorList:
ОбъявлениеПеременной {, ОбъявлениеПеременной}
VariableDeclarator:
ИдентификаторОбъявленияПеременной [= ИнициализаторПеременной]
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Аннотация} [ ] {{Аннотация} [ ]}

Тип Выражение должен быть типом массива (§10.1) или подтипом исходного типа Iterable, в противном случае возникает ошибка компиляции.

Заголовок улучшенного оператора цикла объявляет локальную переменную, имя которой задаётся идентификатором ИдентификаторОбъявленияПеременной. При выполнении оператора цикла локальная переменная инициализируется на каждой итерации последовательными элементами массива или массива, полученного из выражения.

Правила для локальной переменной, объявленной в заголовке улучшенного оператора цикла, указаны в §14.4, игнорируя любые правила в этом разделе, которые применяются, когда ТипЛокальнойПеременной является var. Кроме того, должны выполняться все следующие условия, в противном случае возникает ошибка компиляции:

  • СписокОбъявленийПеременных состоит из одной ОбъявленияПеременной.

  • ОбъявленияПеременной не имеет инициализатора.

  • ИдентификаторОбъявленияПеременной не имеет пар скобок, если ТипЛокальнойПеременной является var.

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

Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения ограничены, как указано в §6.5.6.1.

Тип T локальной переменной, объявленной в заголовке улучшенного оператора цикла, определяется следующим образом:

  • Если ТипЛокальнойПеременной является НетипизированныйТип, и в НетипизированныйТип или ИдентификаторОбъявленияПеременной нет пар скобок, то T — это тип, обозначенный НетипизированныйТип.

  • Если ТипЛокальнойПеременной является НетипизированныйТип, и в НетипизированныйТип или ИдентификаторОбъявленияПеременной есть пары скобок, то T определяется в §10.2.

  • Если ТипЛокальнойПеременной является var, то пусть R выводится из типа Выражение следующим образом:

    • Если Выражение имеет тип массива, то R — это компонентный тип типа массива.

    • В противном случае, если Выражение имеет тип, являющийся подтипом Iterable<X> для некоторого типа X, то R — это X.

    • В противном случае, Выражение имеет тип, являющийся подтипом исходного типа Iterable, и R — это Object.

    T является проекцией R по отношению ко всем синтетическим переменным типов, упомянутым в R (§4.10.5).

Точное значение улучшенного оператора цикла задаётся переводом в базовый оператор цикла следующим образом:

  • Если тип Выражения является подтипом Iterable, то основное утверждение for имеет следующий вид:

    for (I #i = Expression.iterator(); #i.hasNext(); ) {
        {VariableModifier} T Identifier = (TargetType) #i.next();
        Statement
    }
        

    где:

    • Если тип Выражения является подтипом Iterable<X> для некоторого типа аргумента X, то I — это тип java.util.Iterator<X>. В противном случае, I — это исходный тип java.util.Iterator.

    • #i — это автоматически сгенерированный идентификатор, отличный от других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости (§6.3) в момент, когда происходит расширенное утверждение for.

    • {VariableModifier} задается в заголовке расширенного утверждения for.

    • T — это тип локальной переменной, определенный выше.

    • Если T — это тип ссылки, то TargetType — это T. В противном случае, TargetType — это верхняя граница преобразования захвата (§5.1.10) типа аргумента I, или Object, если I является исходным.

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

    S[] #a = Expression;
    L1: L2: ... Lm:
    for (int #i = 0; #i < #a.length; #i++) {
        {VariableModifier} T Identifier = #a[#i];
        Statement
    }
        

    где:

    • L1 ... Lm — это (возможно, пустая) последовательность меток, непосредственно предшествующих расширенному утверждению for.

    • #a и #i — это автоматически сгенерированные идентификаторы, отличные от других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости в момент, когда происходит расширенное утверждение for.

    • {VariableModifier} задается в заголовке расширенного утверждения for.

    • T — это тип локальной переменной, определенный выше.

Например, этот код:

List<? extends Integer> l = ...
for (float i : l) ...

будет переведен в:

for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = (Integer)#i.next();
    ...

Пример 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:

  • Оператор break без метки.

  • Оператор break с меткой Идентификатор.

Оператор break без метки пытается передать управление во внутренний окружающий оператор switch, while, do или for; этот окружающий оператор, который называется целью break, затем немедленно завершается нормально.

Оператор break с меткой Идентификатор пытается передать управление окружающему оператору с меткой (§14.7), имеющему ту же метку Идентификатор, что и цель; этот окружающий оператор, который называется целью break, затем немедленно завершается нормально. В этом случае, цель break не обязательно должна быть оператором switch, while, do или for.

Если у оператора break нет цели break, то это ошибка компиляции.

Если цель break содержит какой-либо метод, конструктор, инициализатор экземпляра, статический инициализатор, лямбда-выражение или выражение switch, которое включает оператор break, то это ошибка компиляции. То есть, нет переходов вне блока.

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

Выполнение оператора break с меткой Идентификатор всегда завершается внезапно, причиной является оператор break с меткой Идентификатор.

Можно видеть, что оператор break всегда завершается внезапно.

В предыдущих описаниях говорится "пытается передать управление", а не просто "передает управление", потому что если есть любые операторы try (§14.20) внутри цели break, блоки try или предложения catch которых содержат оператор break, то любые предложения finally этих операторов try выполняются в порядке, от внутреннего к внешнему, перед передачей управления цели 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 может использоваться только в операторе цикла, операторе цикла с условием или операторе цикла с параметрами; операторы этих трёх типов называются операторами итерации. Управление передаётся к точке продолжения цикла оператора итерации.

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

Существуют два типа оператора continue:

  • Оператор continue без метки.

  • Оператор continue с меткой Идентификатор.

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

Оператор continue с меткой Идентификатор пытается передать управление содержащему оператору с меткой (§14.7), имеющему тот же Идентификатор в качестве метки; этот содержащий оператор, который называется целью continue, затем немедленно завершает текущую итерацию и начинает новую. В этом случае цель continue должна быть оператором цикла с условием, оператором цикла с параметрами или оператором цикла с условием, иначе возникает ошибка компиляции.

Ошибка компиляции возникает, если у оператора continue нет цели continue.

Ошибка компиляции возникает, если цель continue содержит любой метод, конструктор, инициализатор экземпляра, статический инициализатор, лямбда-выражение или выражение switch, которое включает оператор continue. То есть, нет нелокальных переходов.

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

Выполнение оператора continue с меткой Идентификатор всегда завершается прерыванием, причина - оператор continue с меткой Идентификатор.

Таким образом, оператор continue всегда завершается прерыванием.

См. описания оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки прерывания из-за оператора continue.

В предшествующих описаниях используется "попытка передачи управления", а не просто "передача управления", потому что если существуют операторы try (§14.20) в пределах цели continue, блоки try или операторы catch которых содержат оператор continue, затем любые операторы finally этих операторов try будут выполнены в порядке, от вложенного к внешнему, прежде чем управление передаётся цели continue. Прерывание выполнения оператора finally может нарушить передачу управления, начатую оператором continue.

Пример 14.16-1. Оператор continue

В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла. Этот оператор break можно заменить на оператор continue, если сам цикл помечен меткой:

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 со значением Выражение.

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

Ошибка компиляции возникает, если у оператора return нет цели return.

Ошибка компиляции возникает, если цель return содержит (i) инициализатор экземпляра или статический инициализатор, который включает оператор return, или (ii) лямбда-выражение, которое включает оператор return.

Ошибка компиляции возникает, если целью return оператора return без значения является метод, и этот метод не объявлен как void.

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

Ошибка компиляции возникает, если целью return оператора return со значением Выражение является метод с объявленным типом возврата T, а тип Выражения не совместим для присваивания (§5.2) с T.

Выполнение оператора return без значения всегда завершается прерыванием, причиной является return без значения.

Выполнение оператора return со значением Выражение сначала оценивает Выражение. Если оценка Выражения завершается прерыванием по какой-либо причине, то оператор return завершается прерыванием по этой причине. Если оценка Выражения завершается нормально, порождая значение V, то оператор return завершается прерыванием, причиной является return со значением V.

Таким образом, оператор 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) по крайней мере одному из типов, перечисленных в пункте throws (§8.4.6, §8.8.5) объявления.

Типы исключений, которые может выбросить оператор throw, указаны в §11.2.2.

Оператор throw сначала вычисляет Выражение. Затем:

  • Если вычисление Выражения завершается прерывисто по какой-либо причине, то оператор throw завершается прерывисто по той же причине.

  • Если вычисление Выражения завершается нормально, произведя значение, отличное от null, V, то оператор 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, совпадают с блокировками, которые неявно приобретаются методами 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 {CatchClause}
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.

Ссылки на параметр исключения из вложенного класса или интерфейса, или лямбда-выражения ограничены, как указано в §6.5.6.1.

Параметр исключения может обозначать свой тип как один тип класса или объединение двух или более типов класса (называемые альтернативами). Альтернативы объединения разделены |.

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 или является фактически постоянным (§4.12.4).

Неявно объявленный параметр исключения является постоянным по своему определению, в то время как фактически постоянный параметр исключения, так сказать, является постоянным из-за того, как он используется. Параметр исключения многотипной catch секции неявно объявляется final, поэтому никогда не будет левым операндом оператора присваивания, но он не считается фактически постоянным.

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

Типы исключений, которые может выбросить оператор 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-with-resources (§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, и выполняется Блок этого блока 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-с-ресурсами

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

TryWithResourcesStatement:
try СпецификацияРесурсов Блок [ОбработчикиИсключений] [Finally]
ResourceSpecification:
( СписокРесурсов [;] )
ResourceList:
Ресурс {; Ресурс}
Resource:
ОбъявлениеЛокальнойПеременной
ДоступКПеременной
VariableAccess:
ИмяВыражения
ДоступКПолю

Ниже приведены следующие правила из §4.3, §8.3, §8.4.1 и §14.4 для удобства:

LocalVariableDeclaration:
{МодификаторПеременной} ТипЛокальнойПеременной СписокОбъявленийПеременных
VariableModifier:
Аннотация
final
LocalVariableType:
НемаркированныйТип
var
VariableDeclaratorList:
ОбъявлениеПеременной {, ОбъявлениеПеременной}
VariableDeclarator:
ИдентификаторПеременной [= ИнициализаторПеременной]
VariableDeclaratorId:
Идентификатор [Размеры]
Dims:
{Аннотация} [ ] {{Аннотация} [ ]}
VariableInitializer:
Выражение
ИнициализаторМассива

См. §8.3 для НемаркированныйТип.

Спецификация ресурсов обозначает ресурсы оператора try-с-ресурсами, объявляя локальные переменные с инициализаторами или ссылаясь на существующие переменные. Существующая переменная ссылается именем выражения (§6.5.6) или выражением доступа к полю (§15.11).

Правила для локальной переменной, объявленной в спецификации ресурсов, указаны в §14.4. Кроме того, должны выполняться все следующие условия, иначе возникает ошибка компиляции:

  • СписокОбъявленийПеременных состоит из одной ОбъявлениеПеременной.

  • ОбъявлениеПеременной имеет инициализатор.

  • ИдентификаторПеременной не содержит пар скобок.

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

Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения, ограничены, как указано в §6.5.6.1.

Тип локальной переменной, объявленной в спецификации ресурсов, указан в §14.4.1.

Тип локальной переменной, объявленной в спецификации ресурсов, или тип существующей переменной, на которую ссылается спецификация ресурсов, должен быть подтипом AutoCloseable, иначе возникает ошибка компиляции.

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

Ресурсы final, в том числе:

  • Локальная переменная, объявленная в спецификации ресурсов, неявно объявляется final, если она не объявлена явно final (§4.12.4).

  • Существующая переменная, на которую ссылается спецификация ресурсов, должна быть final или фактически final переменной, которая определенно присвоена до оператора try-с-ресурсами (§16 (Определённое присваивание)), иначе возникает ошибка компиляции.

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

Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он инициализирован ненулевым значением. Исключение при закрытии одного ресурса не препятствует закрытию других ресурсов. Такое исключение подавляется, если ранее было выброшено исключение инициализатором, блоком try или при закрытии ресурса.

Оператор try-с-ресурсами, в котором спецификация ресурсов указывает на несколько ресурсов, обрабатывается так, как будто это несколько операторов try-с-ресурсами, каждый из которых имеет спецификацию ресурсов, указывающую на один ресурс. При переводе оператора try-с-ресурсами с n ресурсами (n > 1) результатом является оператор try-с-ресурсами с n-1 ресурсом. После n таких переводов появляются n вложенных операторов try-catch-finally, и весь перевод завершается.

14.20.3.1. Basic try-with-resources

A try-with-resources statement with no catch clauses or finally clause is called a basic try-with-resources statement.

If a basic try-with-resource statement is of the form:

try (VariableAccess ...)
    Block

then the resource is first converted to a local variable declaration by the following translation:

try (T #r = VariableAccess ...) {
    Block
}

T is the type of the variable denoted by VariableAccess and #r is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs. The try-with-resources statement is then translated according to the rest of this section.

The meaning of a basic try-with-resources statement of the form:

try ({VariableModifier} R Identifier = Expression ...)
    Block

is given by the following translation to a local variable declaration and a try-catch-finally statement:

{
    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} is defined as {VariableModifier} without final, if present.

#t, #primaryExc, and #suppressedExc are automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs.

If the resource specification indicates one resource, then ResourceSpecification_tail is empty (and the try-catch-finally statement is not itself a try-with-resources statement).

If the resource specification indicates n > 1 resources, then ResourceSpecification_tail consists of the 2nd, 3rd, ..., n'th resources indicated in the resource specification, in the same order (and the try-catch-finally statement is itself a try-with-resources statement).

Reachability and definite assignment rules for the basic try-with-resources statement are implicitly specified by the translation above.

In a basic try-with-resources statement that manages a single resource:

  • If the initialization of the resource completes abruptly because of a throw of a value V, then the try-with-resources statement completes abruptly because of a throw of the value V.

  • If the initialization of the resource completes normally, and the try block completes abruptly because of a throw of a value V, then:

    • If the automatic closing of the resource completes normally, then the try-with-resources statement completes abruptly because of a throw of the value V.

    • If the automatic closing of the resource completes abruptly because of a throw of a value V2, then the try-with-resources statement completes abruptly because of a throw of value V with V2 added to the suppressed exception list of V.

  • If the initialization of the resource completes normally, and the try block completes normally, and the automatic closing of the resource completes abruptly because of a throw of a value V, then the try-with-resources statement completes abruptly because of a throw of the value V.

In a basic try-with-resources statement that manages multiple resources:

  • If the initialization of a resource completes abruptly because of a throw of a value V, then:

    • If the automatic closings of all successfully initialized resources (possibly zero) complete normally, then the try-with-resources statement completes abruptly because of a throw of the value V.

    • If the automatic closings of all successfully initialized resources (possibly zero) complete abruptly because of throws of values V1...Vn, then the try-with-resources statement completes abruptly because of a throw of the value V with any remaining values V1...Vn added to the suppressed exception list of V.

  • If the initialization of all resources completes normally, and the try block completes abruptly because of a throw of a value V, then:

    • If the automatic closings of all initialized resources complete normally, then the try-with-resources statement completes abruptly because of a throw of the value V.

    • If the automatic closings of one or more initialized resources complete abruptly because of throws of values V1...Vn, then the try-with-resources statement completes abruptly because of a throw of the value V with any remaining values V1...Vn added to the suppressed exception list of V.

  • If the initialization of every resource completes normally, and the try block completes normally, then:

    • If one automatic closing of an initialized resource completes abruptly because of a throw of value V, and all other automatic closings of initialized resources complete normally, then the try-with-resources statement completes abruptly because of a throw of the value V.

    • If more than one automatic closing of an initialized resource completes abruptly because of throws of values V1...Vn, then the try-with-resources statement completes abruptly because of a throw of the value V1 with any remaining values V2...Vn added to the suppressed exception list of V1 (where V1 is the exception from the rightmost resource failing to close and Vn is the exception from the leftmost resource failing to close).

14.20.3.2. Extended try-with-resources

A try-with-resources statement with at least one catch clause and/or a finally clause is called an extended try-with-resources statement.

The meaning of an extended try-with-resources statement:

try ResourceSpecification
    Block
[Catches]
[Finally]

is given by the following translation to a basic try-with-resources statement nested inside a try-catch or try-finally or try-catch-finally statement:

try {
    try ResourceSpecification
        Block
}
[Catches]
[Finally]

The effect of the translation is to put the resource specification "inside" the try statement. This allows a catch clause of an extended try-with-resources statement to catch an exception due to the automatic initialization or closing of any resource.

Furthermore, all resources will have been closed (or attempted to be closed) by the time the finally block is executed, in keeping with the intent of the finally keyword.

14.21. Оператор yield

Оператор yield передает управление, заставляя включающее выражение switch (§15.28) генерировать указанное значение.

YieldStatement:
yield Выражение ;

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

Если у оператора yield нет цели yield, возникает ошибка компиляции.

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

Если Выражение оператора yield имеет тип void (§15.1), возникает ошибка компиляции.

Выполнение оператора yield сначала вычисляет Выражение. Если вычисление Выражения завершается прерывисто по какой-либо причине, то оператор yield завершается прерывисто по этой причине. Если вычисление Выражения завершается нормально, произведя значение V, то оператор yield завершается прерывисто, причиной является yield со значением V.

Таким образом, оператор yield всегда завершается прерывисто.

Пример 14.21-1. Оператор yield

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

 class Test {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
	SATURDAY, SUNDAY
    }

    public int calculate(Day d) {
        return switch (d) {
	    case SATURDAY, SUNDAY -> d.ordinal();
            default -> {
                int len = d.toString().length();
                yield len*len;
            }
        };
    }
}


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

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

Этот раздел посвящен точному объяснению слова "достижимый". Идея в том, что должен быть некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, к самому оператору. Анализ учитывает структуру операторов. За исключением специального обращения с операторами while, do и for, условное выражение которых имеет постоянное значение true, значения выражений не учитываются при анализе потока выполнения.

Например, компилятор Java примет код:

{
    int n = 5;
    while (n > 7) k = 2;
}

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

Правила в этом разделе определяют два технических термина:

  • является ли оператор достижимым

  • может ли оператор завершиться нормально

Правила допускают завершение оператора нормально только если он достижим.

Используются ещё два технических термина:

  • Достижимый оператор break выходит из оператора, если внутри цели разрыва нет операторов try, блоки которых содержат оператор break, или есть операторы try, блоки которых содержат оператор break, и все операторы finally этих операторов try могут завершиться нормально.

    Это определение основано на логике "попытки передачи управления" в §14.15.

  • Оператор continue продолжает оператор do, если внутри оператора do нет операторов try, блоки которых содержат оператор continue, или есть операторы try, блоки которых содержат оператор continue, и все операторы finally этих операторов try могут завершиться нормально.

Правила следующие:

  • Блок, являющийся телом конструктора, метода, инициализатора экземпляра, статического инициализатора, лямбда-выражения или выражения switch, достижим.

  • Пустой блок, не являющийся блоком 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 может завершиться нормально.

    • Существует по крайней мере одна метка switch после последней группы операторов блока switch.

    • Существует достижимая break инструкция, которая выходит из оператора switch.

    • Блок switch не содержит метки default.

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

    • Одно из правил switch вводит выражение правила switch (которое обязательно является выражением-инструкцией).

    • Одно из правил switch вводит блок правила switch, который может завершиться нормально.

    • Одно из правил switch вводит блок правила switch, который содержит достижимую инструкцию break, которая выходит из оператора switch.

    • Блок switch не содержит метки default.

  • Блок switch достижим, если достижима инструкция switch.

  • Инструкция в блоке switch , состоящем из групп операторов с метками switch достижима, если блок switch достижим и выполняется хотя бы одно из следующих условий:

    • Она имеет метку case или default.

    • Существует предшествующая ей инструкция в блоке switch, и эта предшествующая инструкция может завершиться нормально.

  • Блок правила switch в блоке switch достижим, если достижим блок switch.

  • Оператор правила switch throw в блоке switch достижим, если достижим блок switch.

  • Оператор while может завершиться нормально, если выполняется хотя бы одно из следующих условий:

    • Оператор while достижим, и выражение условия не является константным выражением (§15.29) со значением true.

    • Существует достижимая break инструкция, которая выходит из оператора while.

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

  • Оператор do может завершиться нормально, если выполняется хотя бы одно из следующих условий:

    • Вложенный оператор может завершиться нормально, и выражение условия не является константным выражением (§15.29) со значением 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.29) со значением true.

    • Существует достижимая break инструкция, которая выходит из оператора for.

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

  • Расширенный оператор for может завершиться нормально только если он достижим.

  • Оператор break, continue, return, throw или yield не может завершиться нормально.

  • Оператор synchronized может завершиться нормально, если вложенный оператор может завершиться нормально.

    Вложенный оператор достижим, если оператор synchronized достижим.

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

    • Блок try может завершиться нормально или любой блок catch может завершиться нормально.

    • Если у оператора try есть блок finally, то блок finally может завершиться нормально.

  • Блок try достижим, если оператор try достижим.

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

    • Тип параметра C — это тип unchecked exception, или Exception, или его суперкласс, или какая-то выражение или оператор throw в блоке try достижимо и может выбросить checked exception, тип которого совместим с типом параметра C (согласно §5.2). (Выражение достижимо, если достижим самый внутренний оператор, содержащий это выражение.)

      См. §15.6 для информации о нормальном и прерывистом завершении оценки выражений.

    • Нет более раннего блока catch A в операторе try, в котором тип параметра C совпадает или является подклассом типа параметра A.

  • Блок Block блока 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 и затем скомпилировать код правильно без других изменений в тексте программы.

Условной компиляции есть один нюанс. Если набор классов, использующих переменную «флаг» — или точнее, любую переменную константу static (§4.12.4) — компилируется, а условный код опускается, то позже недостаточно просто распространить новую версию класса или интерфейса, содержащую определение флага. Классы, использующие флаг, не увидят его нового значения, поэтому их поведение может быть неожиданным. По сути, изменение значения флага бинарно совместимо с существующими двоичными файлами (никакого LinkageError не происходит), но не поведенчески совместимо.

Еще одна причина «встраивания» значений константных переменных static связана с операторами switch. Они являются единственным типом операторов, которые полагаются на константные выражения, а именно, что каждый метка case оператора switch должна быть константным выражением, значение которого отличается от всех остальных меток case. Метки case часто являются ссылками на переменные константы static, поэтому может быть не сразу очевидно, что все метки имеют разные значения. Если доказано, что дубликатов меток нет во время компиляции, то встраивание значений в файл class гарантирует отсутствие дубликатов меток во время выполнения — очень желательное свойство.

Пример 14.22-1. Условная компиляция

Если пример:

class Flags { static final boolean DEBUG = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.DEBUG)
            System.out.println("DEBUG is true");
    }
}

скомпилирован и запущен, он выведет:

DEBUG is true

Предположим, что создана новая версия класса Flags:

class Flags { static final boolean DEBUG = false; }

Если Flags перекомпилирован, но не Test, то запуск нового двоичного файла с существующим двоичным файлом класса Test выведет:

DEBUG is true

потому что DEBUG — это константная переменная, поэтому её значение могло быть использовано при компиляции Test без ссылки на класс Flags.

Это поведение также произойдет, если Flags был интерфейсом, как в изменённом примере:

interface Flags { boolean DEBUG = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.DEBUG)
            System.out.println("DEBUG is true");
    }
}

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

interface Flags {
    boolean debug = Boolean.valueOf(true).booleanValue();
}

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


14.30. Шаблоны

Шаблон описывает тест, который можно выполнить со значением. Шаблоны появляются как операнды операторов и выражений, предоставляющих значения для проверки. Шаблоны объявляют локальные переменные, известные как переменные шаблона.

Процесс проверки значения по отношению к шаблону известен как сопоставление шаблонов. Если значение успешно соответствует шаблону, то процесс сопоставления шаблонов инициализирует переменную шаблона, объявленную шаблоном.

Переменные шаблона находятся в области видимости (§6.3) только там, где удается сопоставление шаблонов, и, следовательно, переменные шаблона будут инициализированы. Невозможно использовать переменную шаблона, которая не была инициализирована.

14.30.1. Виды шаблонов

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

Шаблон:
TypePattern
TypePattern:
LocalVariableDeclaration

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

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Идентификатор [Размеры]
Размеры:
{Аннотация} [ ] {{Аннотация} [ ]}

См. §8.3 для UnannType.

Шаблон типа объявляет одну локальную переменную, известную как переменная шаблона. Идентификатор в объявлении локальной переменной задаёт имя переменной шаблона.

Правила для локальной переменной, объявленной в шаблоне типа, указаны в §14.4. Кроме того, должны быть истинны все следующие утверждения, в противном случае возникает ошибка компиляции:

  • LocalVariableType обозначает тип ссылок (и, кроме того, не является var).

  • VariableDeclaratorList состоит из одного VariableDeclarator.

  • VariableDeclarator не имеет инициализатора.

  • VariableDeclaratorId не имеет пар скобок.

Тип переменной шаблона — это тип ссылки, обозначенный LocalVariableType.

Тип шаблона типа — это тип его переменной шаблона.

Выражение e совместимо с шаблоном типа T, если e может быть приведено к типу T без возникновения исключения (§5.5).

Совместимость выражения с шаблоном используется оператором сопоставления шаблонов instanceof (§15.20.2).

14.30.2. Сопоставление шаблонов

Сопоставление шаблонов — это процесс проверки значения по отношению к шаблону во время выполнения. Сопоставление шаблонов отличается от выполнения операторов (§14.1) и вычисления выражений (§15.1).

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

  • Значение v, которое не является нулевой ссылкой, соответствует шаблону типа T, если v может быть приведено к T без возникновения исключения; и не соответствует в противном случае.

    Если v соответствует, то переменная шаблона, объявленная шаблоном типа, инициализируется v.

    Если v не соответствует, то переменная шаблона, объявленная шаблоном типа, не инициализируется.

Нет правила, охватывающего значение, являющееся нулевой ссылкой. Это связано с тем, что единственный конструкт, выполняющий сопоставление шаблонов, оператор сопоставления шаблонов instanceof (§15.20.2), выполняет это только тогда, когда значение не является нулевой ссылкой. В будущих версиях Java возможно добавление сопоставления шаблонов в другие выражения и операторы.

© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

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