Spec-Zone.ru › Java Language Specification 24

Глава 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.1.1. Полные блоки switch
14.11.1.2. Определение, какой метке 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. Сопоставление с шаблонами
14.30.3. Свойства шаблонов

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

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

END_OF_DOCUMENT_MARKER

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-с-ресурсами (§14.20.3)

  • шаблон (§14.30.1)

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

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

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

Ошибка компиляции, если объявление локальной переменной, которое (i) не включает Идентификатор и (ii) не имеет инициализатора, появляется в любом из следующих мест:

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

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

Ошибка компиляции, если ТипЛокальнойПеременной является 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. Объявления локальных переменных и типы

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

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

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

Ниже приведены производные правила из §14.9 для удобства:

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

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

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

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

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

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

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

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

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

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

ОператорСМетка:
Идентификатор : Оператор
ОператорСМеткаБезКороткогоЕсли:
Идентификатор : ОператорБезКороткогоЕсли

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

В отличие от C и C++, язык программирования Java не имеет оператора goto; метки операторов-идентификаторов используются с операторами break или continue (§14.15, §14.16) появляющиеся где угодно внутри помеченного оператора.

Область действия метки помеченного оператора — непосредственно содержащийся оператор.

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

Нет ограничений на использование одного и того же идентификатора в качестве метки и в качестве имени пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора для обозначения оператора не скрывает (§6.4.2) пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же именем. Использование идентификатора в качестве класса, интерфейса, метода, поля, локальной переменной или в качестве параметра обработчика исключений (§14.20) не скрывает метку оператора с тем же именем.

Помеченный оператор выполняется путем выполнения непосредственно содержащегося оператора.

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

Пример 14.7-1. Метки и идентификаторы

Следующий код был взят из версии класса String и его метода indexOf, где метка первоначально называлась test. Изменение метки на то же имя, что и локальной переменной i, не скрывает метку в области действия объявления i. Таким образом, код корректен.

class Test {
    char[] value;
    int offset, count;
    int indexOf(TestString str, int fromIndex) {
        char[] v1 = value, v2 = str.value;
        int max = offset + (count - str.count);
        int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
    i:
        for (int i = start; i <= max; i++) {
            int n = str.count, j = i, k = str.offset;
            while (n-- != 0) {
                if (v1[j++] != v2[k++])
                    continue i;
            }
            return i - offset;
        }
        return -1;
    }
}

Идентификатор max также мог быть использован как метка оператора; метка не скрывала бы локальную переменную max в рамках помеченного оператора.


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

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

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

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

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

В отличие от C и C++, язык программирования Java допускает использование только определённых форм выражений в качестве операторов выражения. Например, допустимо использование выражения вызова метода (§15.12):

System.out.println("Hello world");  // OK

но недопустимо использование выражения в скобках (§15.8.5):

(System.out.println("Hello world"));  // illegal

Обратите внимание, что язык программирования Java не допускает «приведения к типу void» — void не является типом — поэтому традиционный приём в C, заключающийся в записи оператора выражения, такого как:

(void)... ;  // incorrect!

не работает. С другой стороны, язык программирования Java допускает все наиболее полезные виды выражений в операторах выражений, и он не требует, чтобы вызов метода, используемый в качестве оператора выражения, вызывал метод void, поэтому подобный трюк почти никогда не нужен. Если такой трюк необходим, можно вместо него использовать оператор присваивания (§15.26) или оператор объявления локальной переменной (§14.4).

14.9. Оператор if

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

IfThenStatement:
if ( Выражение ) Оператор
IfThenElseStatement:
if ( Выражение ) ОператорБезКороткого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 утверждение, содержащее булево выражение. Утверждение может быть активным или неактивным. Если утверждение активное, выполнение утверждения приводит к вычислению булева выражения, и ошибка сообщается, если выражение вычисляется как false. Если утверждение неактивное, выполнение утверждения не оказывает никакого эффекта.

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

Для простоты изложения, первое Выражение в обоих формах утверждения называется Выражение1. Во второй форме утверждения второе Выражение называется Выражение2.

Если Выражение1 не имеет тип boolean или Boolean, это ошибка компиляции.

Если во второй форме утверждения Выражение2 является void (§15.1), это ошибка компиляции.

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

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

Утверждение, которое выполняется до завершения инициализации его класса или интерфейса, активно.

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

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

Неактивное утверждение ничего не делает. В частности, ни Выражение1, ни Выражение2 (если оно присутствует) не вычисляются. Выполнение неактивного утверждения всегда завершается нормально.

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

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

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

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

  • Если значение является false, поведение выполнения зависит от того, присутствует ли Выражение2:

    • Если Выражение2 присутствует, оно вычисляется. Затем:

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

      • Если вычисление завершается нормально, создаётся экземпляр AssertionError, сообщение которого — это результирующее значение Выражение2. Затем:

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

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

    • Если Выражение2 отсутствует, создаётся экземпляр AssertionError без «сообщения подробностей». Затем:

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

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

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

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

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

Вторая проблема использования утверждений для проверки аргументов заключается в том, что неправильные аргументы должны привести к соответствующей ошибке во время выполнения (такой как IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Ошибка утверждения не сгенерирует соответствующую ошибку во время выполнения. Снова, это не является незаконным использовать утверждения для проверки аргументов на public методах, но это, как правило, нежелательно. Предполагается, что AssertionError никогда не будет пойман, но это возможно, следовательно, правила для try утверждений должны рассматривать утверждения, появляющиеся в try блоке, аналогично текущему обращению с throw утверждениями.

14.11. Оператор switch

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

SwitchStatement:
switch ( Выражение ) SwitchBlock

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

14.11.1. Блоки switch

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

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : {SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern {, CasePattern} [Guard]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern
Guard:
when Expression

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

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

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

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

Метка case имеет либо список (непустой) case констант, либо null литерал, либо список (непустой) case шаблонов.

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

Метка case с null литералом может иметь необязательную default.

Метка case с case шаблонами может иметь необязательное выражение when, известное как страж, которое представляет дополнительную проверку значений, соответствующих шаблонам. Метка case называется незащищённой, если (i) у неё нет стража, или (ii) у неё есть страж, который представляет собой выражение константы (§15.29) со значением true; и защищённой в противном случае.

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

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


Object obj = ...;
switch (obj) {
  case Integer i, Boolean b -> {
    ...       // Error! Is i or b initialized?
  }
  ...
}

Даже если только один из case шаблонов объявляет переменную шаблона, всё равно не было бы ясно, была ли переменная инициализирована или нет; например:


Object obj = ...;
switch (obj) {
  case Integer i, Boolean _ -> {
    ...       // Error! Is i initialized?
  }
  ...
}

Следующее не приводит к ошибке времени компиляции:


Object obj = ...;
switch (obj) {
  case Integer _, Boolean _ -> {
    ...       // Matches both an Integer and a Boolean
  }
  ...
}

Метки switch и их case константы, null литералы и case шаблоны называются связанными с блоком switch.

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

  • Ни две case константы, связанные с блоком switch, не могут иметь одинаковое значение.

  • Не более одного null литерала может быть связан с блоком switch.

  • Не более одной метки default может быть связана с блоком switch.

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

  • Страж должен иметь тип boolean или Boolean.

  • Любая локальная переменная, формальный параметр или параметр исключения, используемый, но не объявленный в страже, должен быть либо final, либо эффективно final (§4.12.4).

  • Любая пустая переменная final, используемая, но не объявленная в страже, должна быть однозначно присвоена (§16 (Definite Assignment)) до стража.

  • Страж не может быть выражением константы (§15.29) со значением false.

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

  • Если null литерал связан с блоком switch, то T является ссылочным типом.

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

  • Для каждой case константы, связанной с блоком switch, которая является выражением константы, константа совместима по присваиванию с T, и T является одним из типов char, byte, short, int, Character, Byte, Short, Integer или String.

  • Каждый шаблон p, связанный с блоком switch, применим к типу T (§14.30.3).

Блоки switch не предназначены для работы с типами boolean, long, float и double. Выражение селектора оператора switch или выражения switch не может иметь один из этих типов.

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

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

  • Метка case с шаблоном case q считается доминирующей, если перед ней в блоке switch есть недоступная case метка с шаблоном case p, и p доминирует над q (§14.30.3).

    Определение того, доминирует ли один шаблон над другим, основано на типах. Например, шаблон типа Object o доминирует над шаблоном типа String s, поэтому следующее приведёт к ошибке компиляции:

    
    Object obj = ...
    switch (obj) {
        case Object o ->
            System.out.println("An object");
        case String s ->                 // Error!
            System.out.println("A string");
    }
        

    Защищенная case метка с шаблоном case доминирует над меткой case с тем же шаблоном, но без защиты. Например, следующее приведёт к ошибке компиляции:

    
    String str = ...;
    switch (str) {
        case String s ->
            System.out.println("A string");
        case String s when s.length() == 2 ->  // Error!
            System.out.println("Two character string");
        ...
    }
        

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

    
    Integer j = ...;
    switch (j) {
        case Integer i when i <= 0 ->
            System.out.println("Less than or equal to zero");
        case Integer i ->
            System.out.println("An integer");
    }
        

    Единственное исключение — это когда условие является константным выражением со значением true, например:

    
    Integer j = ...;
    switch (j) {
        case Integer i when true ->            // Ok
            System.out.println("An integer");
        case Integer i ->                      // Error!
            System.out.println("An integer");
    }
        

    Метка case с несколькими шаблонами case считается доминирующей, если любой из этих шаблонов доминирует над шаблоном, который появляется как шаблон case в предшествующей незащищённой case метке. Поэтому следующее приведёт к ошибке компиляции (так как шаблон типа Integer _ доминирует над шаблоном типа Number _):

    
        Object obj = ...
        switch (obj) {
          case Number _ ->
            System.out.println("A Number");
          case Integer _, String _ ->       // Error - dominated!
            System.out.println("An Integer or a String");
          ...
        }
        
  • Метка case с константой case c доминирует, если выполняется одно из следующих условий:

    • c — это константное выражение примитивного типа S, и перед ним в блоке switch есть метка case с незащищенным шаблоном case p, где p является безусловным для оберточного класса S.

    • c — это константное выражение ссылочного типа T, и перед ним в блоке switch есть метка case с незащищенным шаблоном case p, где p является безусловным для типа T.

    • c — это имя константы перечисления класса перечислений E, и перед ним в блоке switch есть метка case с незащищенным шаблоном case p, где p является безусловным для типа E.

    Например, метка case с шаблоном типа Integer доминирует над меткой case с целочисленной литеральной константой:

    
    Integer j = ...;
    switch (j) {
        case Integer i ->
            System.out.println("An integer");
        case 42 ->                              // Error - dominated!
            System.out.println("42!");
    }
        
  • Метка default или метка case null, default доминирует, если в блоке switch имеется предшествующая незащищённая case метка с шаблоном case p, где p является безусловным для типа выражения селектора (§14.30.3).

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

Ошибка компиляции, если в блоке switch есть case метка с n (n>1) шаблонами case, ..., pn, где один из шаблонов pi (1≤i<n) доминирует над другим шаблоном pj (i<j≤n).

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

  • В блоке switch есть метка default, которая предшествует метке case с case шаблонами.

  • В блоке switch есть метка default, которая предшествует метке case с литеральной константой null.

  • В блоке switch есть метка case null, default, за которой следует любая другая метка switch.

Если используется, метка default должна стоять в конце блока switch.

По соображениям совместимости метка default может стоять перед метками case, у которых нет литеральной константы null или шаблонов case.


int i = ...;
switch(i) {
    default ->
        System.out.println("Some other integer");
    case 42 -> // allowed
        System.out.println("42");
}

Если используется, метка case null, default должна стоять в конце блока switch.

Ошибка компиляции, если в блоке switch, состоящем из групп операторов с метками switch, оператор помечен меткой case, которая объявляет одну или несколько переменных шаблона (§6.3.3), и выполняется одно из следующих условий:

  • Непосредственно предшествующий оператор в блоке switch может завершиться нормально (§14.22), или

  • Оператор помечен более чем одной меткой switch.

Первое условие предотвращает «провал» группы операторов к другой группе операторов без инициализации переменных шаблона. Например, если бы оператор, помеченный case Integer i, был достижим из предыдущей группы операторов, переменная шаблона i не была бы инициализирована:


Object o = "Hello";
switch (o) {
    case String s:
        System.out.println("String: " + s );  // No break!
    case Integer i:
        System.out.println(i + 1);            // Error! Can be reached
                                              // without matching the
                                              // pattern `Integer i`
    default:
}

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


Object o = "Hello World";
switch (o) {
    case String s:
    case Integer i:
        System.out.println(i + 1);  // Error! Can be reached
                                    // without matching the
                                    // pattern `Integer i`
    default:
}
Object obj = null;
switch (obj) {
    case null:
    case String s:
        System.out.println(s);      // Error! Can be reached
                                    // without matching the
                                    // pattern `String s`
    default:
}

Оба этих условия применяются только тогда, когда шаблон case объявляет переменные шаблона. Следующие примеры, наоборот, не вызывают проблем:


record R() {}
record S() {}
Object o = "Hello World";
switch (o) {
    case String s:
        System.out.println(s);      // No break
    case R():                       // No pattern variables declared
        System.out.println("It's either an R or a string");
        break;
    default:
}
Object ob = new R();
switch (ob) {
    case R():
    case S():                       // Multiple case labels
        System.out.println("Either R or an S");
        break;
    default:
}
Object obj = null;
switch (obj) {
    case null:
    case R():                       // Multiple case labels
        System.out.println("Either null or an R");
        break;
    default:
}

14.11.1.1. Исчерпывающие блоки switch

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

  • В блоке switch есть метка default.

  • В блоке switch есть метка case null, default.

  • Множество, содержащее все константы case и шаблоны case, появляющиеся в метке case без ограничений (вместе известные как case элементы), связанное с блоком switch, не пусто и охватывает тип выражения-селектора e.

Множество case элементов, P, охватывает тип T, если выполняется одно из следующих условий:

  • P охватывает тип U, где T и U имеют одинаковое стирание.

  • P содержит шаблон, который является безусловным для T.

  • T — переменная типа с верхним ограничением B, и P охватывает B.

  • T — пересечение типов T1& ... &Tn, и P охватывает Ti для одного из типов Ti (1≤ i ≤ n).

  • Тип T — тип перечисления E, и P содержит все имена констант перечисления E.

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

    
    enum E { F, G, H }
    static int testEnumExhaustive(E e) {
      return switch(e) {
          case F -> 0;
          case G -> 1;
          case H -> 2;    // No default required!
      };
    }
    
        
  • Тип T обозначает класс abstract sealed или интерфейс sealed C, и для каждого допустимого непосредственного подкласса или подинтерфейса D от C выполняется одно из двух условий:

    1. Нет типа, который обозначает D и является подтипом T, или

    2. Существует тип U, который обозначает D и является подтипом T, и P охватывает U.

    Метка default допустима, но необязательна в случае, если блок switch исчерпывает все допустимые непосредственные подклассы и подинтерфейсы класса abstract sealed или интерфейса sealed. Например:

    
    sealed interface I permits A, B, C {}
    final class A   implements I {}
    final class B   implements I {}
    record C(int j) implements I {}  // Implicitly final
    
    static int testExhaustive1(I i) {
        return switch(i) {
            case A a -> 0;
            case B b -> 1;
            case C c -> 2;           // No default required!
        };
    }
        

    Поскольку блок switch содержит case шаблоны, которые соответствуют всем значениям типов A, B и C, и другие экземпляры типа I недопустимы, этот блок switch является исчерпывающим.

    Тот факт, что допустимый непосредственный подкласс или подинтерфейс может расширять только определённую параметризацию генерического sealed суперкласса или суперинтерфейса, означает, что его не всегда нужно учитывать при определении исчерпываемости блока switch. Например:

    
    sealed interface J<X> permits D, E {}
    final class D<Y> implements J<String> {}
    final class E<X> implements J<X> {}
    
    static int testExhaustive2(J<Integer> ji) {
        return switch(ji) {          // Exhaustive!
            case E<Integer> e -> 42;
        };
    }
        

    Поскольку выражение-селектор имеет тип J<Integer>, допустимый непосредственный подкласс D не нужно рассматривать, так как значение ji не может быть экземпляром D.

  • Тип T обозначает класс записи R, и P содержит шаблон записи p с типом, обозначающим R, и для каждого компонента записи R типа U, если таковой есть, единственное множество, содержащее соответствующий компонент шаблона от p, охватывает U.

    Шаблон записи, компоненты которого все охватывают тип соответствующего компонента записи, считается охватывающим тип записи. Например:

    
    record Test<X>(Object o, X x){}
        static int testExhaustiveRecordPattern(Test<String> r) {
        return switch(r) {                           // Exhaustive!
            case Test<String>(Object o, String s) -> 0;
        };
    }
        
  • P преобразуется в множество Q, и Q охватывает T.

    Множество элементов случая P преобразуется в множество Q, если подмножество P сводится к шаблону p, а Q состоит из оставшихся элементов P вместе с шаблоном p.

    Непустое множество шаблонов RP сводится к одному шаблону rp, если выполняется одно из следующих условий:

    • RP охватывает некоторый тип U, и rp — шаблон типа U.

    • RP состоит из шаблонов записей, типы которых все стираются до одного класса записей R с k (k≥1) компонентами, и существует выделенный компонент cr (1≤r≤k) R, такой что для каждого другого компонента ci (1≤i≤k, i≠r) множество, содержащее компонентные шаблоны от шаблонов записей, соответствующих компоненту ci, эквивалентно одному шаблону qi, множество, содержащее компонентные шаблоны от шаблонов записей, соответствующих компоненту cr, сводится к одному шаблону q, а rp — шаблон записи типа R со списком шаблонов, состоящим из шаблонов q1, ..., qr-1, q, qr+1, ..., qk.

      Непустое множество шаблонов EP эквивалентно одному шаблону ep, если выполняется одно из следующих условий:

      • EP состоит из шаблонов типов, типы которых все имеют одинаковое стирание T, и ep — шаблон типа T.

      • EP состоит из шаблонов записей, типы которых все стираются до одного класса записей R с k (k≥1) компонентами, и для каждого компонента записи множество, содержащее соответствующие компонентные шаблоны от шаблонов записей, эквивалентно одному шаблону qj (1≤j≤k), и ep — шаблон записи типа R со списком компонентных шаблонов, состоящим из компонентных шаблонов q1,...qk.

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


sealed interface I permits A, B, C {}
final class A   implements I {}
final class B   implements I {}
record C(int j) implements I {}  // Implicitly final
record Box(I i) {}

int testExhaustiveRecordPatterns(Box b) {
    return switch (b) {     // Exhaustive!
        case Box(A a) -> 0;
        case Box(B b) -> 1;
        case Box(C c) -> 2;
    };
}

Определение исчерпываемости этого блока switch требует анализа комбинации шаблонов записей. Множество, содержащее шаблон записи Box(I i), охватывает тип Box, и поэтому множество, содержащее шаблоны Box(A a), Box(B b) и Box(C c), может быть преобразовано в множество, содержащее шаблон Box(I i). Это происходит потому, что множество, содержащее шаблоны A a, B b и C c, сводится к шаблону I i (поскольку то же множество охватывает тип I), и, следовательно, множество, содержащее шаблоны Box(A a), Box(B b) и Box(C c), сводится к шаблону Box(I i).

Однако преобразование множества шаблонов записей не всегда так просто. Например:


record IPair(I i, I j){}

int testNonExhaustiveRecordPatterns(IPair p) {
    return switch (p) {     // Not Exhaustive!
        case IPair(A a, A a) -> 0;
        case IPair(B b, B b) -> 1;
        case IPair(C c, C c) -> 2;
    };
}

Искушение применить логику из предыдущего примера для переписывания множества, содержащего шаблоны IPair(A a, A a), IPair(B b, B b), IPair(C c, C c), в множество, содержащее шаблон IPair(I i, I j), и, следовательно, сделать вывод, что блок switch исчерпывает тип IPair. Но это неверно, так как, например, блок switch фактически не имеет метки, соответствующей значению IPair, первой компонентой которого является значение A, а второй компонентой — значение B. Сочетание шаблонов записей для одного компонента допустимо только в том случае, если они соответствуют тем же значениям в других компонентах. Например, множество, содержащее три шаблона записей IPair(A a, I i), IPair(B b, I i) и IPair(C c, I i), может быть сокращено до шаблона IPair(I j, I i).

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

14.11.1.2. Определение метки switch, применяемой во время выполнения

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

  1. Если значение равно null-ссылке, то применяется метка case со литералом null.

  2. Если значение не равно null-ссылке, то мы определяем первую (если есть) метку case в блоке switch, которая применяется к значению следующим образом:

    • Метка case со константой case c применяется к значению типа Character, Byte, Short или Integer, если значение сначала подвергается преобразованию распаковки (§5.1.8) и константа c равна распакованному значению.

      Любое преобразование распаковки завершится нормально, так как гарантируется, что значение, которое распаковывается, не является null-ссылкой.

      Равенство определяется с помощью оператора == (§15.21).

    • Метка case с константой case c применяется к значению типа char, byte, short, int или String, или типу перечисления, если константа c равна значению.

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

    • Определение того, что метка case с шаблонами case, ..., pn (n≥1) применяется к значению, заключается в поиске первой (если есть) метки case шаблона pi (1≤i≤n), которая применяется к значению.

      Определение того, что шаблон case применяется к значению, происходит сначала путем проверки соответствия значения шаблону (§14.30.2). Затем:

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

      • Если сопоставление с шаблоном успешно и метка case не защищена, то применяется этот шаблон case.

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

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

        В противном случае, если полученное значение равно true, то применяется шаблон case.

    • Метка case null, default применяется к каждому значению.

  3. Если значение не равно null-ссылке и ни одна метка case не применяется в соответствии с правилами шага 2, но существует метка default, связанная с блоком switch, то применяется метка default.

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

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

Если метка case с шаблоном case применяется, это означает, что процесс сопоставления значения с шаблоном завершился успешно (§14.30.2). Если значение успешно соответствует шаблону, то процесс сопоставления с шаблоном инициализирует любые переменные шаблона, объявленные шаблоном.

В 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 — это оператор, в котором либо (i) тип выражения-селектора не является char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления, либо (ii) существует шаблон case или литерал null, связанный с блоком switch.

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

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

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

  • Если оператор switch является расширенным оператором switch, то он должен быть полным (§14.11.1.1).

До Java SE 21 операторы switch (и выражения switch) были ограничены двумя способами: (i) тип выражения-селектора был ограничен целочисленным типом (исключая long), типом перечисления или String, и (ii) не поддерживались метки case null. Более того, в отличие от выражений switch, операторы switch не должны были быть полными. Это часто является причиной трудно обнаруживаемых ошибок, когда ни одна метка switch не применяется, и оператор switch молча ничего не делает. Например:

enum E { A, B, C }
E e = ...;
switch (e) {
   case A -> System.out.println("A");
   case B -> System.out.println("B");
   // No case for C!
}

В Java SE 21, помимо поддержки шаблонов case, два ограничения операторов switch (и выражений switch), перечисленные выше, были ослаблены до (i) разрешения выражения-селектора любого ссылочного типа и (ii) разрешения метки case с литералом null. Разработчики языка программирования Java также решили, что операторы switch с расширенным switch должны соответствовать выражениям switch и должны быть полными. Это часто достигается добавлением тривиальной метки default. Например, следующий расширенный оператор switch не является полным:

Object o = ...;
switch (o) {    // Error - non-exhaustive switch!
    case String s -> System.out.println("A string!");
}

но его легко сделать полным:

Object o = ...;
switch (o) {
    case String s -> System.out.println("A string!");
    default -> {}
}

По соображениям совместимости, операторы switch, которые не являются расширенными операторами switch, не обязаны быть полными.

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

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

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

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

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

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

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

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

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

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

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

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

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

WhileStatement:
while ( Выражение ) Оператор
WhileStatementNoShortIf:
while ( Выражение ) ОператорБезКороткогоIf

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

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

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

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

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

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

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

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

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

14.12.1. Аномальное завершение оператора while

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

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

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

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

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

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

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

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

14.13. Оператор do

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

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

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

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

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

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

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

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

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

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

Выполнение оператора do всегда выполняет содержащийся в нём Оператор хотя бы один раз.

14.13.1. Аварийное завершение оператора do

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

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

  • Если выполнение Оператора завершается аварийно из-за continue без метки, то вычисляется Выражение. Далее происходит выбор, основанный на полученном значении:

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

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

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

    • Если оператор do имеет метку L, то вычисляется Выражение. Далее происходит выбор:

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

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

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

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

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

Пример 14.13-1. Оператор do

Следующий код представляет собой возможную реализацию метода toHexString класса Integer:


public static String toHexString(int i) {
    StringBuffer buf = new StringBuffer(8);
    do {
        buf.append(Character.forDigit(i & 0xF, 16));
        i >>>= 4;
    } while (i != 0);
    return buf.reverse().toString();
}

Поскольку необходимо сгенерировать хотя бы одну цифру, оператор do является подходящей управляющей конструкцией.


14.14. Оператор цикла for

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

14.14.2. Улучшенный цикл for

Улучшенный цикл for имеет вид:

EnhancedForStatement:
for ( Объявление локальной переменной : Выражение ) Оператор
EnhancedForStatementNoShortIf:
for ( Объявление локальной переменной : Выражение ) Оператор без сокращённого if

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

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

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

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

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

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

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

  • VariableDeclaratorId не содержит пар квадратных скобок, если LocalVariableType является var.

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

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

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

  • Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId отсутствуют пары квадратных скобок, то T — это тип, обозначаемый UnannType.

  • Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId присутствуют пары квадратных скобок, то T определяется в §10.2.

  • Если LocalVariableType является var, то пусть R выводится из типа Expression следующим образом:

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

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

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

    T — это проекция R вверх относительно всех синтетических типов переменных, упомянутых в R (§4.10.5).

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

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

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

    где:

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

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

    • {МодификаторПеременной} указан в заголовке расширенного утверждения for.

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

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

    • Если объявление локальной переменной в заголовке включает идентификатор, то VarDeclId определяется как Identifier , как указано в заголовке; в противном случае VarDeclId определяется как _ (подчеркивание).

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

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

    где:

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

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

    • {МодификаторПеременной} указан в заголовке расширенного утверждения for.

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

    • Если объявление локальной переменной в заголовке включает идентификатор, то VarDeclId определяется как Identifier , как указано в заголовке; в противном случае VarDeclId определяется как _ (подчеркивание).

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

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 может использоваться только в операторе while, операторе do или операторе for; операторы этих трёх типов называются операторными циклами. Управление передаётся точке продолжения цикла оператора цикла.

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

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

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

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

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

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

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

class Graph {
    int[][] edges;
    public Graph(int[][] edges) { this.edges = edges; }

    public Graph loseEdges(int i, int j) {
        int n = edges.length;
        int[][] newedges = new int[n][];
edgelists:
        for (int k = 0; k < n; ++k) {
            int z;
search:
{
            if (k == i) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == j) break search;
                }
            } else if (k == j) {
                for (z = 0; z < edges[k].length; ++z) {
                    if (edges[k][z] == i) break search;
                }
            }

            // No edge to be deleted; share this list.
            newedges[k] = edges[k];
            continue edgelists;
} //search

            // Copy the list, omitting the edge at position z.
            int   m  = edges[k].length - 1;
            int[] ne = new int[m];
            System.arraycopy(edges[k], 0, ne, 0, z);
            System.arraycopy(edges[k], z+1, ne, z, m-z);
            newedges[k] = ne;
        } //edgelists
        return new Graph(newedges);
    }
}

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


14.17. Оператор return

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

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

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

  • Оператор return без значения.

  • Оператор return со значением Выражение.

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

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

Это ошибка компиляции, если цель return содержит (i) инициализатор экземпляра или статический инициализатор, который включает оператор return, или (ii) выражение switch, которое включает оператор 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 завершается прерыванием по этой причине.

  • Если вычисление Выражение завершается нормально, генерируя ненулевое значение V, то оператор throw завершается прерыванием, причиной которого является исключение throw со значением V.

  • Если вычисление Выражение завершается нормально, генерируя нулевое значение, то создаётся экземпляр 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` обычным образом для достижения взаимного исключения.

Пример 14.19-1. Оператор `synchronized`

class Test {
    public static void main(String[] args) {
        Test t = new Test();
        synchronized(t) {
            synchronized(t) {
                System.out.println("made it!");
            }
        }
    }
}

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

made it!

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


14.20. Оператор try

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

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

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

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

Блок Block сразу после ключевого слова try называется блоком try оператора try.

Блок Block сразу после ключевого слова finally называется блоком finally оператора try.

Оператор try может содержать блоки catch, также называемые обработчиками исключений.

Блок catch объявляет ровно один параметр, который называется параметром исключения.

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

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

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

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

Блок catch, в котором параметр исключения обозначен как единственный тип класса, называется однотипным блоком перехвата.

Блок catch, в котором параметр исключения обозначен как объединение типов, называется многотипным блоком перехвата.

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

Использование переменной типа в обозначении типа параметра исключения является ошибкой компиляции.

Объединение типов содержит две альтернативы Di и Dj (i ≠ j), где Di является подтипом Dj (§4.10.2), является ошибкой компиляции.

Объявленный тип параметра исключения, обозначающего его тип одним классом, — это тип класса.

Объявленный тип параметра исключения, обозначающего его тип как объединение альтернатив D1 | D2 | ... | Dn, — это lub(D1, D2, ..., Dn).

Параметр исключения многотипного блока перехвата неявно объявлен final, если не объявлен явно final.

Если параметр исключения, неявно или явно объявленный final, присваивается в теле блока catch, это ошибка компиляции.

Параметр исключения однотипного блока перехвата никогда неявно объявляется final, но может быть явно объявлен final или являться эффективно final (§4.12.4).

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

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

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

Взаимосвязь между исключениями, которые выбрасывает блок try оператора try, и блоками перехвата catch (если таковые имеются) оператора try описана в §11.2.3.

Обработчики исключений рассматриваются в порядке следования слева направо: самый ранний возможный блок catch перехватывает исключение, получая в качестве аргумента объект выброшенного исключения, как указано в §11.3.

Многоточечный catch можно рассматривать как последовательность одноточечных catch. То есть, catch, где тип параметра исключения обозначается как объединение D1|D2|...|Dn эквивалентно последовательности из n catch, где типы параметров исключения — классовые типы D1, D2, ..., Dn соответственно. В блоке каждой из n catch объявленный тип параметра исключения равен lub(D1, D2, ..., Dn). Например, следующий код:

try {
    ... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
    ... body ...
}

семантически эквивалентен следующему коду:

try {
    ... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
    final ReflectiveOperationException ex = ex1;
    ... body ...
}
catch (final IllegalAccessException ex2) {
    final ReflectiveOperationException ex = ex2;
    ... body ...
}

где многоточечный catch с двумя альтернативами был преобразован в две одноточечные catch, по одной для каждой альтернативы. Компилятор Java не обязан и не рекомендуется компилировать многоточечный catch, дублируя код таким образом, поскольку можно представить многоточечный catch в файле class без дублирования.

finally гарантирует, что finally блок выполняется после try блока и любого catch блока, который может быть выполнен, независимо от того, как управление покидает try блок или catch блок. Обработка finally блока довольно сложна, поэтому два случая оператора try с и без finally блока описываются отдельно (§14.20.1, §14.20.2).

Оператор try может опустить catch блоки и finally блок, если это оператор try-с-ресурсами (§14.20.3).

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

Оператор try без finally блока выполняется путём сначала выполнения try блока. Затем возникает выбор:

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

  • Если выполнение try блока завершается прерывисто из-за throw значения V, то возникает выбор:

    • Если тип времени выполнения V совместим по присваиванию (§5.2) с любым из класса исключений, которые можно перехватить, в любом catch блоке оператора try, то выбирается первый (левый) такой catch блок. Значение V присваивается параметру выбранного catch блока, и выполняется блок этого catch блока, после чего возникает выбор:

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

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

    • Если тип времени выполнения V не совместим по присваиванию с классом любого исключения, которое можно перехватить, в любом из catch блоков оператора try, то оператор try завершается прерывисто из-за throw значения V.

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

Пример 14.20.1-1. Перехват исключения

class BlewIt extends Exception {
    BlewIt() { }
    BlewIt(String s) { super(s); }
}
class Test {
    static void blowUp() throws BlewIt { throw new BlewIt(); }

    public static void main(String[] args) {
        try {
            blowUp();
        } catch (RuntimeException r) {
            System.out.println("Caught RuntimeException");
        } catch (BlewIt b) {
            System.out.println("Caught BlewIt");
        }
    }
}

Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет два catch блока. Тип времени выполнения исключения — BlewIt, который не присваивается переменной типа RuntimeException, но присваивается переменной типа BlewIt, поэтому вывод примера:

Caught BlewIt

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

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

  • Если выполнение блока try завершается нормально, то выполняется блок finally, и затем происходит выбор:

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

    • Если блок finally завершается прерывисто по причине S, то оператор try завершается прерывисто по причине S.

  • Если выполнение блока try завершается прерывисто из-за исключения throw значения V, то происходит выбор:

    • Если тип времени выполнения V совместим по присваиванию с классом обрабатываемого исключения любого блока catch оператора try, то выбирается первый (самый левый) такой блок catch. Значение V присваивается параметру выбранного блока catch, и выполняется блок Block этого блока catch. Затем происходит выбор:

      • Если блок catch завершается нормально, то выполняется блок finally. Затем происходит выбор:

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

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

      • Если блок catch завершается прерывисто по причине R, то выполняется блок finally. Затем происходит выбор:

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

        • Если блок finally завершается прерывисто по причине S, то оператор try завершается прерывисто по причине S (и причина R отбрасывается).

    • Если тип времени выполнения V не совместим по присваиванию с классом обрабатываемого исключения любого блока catch оператора try, то выполняется блок finally. Затем происходит выбор:

      • Если блок finally завершается нормально, то оператор try завершается прерывисто из-за исключения throw значения V.

      • Если блок finally завершается прерывисто по причине S, то оператор try завершается прерывисто по причине S (и исключение throw значения V отбрасывается и забывается).

  • Если выполнение блока try завершается прерывисто по любой другой причине R, то выполняется блок finally, и затем происходит выбор:

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

    • Если блок finally завершается прерывисто по причине S, то оператор try завершается прерывисто по причине S (и причина R отбрасывается).

Пример 14.20.2-1. Обработка неперехваченного исключения с finally

class BlewIt extends Exception {
    BlewIt() { }
    BlewIt(String s) { super(s); }
}
class Test {
    static void blowUp() throws BlewIt {
        throw new NullPointerException();
    }
    public static void main(String[] args) {
        try {
            blowUp();
        } catch (BlewIt b) {
            System.out.println("Caught BlewIt");
        } finally {
            System.out.println("Uncaught Exception");
        }
    }
}

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

Uncaught Exception
Exception in thread "main" java.lang.NullPointerException
        at Test.blowUp(Test.java:7)
        at Test.main(Test.java:11)

Исключение NullPointerException (которое является видом RuntimeException), сгенерированное методом blowUp, не перехватывается оператором try в main, так как NullPointerException не может быть присвоено переменной типа BlewIt. Это вызывает выполнение блока finally, после чего поток, выполняющий main, который является единственным потоком тестовой программы, завершается из-за неперехваченного исключения, что обычно приводит к выводу имени исключения и краткому отладочному сообщению. Однако в этом спецификации не требуется отладочного сообщения.

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


14.20.3. Оператор try-with-resources

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

TryWithResourcesStatement:
try Спецификация ресурсов Блок [Блоки catch] [Блок 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 для UnannType.

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

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

  • Список объявления переменной (VariableDeclaratorList) состоит из одной переменной (VariableDeclarator).

  • Переменная (VariableDeclarator) имеет инициализатор.

  • Идентификатор объявления переменной (VariableDeclaratorId) не содержит квадратных скобок.

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

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

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

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

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

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

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

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

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

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

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

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

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-resources 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 VariableDeclaratorId = 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.

If VariableDeclaratorId is an identifier then Identifier is defined to be that identifier, otherwise Identifier is defined to be 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.

#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 the 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 (where V1 is the exception from the rightmost resource failing to close and Vn is the exception from the leftmost resource failing to close), 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.

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 могут завершиться нормально.

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

  • The block that is the body of a constructor, method, instance initializer, static initializer, lambda expression, or switch expression is reachable.

  • An empty block that is not a switch block can complete normally iff it is reachable.

    A non-empty block that is not a switch block can complete normally iff the last statement in it can complete normally.

    The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.

    Every other statement S in a non-empty block that is not a switch block is reachable iff the statement preceding S can complete normally.

  • A local class declaration statement can complete normally iff it is reachable.

  • A local variable declaration statement can complete normally iff it is reachable.

  • An empty statement can complete normally iff it is reachable.

  • A labeled statement can complete normally if at least one of the following is true:

    • The contained statement can complete normally.

    • There is a reachable break statement that exits the labeled statement.

    The contained statement is reachable iff the labeled statement is reachable.

  • An expression statement can complete normally iff it is reachable.

  • An if-then statement can complete normally iff it is reachable.

    The then-statement is reachable iff the if-then statement is reachable.

    An if-then-else statement can complete normally iff the then-statement can complete normally or the else-statement can complete normally.

    The then-statement is reachable iff the if-then-else statement is reachable.

    The else-statement is reachable iff the if-then-else statement is reachable.

    This handling of an if statement, whether or not it has an else part, is rather unusual. The rationale is given at the end of this section.

  • An assert statement can complete normally iff it is reachable.

  • A switch statement whose switch block is empty, or contains only switch labels, can complete normally.

  • A switch statement whose switch block consists of switch labeled statement groups can complete normally iff at least one of the following is true:

    • The last statement in the switch block can complete normally.

    • There is at least one switch label after the last switch block statement group.

    • There is a reachable break statement that exits the switch statement.

    • The switch statement is not enhanced (§14.11.2) and its switch block does not contain a default label.

  • A switch statement whose switch block consists of switch rules can complete normally iff at least one of the following is true:

    • One of the switch rules introduces a switch rule expression (which is necessarily a statement expression).

    • One of the switch rules introduces a switch rule block that can complete normally.

    • One of the switch rules introduces a switch rule block that contains a reachable break statement which exits the switch statement.

    • The switch statement is not enhanced (§14.11.2) and its switch block does not contain a default label.

  • A switch block is reachable iff its switch statement is reachable.

  • A statement in a switch block that consists of switch labeled statement groups is reachable iff the switch block is reachable and at least one of the following is true:

    • It bears a case or default label.

    • There is a statement preceding it in the switch block and that preceding statement can complete normally.

  • A switch rule block in a switch block is reachable iff the switch block is reachable.

  • A switch rule throw statement in a switch block is reachable iff the switch block is reachable.

  • A while statement can complete normally iff at least one of the following is true:

    • The while statement is reachable and the condition expression is not a constant expression (§15.29) with value true.

    • There is a reachable break statement that exits the while statement.

    The contained statement is reachable iff the while statement is reachable and the condition expression is not a constant expression whose value is false.

  • A do statement can complete normally iff at least one of the following is true:

    • The contained statement can complete normally and the condition expression is not a constant expression (§15.29) with value true.

    • The do statement contains a reachable continue statement with no label, and the do statement is the innermost while, do, or for statement that contains that continue statement, and the continue statement continues that do statement, and the condition expression is not a constant expression with value true.

    • The do statement contains a reachable continue statement with label L, and the do statement has label L, and the continue statement continues that do statement, and the condition expression is not a constant expression with value true.

    • There is a reachable break statement that exits the do statement.

    The contained statement is reachable iff the do statement is reachable.

  • A basic for statement can complete normally iff at least one of the following is true:

    • The for statement is reachable, there is a condition expression, and the condition expression is not a constant expression (§15.29) with value true.

    • There is a reachable break statement that exits the for statement.

    The contained statement is reachable iff the for statement is reachable and the condition expression is not a constant expression whose value is false.

  • An enhanced for statement can complete normally iff it is reachable.

  • A break, continue, return, throw, or yield statement cannot complete normally.

  • A synchronized statement can complete normally iff the contained statement can complete normally.

    The contained statement is reachable iff the synchronized statement is reachable.

  • A try statement can complete normally iff both of the following are true:

    • The try block can complete normally or any catch block can complete normally.

    • If the try statement has a finally block, then the finally block can complete normally.

  • The try block is reachable iff the try statement is reachable.

  • A catch block C может быть достигнут, если оба следующих условия истинны:

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

      См. §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 — это переменная константы static, поэтому ее значение может использоваться при компиляции 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. Типы шаблонов

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

Шаблон:
ШаблонТипа
ШаблонЗаписи
ШаблонТипа:
ЛокальноеОбъявлениеПеременной
ШаблонЗаписи:
ТипСсылка ( [СписокКомпонентныхШаблонов] )
СписокКомпонентныхШаблонов:
КомпонентныйШаблон {, КомпонентныйШаблон }
КомпонентныйШаблон:
Шаблон
ШаблонСопоставленияВсех
ШаблонСопоставленияВсех:
_

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

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

См. §8.3 для ТипБезАннотаций.

Шаблон вложен в шаблоне записи, если (1) он появляется непосредственно в списке компонентных шаблонов шаблона записи, или (2) он вложен в шаблоне записи, который появляется непосредственно в списке компонентных шаблонов шаблона записи. Шаблон главный, если он не вложен в шаблон записи.

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

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

  • ТипЛокальнойПеременной в главном шаблоне типа обозначает тип ссылки (и, более того, не является var).

  • СписокИнициализаторовПеременных состоит из одного ИнициализатораПеременной.

  • У ИнициализатораПеременной нет инициализатора.

  • У ИдентификаторИнициализатора нет пар скобок.

Тип переменной шаблона, объявленной в главном шаблоне типа, является типом ссылки, обозначаемым ТипЛокальнойПеременной.

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

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

  • Если ТипЛокальнойПеременной является var, то шаблон типа должен появиться непосредственно в списке компонентных шаблонов шаблона записи, в противном случае возникает ошибка компиляции.

    Пусть R — тип шаблона записи, а T — тип соответствующего компонента поля в R (§8.10.3). Тип переменной шаблона — это проекция вверх T по отношению ко всем синтетическим типам, упомянутым в T.

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

    
    record R<T>(ArrayList<T> a){}
        

    В данном случае шаблон записи R<String>(var b), тип переменной шаблона b составляет ArrayList<String>.

Шаблон типа называется сопоставлением с null, если он появляется непосредственно в списке компонентных шаблонов шаблона записи типа R, где соответствующий компонент записи R имеет тип U, и шаблон типа является безусловным для типа U (§14.30.3).

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

Шаблон записи состоит из ТипСсылка и списка компонентных шаблонов, содержащих компонентные шаблоны (если таковые имеются). Если ТипСсылка не является типом класса записи (§8.10), то возникает ошибка компиляции.

Если ТипСсылка является сырым типом, то тип шаблона записи определяется неявно, как описано в §18.5.5. Возникает ошибка компиляции, если тип шаблона записи нельзя определить неявно.

Если ТипСсылка (или любая его часть) аннотирована, возникает ошибка компиляции.

В будущих версиях Java Programming Language это ограничение на аннотации может быть снято.

В противном случае, тип шаблона записи — ТипСсылка.

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

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

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

Шаблон сопоставления всех — это специальный шаблон, который не объявляет переменные шаблонов и может появиться только непосредственно в списке компонентных шаблонов шаблона записи r.

Пусть R — тип шаблона записи r, а T — тип соответствующего компонента поля в R (§8.10.3). Тип шаблона сопоставления всех — это проекция вверх T по отношению ко всем синтетическим типам, упомянутым в T.

Можно заметить, что шаблон сопоставления всему соответствует вложенному шаблону типа, который объявляет переменную шаблона без имени, и у которой LocalVariableType — var.

14.30.2. Сопоставление с образцом

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

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

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

  • Нулевое значение ссылки соответствует шаблону типа, если шаблон типа является нулевым (§14.30.1); и не соответствует в противном случае.

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

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

  • Значение v, которое не является нулевой ссылкой, соответствует шаблону типа типа T, если v может быть преобразовано проверкой преобразования (§5.7) к целевому типу T без повышения ClassCastException; и не соответствует в противном случае.

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

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

  • Нулевая ссылка не соответствует шаблону записи.

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

  • Значение v, которое не является нулевой ссылкой, соответствует шаблону записи типа R и списку шаблонов компонентов L, если (i) v может быть преобразовано проверкой преобразования (§5.7) к целевому типу R без повышения ClassCastException; и (ii) каждый компонент записи v соответствует соответствующему шаблону компонента в L; и не соответствует в противном случае.

    Каждый компонент записи v определяется путём вызова метода доступа v, соответствующего этому компоненту. Если выполнение вызова метода доступа прерывается внезапно по причине S, то сопоставление с образцом прерывается внезапно путём выброса MatchException с причиной S.

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

  • Каждое значение соответствует шаблону сопоставления всему.

14.30.3. Свойства шаблонов

Шаблон p называется применимым для типа T, если применяется одно из следующих правил:

  • Шаблон типа, объявляющий переменную шаблона с типом ссылки U, применим к типу ссылки T, если существует преобразование проверки (§5.7) из типа T в тип U.

  • Шаблон типа, объявляющий переменную шаблона с примитивным типом P, применим к типу P.

  • Шаблон записи с типом R и списком шаблонов L применим к типу T, если (i) существует преобразование проверки (§5.7) из типа T в тип R, и (ii) для каждого шаблона компонента p, появляющегося в L, если таковой имеется, p применим к типу соответствующего поля компонента в R.

  • Шаблон сопоставления всему применим к любому типу T.

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

  • Шаблон типа, объявляющий переменную шаблона с типом ссылки S, безусловен для типа ссылки T, если стёртый тип T является подтипом стёртого типа S.

  • Шаблон типа, объявляющий переменную шаблона с примитивным типом P, безусловен для типа P.

  • Шаблон сопоставления всему безусловен для любого типа T.

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

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

  • Шаблон p доминирует над шаблоном типа, который объявляет переменную шаблона типа T, если p безусловен для T.

  • Шаблон p доминирует над шаблоном записи с типом R, если p безусловен для R.

  • Шаблон записи с типом R и списком шаблонов компонентов L доминирует над другим шаблоном записи с типом S и списком шаблонов компонентов M, если (i) R и S называют один и тот же класс записи, и (ii) каждый шаблон компонента, если таковой имеется, в L доминирует над соответствующим шаблоном компонента в M.

  • Шаблон p доминирует над шаблоном сопоставления всему с типом T, если p безусловен для T.

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

Spec-Zone.ru

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