Spec-Zone.ru › Java Language Specification 21

Глава 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 each (§14.14.2)

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

  • шаблон (§14.30.1)

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

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

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

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

  • Указано более одного ОбъявлениеПеременной.

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

LocalVariableDeclarationStatement:
LocalVariableDeclaration ;

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

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

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

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

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

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

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

14.5. Операторы

В языке программирования Java есть много видов операторов. Большинство из них соответствуют операторам языков C и C++, но некоторые являются уникальными.

Как и в C и C++, оператор языка программирования Java страдает от так называемой проблемы «висящего else», проиллюстрированной этим вводящим в заблуждение примером:

if (door.isOpen())
    if (resident.isVisible())
        resident.greet("Hello!");
else door.bell.ring();  // A "dangling else"

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

Язык программирования Java, подобно C и C++ и многим языкам программирования до них, произвольно объявляет, что фраза else принадлежит самому внутреннему оператору if, которому она потенциально может принадлежать. Это правило отражено в следующей грамматике:

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

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

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

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

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

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

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

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

EmptyStatement:
;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

ОператорВыражения:
StatementExpression ;
StatementExpression:
Присваивание
ПрефиксноеУвеличение
ПрефиксноеУменьшение
ПостфиксноеУвеличение
ПостфиксноеУменьшение
ВызовМетода
ВыражениеСозданиеЭкземпляраКласса

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

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

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

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

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

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

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

(void)... ;  // incorrect!

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

14.9. Оператор if

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

IfThenStatement:
if ( Выражение ) Оператор
IfThenElseStatement:
if ( Выражение ) ОператорБезКороткогоIf else Оператор
IfThenElseStatementNoShortIf:
if ( Выражение ) ОператорБезКороткогоIf else ОператорБезКороткогоIf

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

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

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

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

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

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

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

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

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

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

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

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

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

14.10. The assert Оператор

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

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

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

Если Expression1 не имеет тип boolean или Boolean, это ошибка времени компиляции.

Если во второй форме оператора assert, Expression2 имеет тип 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

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

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

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

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

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

  • Если значение равно false, поведение выполнения зависит от наличия Expression2:

    • Если Expression2 присутствует, он оценивается. Затем:

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

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

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

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

    • Если Expression2 отсутствует, создается экземпляр AssertionError без "сообщения детали". Затем:

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

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

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

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

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

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

14.11. The 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 [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; и фильтрованной в противном случае.

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

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

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

    • c обозначает константу перечисления перечисления E, и в блоке переключателя имеется предшествующая недоминирующая 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 метка доминируется, если в блоке переключателя имеется предшествующая недоминирующая case метка с шаблоном case p, где p является безусловным для типа выражения селектора (§14.30.3).

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

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

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

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

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

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

По соображениям совместимости, метка 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 должна стоять последней в блоке переключателя.

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

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

  • Инструкция помечена более чем одной меткой переключателя.

Первое условие предотвращает «просеивание» группы инструкций в другую группу инструкций без инициализации переменных шаблона. Например, если бы инструкция, помеченная 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:
}

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


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 p применяется к значению, происходит сначала с проверкой соответствия значения шаблону p (§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, улучшенный по сравнению с первоначальным, — это оператор, в котором (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 и должны быть исчерпывающими. Это часто достигается добавлением тривиальной метки 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 продолжается путём определения, применим ли метка переключателя, связанная с блоком переключателя, к значению выражения селектора (§14.11.1.2). Затем:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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:
BasicForStatement
EnhancedForStatement
ForStatementNoShortIf:
BasicForStatementNoShortIf
EnhancedForStatementNoShortIf

14.14.1. Основной оператор цикла for

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

BasicForStatement:
for ( [ForInit] ; [Выражение] ; [ForUpdate] ) Оператор
BasicForStatementNoShortIf:
for ( [ForInit] ; [Выражение] ; [ForUpdate] ) ОператорБезКороткихУсловий
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. Оператор цикла с расширенной формой

Оператор цикла с расширенной формой имеет следующий вид:

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, в противном случае произойдёт ошибка компиляции.

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

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

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

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

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

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

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

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

  • Если 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, как показано ниже:

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

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

    где:

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

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

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

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

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

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

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

    где:

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

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

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

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

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

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

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

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

Пример 14.14-1. Улучшенная for и массивы

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


int sum(int[] a) {
    int sum = 0;
    for (int i : a) sum += i;
    return sum;
}


Пример 14.14-2. Улучшенная for и преобразование разворачивания

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


Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
    total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
    System.out.println(e.getKey() + " " + e.getValue() / total);
}


14.15. Инструкция break

Инструкция break передаёт управление из окружающего оператора.

BreakStatement:
break [Идентификатор] ;

Существует два вида инструкции break:

  • Инструкция break без метки.

  • Инструкция break с меткой Идентификатор.

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

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

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

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

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

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

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

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

Пример 14.15-1. Инструкция break

В следующем примере математический граф представляется массивом массивов. Граф состоит из множества узлов и множества рёбер; каждое ребро — это стрелка, которая указывает из некоторого узла в другой узел или из узла в себя. В этом примере предполагается, что нет избыточных рёбер; то есть, для любых двух узлов P и Q, где Q может быть таким же, как P, существует не более одного ребра от P к Q.

Узлы представлены целыми числами, и есть ребро от узла i к узлу edges[i][j] для всех i и j, для которых ссылка на массив edges[i][j] не бросает ArrayIndexOutOfBoundsException.

Задача метода loseEdges, заданных целых чисел i и j, заключается в построении нового графа путём копирования заданного графа, но опуская ребро из узла i в узел j, если оно есть, и ребро из узла j в узел i, если оно есть:

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

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

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

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

Обратите внимание на использование двух меток операторов, edgelist и search, и использование инструкций break. Это позволяет разделить код, копирующий список, исключая одно ребро, между двумя отдельными тестами, тестом для ребра от узла i к узлу j и тестом для ребра от узла j к узлу i.


14.16. Оператор continue

Оператор continue может встречаться только в операторе 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.

В предыдущих описаниях используется фраза "пытается передать управление", а не просто "передает управление", потому что если внутри цели continue имеются операторы try (§14.20) блоки 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) лямбда-выражение, которое включает в себя оператор return, то возникает ошибка компиляции.

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

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

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

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

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

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

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

14.18. Оператор throw

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

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

Выражение Выражение в операторе throw должно обозначать переменную или значение ссылочного типа, которое присваивается (§5.2) типу Throwable, или обозначать нулевую ссылку, в противном случае возникает ошибка компиляции.

Тип ссылки Выражение всегда будет типом класса (поскольку типы интерфейсов не могут быть присвоены типу Throwable), который не является параметризованным (поскольку подкласс Throwable не может быть обобщенным (§8.1.2)).

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

  • Тип Выражения является классом исключения без проверки (§11.1.1) или типом null (§4.1).

  • Оператор throw содержится в блоке try оператора try (§14.20) и не выполняется условие, что оператор try может выбросить исключение типа Выражение. (В этом случае мы говорим, что брошенное значение перехватывается оператором try.)

  • Оператор throw содержится в объявлении метода или конструктора, и тип Выражение присваивается (§5.2) хотя бы одному из типов, перечисленных в блоке throws объявления (§8.4.6, §8.8.5).

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

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

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

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

  • Если вычисление Выражения завершается нормально, производя значение null, то создается экземпляр V' класса NullPointerException и бросается вместо null. Оператор throw затем прерывается, причиной является throw со значением V'.

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

Если существуют какие-либо окружающие операторы try (§14.20), блоки try которых содержат оператор throw, то любые блоки finally этих операторов try выполняются по мере переноса управления наружу, пока брошенное значение не будет перехвачено. Обратите внимание, что прерывание выполнения блока finally может нарушить перенос управления, инициированный оператором throw.

Если оператор throw содержится в объявлении метода или лямбда-выражении, но его значение не перехватывается каким-либо оператором try, содержащим его, то вызов метода прерывается из-за throw.

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

Если оператор throw содержится в статическом инициализаторе (§8.7), то проверка во время компиляции (§11.2.3) гарантирует, что его значение всегда является исключением без проверки, или его значение всегда перехватывается каким-либо оператором try, содержащим его. Если во время выполнения, несмотря на эту проверку, значение не перехватывается каким-либо оператором try, содержащим оператор throw, то значение повторно бросается, если оно является экземпляром класса Error или одного из его подклассов; в противном случае оно оборачивается в объект ExceptionInInitializerError, который затем бросается (§12.4.2).

Если оператор throw содержится в инициализаторе экземпляра (§8.6), то проверка во время компиляции (§11.2.3) гарантирует, что либо его значение всегда является исключением без проверки, либо его значение всегда перехватывается каким-либо оператором try, содержащим его, или тип выброшенного исключения (или один из его суперклассов) встречается в блоке throws каждого конструктора класса.

По соглашению, пользовательские типы, допускающие исключения, обычно объявляются как подклассы класса Exception, который является подклассом класса Throwable (§11.1.1).

14.19. Оператор synchronized

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

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

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

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

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

  • В противном случае, если значение Выражения является null, выбрасывается NullPointerException.

  • В противном случае, пусть не-null значение Выражения будет V. Выполняющийся поток блокирует монитор, связанный с V. Затем выполняется Блок, а затем есть выбор:

    • Если выполнение Блока завершается нормально, то монитор разблокируется и оператор synchronized завершается нормально.

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

Блокировки, приобретаемые операторами synchronized, совпадают с блокировками, которые неявно приобретаются synchronized методами (§8.4.3.6). Один поток может приобретать блокировку более чем один раз.

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

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

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

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

made it!

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


14.20. Оператор try

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Многострочное catch предложение можно рассматривать как последовательность однострочных catch предложений. То есть, catch предложение, где тип параметра исключения обозначается как объединение D1|D2|...|Dn эквивалентно последовательности n catch предложений, где типы параметров исключения — классы D1, D2, ..., Dn соответственно. В блоке Block каждого из 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, и блок Block этого блока 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. Обычно, в операторах с автоматическим закрытием ресурсов, не требуются операторы finally и catch.

TryWithResourcesStatement:
try ResourceSpecification Блок [Обработка исключений] [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. Кроме того, должны выполняться все следующие условия, иначе возникает ошибка компиляции:

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

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

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

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

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

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

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

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

Ресурсы являются 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 Identifier = Expression ...)
    Block

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

{
    final {VariableModifierNoFinal} R Identifier = Expression;
    Throwable #primaryExc = null;

    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (Identifier != null) {
            if (#primaryExc != null) {
                try {
                    Identifier.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                Identifier.close();
            }
        }
    }
}

{VariableModifierNoFinal} is defined as {VariableModifier} without final, if present.

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

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

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

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

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

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

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

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

    • If the automatic closing of the resource completes abruptly because of a throw of a value V2, then the try-with-resources statement completes abruptly because of a throw of 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 выходит из оператора, если внутри цели break нет операторов try, блоки которых содержат оператор break, или есть операторы try, блоки которых содержат оператор break, и все операторы finally этих операторов try могут завершиться нормально.

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

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

Правила таковы:

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

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

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

    Первый оператор в непустом блоке, который не является блоком switch, достижим тогда и только тогда, когда блок достижим.

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

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

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

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

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

    • Содержащийся оператор может завершиться нормально.

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

    Содержащийся оператор достижим тогда и только тогда, когда помеченный оператор достижим.

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

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

    Оператор then-достижим тогда и только тогда, когда оператор if-then достижим.

    Оператор if-then-else может завершиться нормально тогда и только тогда, когда оператор then может завершиться нормально или оператор else может завершиться нормально.

    Оператор then-достижим тогда и только тогда, когда оператор if-then-else достижим.

    Оператор else-достижим тогда и только тогда, когда оператор if-then-else достижим.

    Эта обработка оператора if, независимо от того, имеет ли он часть else, довольно необычна. Обоснование приводится в конце этого раздела.

  • Оператор assert может завершиться нормально тогда и только тогда, когда он достижим.

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

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

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

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

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

    • Оператор switch не расширен (§14.11.2), и его блок switch не содержит метки default.

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

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

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

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

    • Оператор switch не расширен (§14.11.2), и его блок switch не содержит метки default.

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

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

    • Он содержит метку case или default.

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

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

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

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

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

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

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

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

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

    • Оператор do содержит достижимый оператор continue без метки, и оператор do является самым внутренним оператором while, do или for, который содержит этот оператор continue, и оператор continue продолжает этот оператор do, и выражение условия не является константным выражением со значением true.

    • Оператор do содержит достижимый оператор continue с меткой L, и оператор do имеет метку L, и оператор continue продолжает этот оператор do, и выражение условия не является константным выражением со значением true.

    • Существует достижимый оператор break, который выходит из оператора do.

    Содержащийся оператор достижим тогда и только тогда, когда оператор do достижим.

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

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

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

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

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

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

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

    Содержащийся оператор достижим тогда и только тогда, когда оператор synchronized достижим.

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

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

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

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

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

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

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

    • В операторе try нет более раннего блока catch A, в котором тип параметра 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 это ограничение на аннотации может быть снято.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

14.30.3. Свойства образцов

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

  • Образец типа, объявляющий переменную образца ссылочного типа U, применим к ссылочному типу T, если T допускает приведение к U (§5.5).

  • Образец типа, объявляющий переменную образца примитивного типа P, применим к типу P.

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

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

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

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

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

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

  • Образец p доминирует над образцом типа, объявляющим переменную образца типа T, если p является безусловным для T.

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

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

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

Spec-Zone.ru

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