Глава 14. Блоки, операторы и шаблоны
Содержание
- 14.1. Нормальное и прерывистое завершение операторов
- 14.2. Блоки
- 14.3. Объявления локальных классов и интерфейсов
- 14.4. Объявления локальных переменных
- 14.5. Операторы
- 14.6. Пустой оператор
- 14.7. Операторы с метками
- 14.8. Операторы выражений
- 14.9. Оператор
if - 14.10. Оператор
assert - 14.11. Оператор
switch - 14.12. Оператор
while - 14.13. Оператор
do - 14.14. Оператор
for - 14.15. Оператор
break - 14.16. Оператор
continue - 14.17. Оператор
return - 14.18. Оператор
throw - 14.19. Оператор
synchronized - 14.20. Оператор
try - 14.21. Оператор
yield - 14.22. Недостижимые операторы
- 14.30. Шаблоны
Последовательность выполнения программы контролируется операторами, которые выполняются для своего эффекта и не имеют значений.
Некоторые операторы содержат другие операторы в качестве части своей структуры; такие операторы являются подоператорами оператора. Мы говорим, что оператор 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Каждый оператор имеет нормальный режим выполнения, в котором выполняются определённые вычислительные шаги. В следующих разделах описывается нормальный режим выполнения для каждого типа оператора.
Если все шаги выполняются как описано, без признаков прерывистого завершения, то оператор считается завершенным нормально. Однако, некоторые события могут помешать оператору завершиться нормально:
-
Операторы
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).
Если оператор оценивает выражение, прерывистое завершение выражения всегда вызывает немедленное прерывистое завершение оператора с той же причиной. Все последующие шаги в нормальном режиме выполнения не выполняются.
Если не указано иное в этой главе, прерывистое завершение под-оператора вызывает немедленное прерывистое завершение самого оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное, оператор завершается нормально, если все выражения, которые он оценивает, и все под-операторы, которые он выполняет, завершаются нормально.
Блок — это последовательность операторов, объявлений локальных переменных и объявлений локальных классов и интерфейсов в фигурных скобках.
Блок выполняется путём выполнения каждого оператора объявления локальной переменной и других операторов в порядке от первого к последнему (слева направо). Если все эти операторы блока завершаются нормально, то блок завершается нормально. Если любой из этих операторов блока завершается прерывисто по любой причине, то блок завершается прерывисто по той же причине.
Локальный класс — это вложенный класс (§8 (Классы)), объявление которого находится непосредственно внутри блока (§14.2).
Локальный интерфейс — это вложенный интерфейс (§9 (Интерфейсы)), объявление которого находится непосредственно внутри блока.
Следующие правила представлены здесь для удобства:
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.
Объявление локальной переменной объявляет и необязательно инициализирует одну или несколько локальных переменных (§4.12.3).
См. §8.3 для ТипБезАннотаций. Следующие правила из §4.3, §8.3 и §8.4.1 приведены здесь для удобства:
Объявление локальной переменной может появляться в следующих местах:
Правила, касающиеся аннотационных модификаторов для объявления локальной переменной, определены в §9.7.4 и §9.7.5.
Если ключевое слово final появляется в качестве модификатора для объявления локальной переменной, то локальная переменная является final переменной (§4.12.4).
Ошибка компиляции, если final появляется более одного раза в качестве модификатора для объявления локальной переменной.
Ошибка компиляции, если объявление локальной переменной, которое (i) не включает Идентификатор и (ii) не имеет инициализатора, появляется в любом из следующих мест:
Ошибка компиляции, если ТипЛокальнойПеременной является 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.
Каждый объявитель в объявлении локальной переменной объявляет одну локальную переменную. Если объявлятель содержит Идентификатор, то это имя локальной переменной, в противном случае локальная переменная неименована (§6.1).
Если необязательное ключевое слово final появляется в начале объявления, то объявляемая переменная является конечной переменной (§4.12.4).
Тип объявленной локальной переменной определяется следующим образом:
-
Если ТипЛокальнойПеременной является НеаннотированныйТип, и пары скобок не появляются в НеаннотированныйТип или ИдентификаторОбъявителяПеременной, то тип локальной переменной обозначается НеаннотированныйТип.
-
Если ТипЛокальнойПеременной является НеаннотированныйТип, и пары скобок появляются в НеаннотированныйТип или ИдентификаторОбъявителяПеременной, то тип локальной переменной задаётся §10.2.
-
Если ТипЛокальнойПеременной является
var, то пусть T будет типом выражения инициализатора, когда оно рассматривается так, как будто оно не появляется в контексте присваивания и является автономным выражением (§15.2). Тип локальной переменной является проекцией вверх T по отношению ко всем синтетическим переменным типов, упомянутым T (§4.10.5).Ошибка компиляции, если T является типом null.
Поскольку инициализатор рассматривается так, как будто он не появляется в контексте присваивания, возникает ошибка, если это лямбда-выражение (§15.27) или выражение ссылки на метод (§15.13).
Область действия и перекрытие объявления локальной переменной указаны в §6.3 и §6.4.
Ссылка на локальную переменную из вложенного класса или интерфейса или лямбда-выражения ограничена, как указано в §6.5.6.1.
Пример 14.4.1-1. Тип локальных переменных, объявленных с var
Следующий код демонстрирует типизацию переменных, объявленных с var:
var a = 1; // a has type 'int'
var b = java.util.List.of(1, 2); // b has type 'List<Integer>'
var c = "x".getClass(); // c has type 'Class<? extends String>'
// (see JLS 15.12.2.6)
var d = new Object() {}; // d has the type of the anonymous class
var e = (CharSequence & Comparable<String>) "x";
// e has type CharSequence & Comparable<String>
var f = () -> "hello"; // Illegal: lambda not in an assignment context
var g = null; // Illegal: null type
Обратите внимание, что некоторые переменные, объявленные с var, не могут быть объявлены с явным типом, потому что тип переменной не может быть обозначен.
Проекция вверх применяется к типу инициализатора при определении типа переменной. Если тип инициализатора содержит переменные захвата, эта проекция отображает тип инициализатора на супертип, не содержащий переменных захвата.
Хотя было бы возможно разрешить упоминание переменных захвата в типе переменной, путем их проекции мы обеспечиваем привлекательное свойство, что область действия переменной захвата никогда не больше, чем утверждение, содержащее выражение, тип которого захватывается. Неформально, переменные захвата не могут «просачиваться» в последующие утверждения.
Утверждение объявления локальной переменной состоит из объявления локальной переменной.
Каждое утверждение объявления локальной переменной немедленно содержится в блоке, в то время как другие виды утверждений (§14.5) могут быть немедленно содержащимися либо в блоке, либо в другом утверждении.
В содержащем блоке утверждения объявления локальных переменных могут свободно перемешиваться с другими видами утверждений и с объявлениями локальных классов и интерфейсов.
Утверждение объявления локальной переменной является исполняемым утверждением. Каждый раз при его выполнении объявлятели обрабатываются в порядке следования слева направо. Если у объявлятеля есть инициализатор, инициализатор оценивается, и его значение присваивается переменной.
Если у объявлятеля нет инициализатора, то любая ссылка на переменную должна предшествовать выполнению присваивания переменной или происходит ошибка времени компиляции по правилам §16 (Определённое присваивание).
Каждый инициализатор (кроме первого) оценивается только в том случае, если оценка предыдущего инициализатора завершается нормально.
Выполнение утверждения объявления локальной переменной завершается нормально только в том случае, если оценка последнего инициализатора завершается нормально.
Если ни один из объявлятелей в утверждении объявления локальной переменной не имеет инициализатора, то выполнение утверждения всегда завершается нормально.
В языке программирования Java существует множество типов операторов. Большинство из них соответствуют операторам языков C и C++, но некоторые являются уникальными.
Как и в C и C++, оператор if языка программирования Java страдает от так называемой проблемы «висящего else», проиллюстрированной этим вводящим в заблуждение примером:
if (door.isOpen())
if (resident.isVisible())
resident.greet("Hello!");
else door.bell.ring(); // A "dangling else"
Проблема заключается в том, что как внешний оператор if, так и внутренний оператор if потенциально могут владеть клаузой else. В данном примере можно предположить, что программист намеревался, чтобы клауза else относилась к внешнему оператору if.
Язык программирования Java, как и C, C++ и многие другие языки программирования до них, произвольно определяет, что клауза else принадлежит самому внутреннему оператору if, к которому она потенциально может относиться. Это правило отражено в следующей грамматике:
Ниже приведены производные правила из §14.9 для удобства:
Таким образом, операторы грамматически делятся на две категории: те, которые могут завершаться оператором if, не имеющим клаузы else ("короткий оператор if") и те, которые определённо не завершаются.
Только операторы, которые определённо не завершаются коротким оператором if, могут появляться как непосредственный под-оператор перед ключевым словом else в операторе if, имеющем клаузу else.
Это простое правило предотвращает проблему «висящего else». Поведение оператора с ограничением «без короткого if» идентично поведению оператора того же типа без ограничения «без короткого if»; это различие вводится исключительно для решения синтаксической проблемы.
Пустой оператор ничего не делает.
; Выполнение пустого оператора всегда завершается нормально.
Операторы могут иметь префиксы метки.
Идентификатор объявляется меткой непосредственно содержащегося оператора.
В отличие от C и C++, язык программирования Java не имеет оператора goto; метки операторов-идентификаторов используются с операторами break или continue (§14.15, §14.16) появляющиеся где угодно внутри помеченного оператора.
Область действия метки помеченного оператора — непосредственно содержащийся оператор.
Если имя метки помеченного оператора используется в области действия метки в качестве метки другого помеченного оператора, то это является ошибкой компиляции.
Нет ограничений на использование одного и того же идентификатора в качестве метки и в качестве имени пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора для обозначения оператора не скрывает (§6.4.2) пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же именем. Использование идентификатора в качестве класса, интерфейса, метода, поля, локальной переменной или в качестве параметра обработчика исключений (§14.20) не скрывает метку оператора с тем же именем.
Помеченный оператор выполняется путем выполнения непосредственно содержащегося оператора.
Если оператор помечен идентификатором и содержащийся оператор завершается прерыванием из-за оператора break с тем же идентификатором, то помеченный оператор завершается нормально. Во всех других случаях прерывания выполнения оператора помеченный оператор завершается прерыванием по той же причине.
Пример 14.7-1. Метки и идентификаторы
Следующий код был взят из версии класса String и его метода indexOf, где метка первоначально называлась test. Изменение метки на то же имя, что и локальной переменной i, не скрывает метку в области действия объявления i. Таким образом, код корректен.
class Test {
char[] value;
int offset, count;
int indexOf(TestString str, int fromIndex) {
char[] v1 = value, v2 = str.value;
int max = offset + (count - str.count);
int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
i:
for (int i = start; i <= max; i++) {
int n = str.count, j = i, k = str.offset;
while (n-- != 0) {
if (v1[j++] != v2[k++])
continue i;
}
return i - offset;
}
return -1;
}
}
Идентификатор max также мог быть использован как метка оператора; метка не скрывала бы локальную переменную max в рамках помеченного оператора.
Некоторые виды выражений могут использоваться как операторы, если после них следует точка с запятой.
Оператор выражения выполняется путём вычисления выражения; если выражение имеет значение, это значение игнорируется.
Выполнение оператора выражения завершается нормально тогда и только тогда, когда вычисление выражения завершается нормально.
В отличие от C и C++, язык программирования Java допускает использование только определённых форм выражений в качестве операторов выражения. Например, допустимо использование выражения вызова метода (§15.12):
System.out.println("Hello world"); // OK
но недопустимо использование выражения в скобках (§15.8.5):
(System.out.println("Hello world")); // illegal
Обратите внимание, что язык программирования Java не допускает «приведения к типу void» — void не является типом — поэтому традиционный приём в C, заключающийся в записи оператора выражения, такого как:
(void)... ; // incorrect!
не работает. С другой стороны, язык программирования Java допускает все наиболее полезные виды выражений в операторах выражений, и он не требует, чтобы вызов метода, используемый в качестве оператора выражения, вызывал метод void, поэтому подобный трюк почти никогда не нужен. Если такой трюк необходим, можно вместо него использовать оператор присваивания (§15.26) или оператор объявления локальной переменной (§14.4).
Оператор if позволяет условно выполнить оператор или условно выбрать один из двух операторов, выполнив один из них, но не оба.
Выражение должно иметь тип boolean или Boolean, иначе произойдёт ошибка компиляции.
Оператор if-then выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он преобразуется в свой примитивный тип (§5.1.8).
Если вычисление выражения Выражение или последующее приведение к примитивному типу (если таковое имеется) прерывается по какой-либо причине, то оператор if-then прерывается по той же причине.
В противном случае выполнение продолжается с выбором, основанным на полученном значении:
-
Если значение равно
true, то выполняется содержащийся оператор Оператор; операторif-thenзавершается нормально, тогда и только тогда, когда выполнение оператора Оператор завершается нормально. -
Если значение равно
false, то никаких дальнейших действий не выполняется, и операторif-thenзавершается нормально.
Оператор if-then-else выполняется путём сначала вычисления выражения Выражение. Если результат имеет тип Boolean, он преобразуется в свой примитивный тип (§5.1.8).
Если вычисление выражения Выражение или последующее приведение к примитивному типу (если таковое имеется) прерывается по какой-либо причине, то оператор if-then-else прерывается по той же причине.
В противном случае выполнение продолжается с выбором, основанным на полученном значении:
-
Если значение равно
true, то выполняется первый содержащийся оператор Оператор (который стоит перед ключевым словомelse); операторif-then-elseзавершается нормально, тогда и только тогда, когда выполнение этого оператора завершается нормально. -
Если значение равно
false, то выполняется второй содержащийся оператор Оператор (который стоит после ключевого словаelse); операторif-then-elseзавершается нормально, тогда и только тогда, когда выполнение этого оператора завершается нормально.
Утверждение — это assert утверждение, содержащее булево выражение. Утверждение может быть активным или неактивным. Если утверждение активное, выполнение утверждения приводит к вычислению булева выражения, и ошибка сообщается, если выражение вычисляется как false. Если утверждение неактивное, выполнение утверждения не оказывает никакого эффекта.
Для простоты изложения, первое Выражение в обоих формах утверждения называется Выражение1. Во второй форме утверждения второе Выражение называется Выражение2.
Если Выражение1 не имеет тип boolean или Boolean, это ошибка компиляции.
Если во второй форме утверждения Выражение2 является void (§15.1), это ошибка компиляции.
Утверждение, которое выполняется после завершения инициализации его класса или интерфейса, активно, если и только если хост-система определила, что верхнеуровневый класс или интерфейс, содержащий утверждение лексически, позволяет использовать утверждения.
Определяется ли верхнеуровневый класс или интерфейс позволяет использовать утверждения не позднее, чем (i) инициализации верхнеуровневого класса или интерфейса, и (ii) инициализации любого вложенного класса или интерфейса в верхнеуровневом классе или интерфейсе. После определения того, позволяет ли верхнеуровневый класс или интерфейс использовать утверждения, это свойство нельзя изменить.
Утверждение, которое выполняется до завершения инициализации его класса или интерфейса, активно.
Это правило мотивировано случаем, который требует особого обращения. Вспомним, что статус утверждения класса устанавливается не позднее времени его инициализации. Возможно, хотя и обычно нежелательно, выполнить методы или конструкторы до инициализации. Это может произойти, когда иерархия классов содержит цикличность в своей статической инициализации, как в следующем примере:
public class Foo {
public static void main(String[] args) {
Baz.testAsserts();
// Will execute after Baz is initialized.
}
}
class Bar {
static {
Baz.testAsserts();
// Will execute before Baz is initialized!
}
}
class Baz extends Bar {
static void testAsserts() {
boolean enabled = false;
assert enabled = true;
System.out.println("Asserts " +
(enabled ? "enabled" : "disabled"));
}
}
Вызов Baz.testAsserts() приводит к инициализации Baz. Прежде чем это произойдёт, должна быть инициализирована Bar. Статический инициализатор Bar снова вызывает Baz.testAsserts(). Поскольку инициализация Baz уже происходит в текущей потоке, второе обращение выполняется немедленно, хотя Baz ещё не инициализирован (§12.4.2).
Из-за правила выше, если программа выше выполняется без включения утверждений, она должна вывести:
Asserts enabled Asserts disabled
Неактивное утверждение ничего не делает. В частности, ни Выражение1, ни Выражение2 (если оно присутствует) не вычисляются. Выполнение неактивного утверждения всегда завершается нормально.
Активное утверждение выполняется путём сначала вычисления Выражение1. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если вычисление Выражение1 или последующее преобразование распаковки (если таковое имеется) завершается прерывисто по какой-либо причине, то и утверждение завершается прерывисто по той же причине.
В противном случае выполнение продолжается, приняв решение на основе значения Выражение1:
-
Если значение является
true, никаких дальнейших действий не предпринимается, и утверждение завершается нормально. -
Если значение является
false, поведение выполнения зависит от того, присутствует ли Выражение2:-
Если Выражение2 присутствует, оно вычисляется. Затем:
-
Если вычисление завершается прерывисто по какой-либо причине, утверждение завершается прерывисто по той же причине.
-
Если вычисление завершается нормально, создаётся экземпляр
AssertionError, сообщение которого — это результирующее значение Выражение2. Затем:-
Если создание экземпляра завершается прерывисто по какой-либо причине, утверждение завершается прерывисто по той же причине.
-
Если создание экземпляра завершается нормально, утверждение завершается прерывисто, сбрасывая вновь созданный объект
AssertionError.
-
-
-
Если Выражение2 отсутствует, создаётся экземпляр
AssertionErrorбез «сообщения подробностей». Затем:-
Если создание экземпляра завершается прерывисто по какой-либо причине, утверждение завершается прерывисто по той же причине.
-
Если создание экземпляра завершается нормально, утверждение завершается прерывисто, сбрасывая вновь созданный объект
AssertionError.
-
-
Обычно проверка утверждений включена во время разработки и тестирования программ и отключена при развертывании для повышения производительности.
Поскольку утверждения могут быть отключены, программы не должны предполагать, что выражения, содержащиеся в утверждениях, будут вычисляться. Таким образом, эти булевы выражения, как правило, не должны иметь побочных эффектов. Вычисление такого булева выражения не должно влиять на любой состояние, видимое после завершения вычисления. Не является незаконным, чтобы булево выражение, содержащееся в утверждении, имело побочный эффект, но это, как правило, нежелательно, так как это может привести к изменению поведения программы в зависимости от того, были ли утверждения включены или отключены.
В свете этого утверждения не следует использовать для проверки аргументов в public методах. Проверка аргументов обычно является частью контракта метода, и этот контракт должен соблюдаться независимо от того, включены или отключены утверждения.
Вторая проблема использования утверждений для проверки аргументов заключается в том, что неправильные аргументы должны привести к соответствующей ошибке во время выполнения (такой как IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Ошибка утверждения не сгенерирует соответствующую ошибку во время выполнения. Снова, это не является незаконным использовать утверждения для проверки аргументов на public методах, но это, как правило, нежелательно. Предполагается, что AssertionError никогда не будет пойман, но это возможно, следовательно, правила для try утверждений должны рассматривать утверждения, появляющиеся в try блоке, аналогично текущему обращению с throw утверждениями.
Оператор switch передает управление одному из нескольких операторов или выражений в зависимости от значения выражения.
Выражение называется выражением-селектором. Тип выражения-селектора должен быть char, byte, short, int, или ссылочным типом; в противном случае произойдёт ошибка компиляции.
Тело как оператора switch, так и выражения switch (§15.28) называется блоком switch. В этом подразделе приводятся общие правила, которые применяются ко всем блокам switch, независимо от того, появляются ли они в операторах switch или выражениях switch. Другие подразделы содержат дополнительные правила, которые применяются либо к блокам switch в операторах switch (§14.11.2), либо к блокам switch в выражениях switch (§15.28.1).
case CaseConstant {, CaseConstant} case null [, default] case CasePattern {, CasePattern} [Guard] default when Expression Блок switch может состоять либо из:
-
Правил switch, которые используют
->для введения либо выражения правила switch, либо блока правила switch, либо оператора правила switchthrow; или -
Группы операторов с метками switch, которые используют
:для введения блоков операторов с метками switch.
Каждое правило switch и каждая группа операторов с метками switch начинаются с метки switch, которая является либо меткой case, либо меткой default. Для группы операторов с метками switch допускается несколько меток switch.
Метка case имеет либо список (непустой) case констант, либо null литерал, либо список (непустой) case шаблонов.
Каждая case константа должна быть либо выражением константы (§15.29), либо именем константы перечисления (§8.9.1), в противном случае происходит ошибка времени компиляции.
Метка case с null литералом может иметь необязательную default.
Метка case с case шаблонами может иметь необязательное выражение when, известное как страж, которое представляет дополнительную проверку значений, соответствующих шаблонам. Метка case называется незащищённой, если (i) у неё нет стража, или (ii) у неё есть страж, который представляет собой выражение константы (§15.29) со значением true; и защищённой в противном случае.
Ошибка времени компиляции возникает, если у метки case более одного case шаблона и она объявляет переменные шаблона (кроме тех, которые объявлены стражем, связанным с меткой case).
Если у метки case с несколькими case шаблонами могли быть объявлены переменные шаблона, то не было бы ясно, какие переменные были бы инициализированы, если бы метка case была применена. Например:
Object obj = ...;
switch (obj) {
case Integer i, Boolean b -> {
... // Error! Is i or b initialized?
}
...
}
Даже если только один из case шаблонов объявляет переменную шаблона, всё равно не было бы ясно, была ли переменная инициализирована или нет; например:
Object obj = ...;
switch (obj) {
case Integer i, Boolean _ -> {
... // Error! Is i initialized?
}
...
}
Следующее не приводит к ошибке времени компиляции:
Object obj = ...;
switch (obj) {
case Integer _, Boolean _ -> {
... // Matches both an Integer and a Boolean
}
...
}
Метки switch и их case константы, null литералы и case шаблоны называются связанными с блоком switch.
Для данного блока switch оба следующих условия должны быть истинны, в противном случае возникает ошибка времени компиляции:
-
Ни две
caseконстанты, связанные с блоком switch, не могут иметь одинаковое значение. -
Не более одного
nullлитерала может быть связан с блоком switch. -
Не более одной метки
defaultможет быть связана с блоком switch.
Страж, связанный с меткой case, должен удовлетворять всем следующим условиям, в противном случае возникает ошибка времени компиляции:
-
Страж должен иметь тип
booleanилиBoolean. -
Любая локальная переменная, формальный параметр или параметр исключения, используемый, но не объявленный в страже, должен быть либо
final, либо эффективно final (§4.12.4). -
Любая пустая переменная
final, используемая, но не объявленная в страже, должна быть однозначно присвоена (§16 (Definite Assignment)) до стража. -
Страж не может быть выражением константы (§15.29) со значением
false.
Блок switch оператора switch или выражения switch совместим со switch с типом выражения селектора, T, если выполняются все следующие условия:
-
Если
nullлитерал связан с блоком switch, то T является ссылочным типом. -
Для каждой
caseконстанты, связанной с блоком switch, которая называет константу перечисления, типcaseконстанты совместим по присваиванию с T (§5.2). -
Для каждой
caseконстанты, связанной с блоком switch, которая является выражением константы, константа совместима по присваиванию с T, и T является одним из типовchar,byte,short,int,Character,Byte,Short,IntegerилиString. -
Каждый шаблон
p, связанный с блоком switch, применим к типу T (§14.30.3).
Блоки switch не предназначены для работы с типами boolean, long, float и double. Выражение селектора оператора switch или выражения switch не может иметь один из этих типов.
Блок switch оператора switch или выражения switch должен быть совместим со switch с типом выражения селектора, в противном случае возникает ошибка времени компиляции.
Метка switch в блоке switch называется доминирующей, если для каждого значения, к которому она применима, можно определить, что одно из предыдущих меток switch также было бы применимо. Если какая-либо метка switch в блоке switch доминирует, возникает ошибка времени компиляции. Правила определения того, доминирует ли метка switch, приведены ниже:
-
Метка
caseс шаблономcaseqсчитается доминирующей, если перед ней в блоке switch есть недоступнаяcaseметка с шаблономcasep, иpдоминирует надq(§14.30.3).Определение того, доминирует ли один шаблон над другим, основано на типах. Например, шаблон типа
Objectoдоминирует над шаблоном типаStrings, поэтому следующее приведёт к ошибке компиляции:Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error! System.out.println("A string"); }Защищенная
caseметка с шаблономcaseдоминирует над меткойcaseс тем же шаблоном, но без защиты. Например, следующее приведёт к ошибке компиляции:String str = ...; switch (str) { case String s -> System.out.println("A string"); case String s when s.length() == 2 -> // Error! System.out.println("Two character string"); ... }С другой стороны, защищенная
caseметка с шаблономcaseне считается доминирующей над незащищеннойcaseметкой с тем же шаблономcase. Это позволяет использовать распространённый стиль программирования с шаблонами:Integer j = ...; switch (j) { case Integer i when i <= 0 -> System.out.println("Less than or equal to zero"); case Integer i -> System.out.println("An integer"); }Единственное исключение — это когда условие является константным выражением со значением
true, например:Integer j = ...; switch (j) { case Integer i when true -> // Ok System.out.println("An integer"); case Integer i -> // Error! System.out.println("An integer"); }Метка
caseс несколькими шаблонамиcaseсчитается доминирующей, если любой из этих шаблонов доминирует над шаблоном, который появляется как шаблонcaseв предшествующей незащищённойcaseметке. Поэтому следующее приведёт к ошибке компиляции (так как шаблон типаInteger_доминирует над шаблоном типаNumber_):Object obj = ... switch (obj) { case Number _ -> System.out.println("A Number"); case Integer _, String _ -> // Error - dominated! System.out.println("An Integer or a String"); ... } -
Метка
caseс константойcasecдоминирует, если выполняется одно из следующих условий:-
c— это константное выражение примитивного типа S, и перед ним в блоке switch есть меткаcaseс незащищенным шаблономcasep, гдеpявляется безусловным для оберточного класса S. -
c— это константное выражение ссылочного типа T, и перед ним в блоке switch есть меткаcaseс незащищенным шаблономcasep, гдеpявляется безусловным для типа T. -
c— это имя константы перечисления класса перечислений E, и перед ним в блоке switch есть меткаcaseс незащищенным шаблономcasep, где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или меткаcasenull,defaultдоминирует, если в блоке switch имеется предшествующая незащищённаяcaseметка с шаблономcasep, гдеpявляется безусловным для типа выражения селектора (§14.30.3).Метка
caseс шаблономcase, который безусловен для типа выражения селектора, как следует из названия, будет соответствовать каждому значению и будет вести себя как меткаdefault. В блоке switch не может быть более одной метки, которая действует какdefault.
Ошибка компиляции, если в блоке switch есть case метка с n (n>1) шаблонами case, ..., pn, где один из шаблонов pi (1≤i<n) доминирует над другим шаблоном pj (i<j≤n).
Ошибка компиляции, если выполняется любое из следующих условий:
-
В блоке switch есть метка
default, которая предшествует меткеcaseсcaseшаблонами. -
В блоке switch есть метка
default, которая предшествует меткеcaseс литеральной константойnull. -
В блоке switch есть метка
casenull,default, за которой следует любая другая метка switch.
Если используется, метка default должна стоять в конце блока switch.
По соображениям совместимости метка default может стоять перед метками case, у которых нет литеральной константы null или шаблонов case.
int i = ...;
switch(i) {
default ->
System.out.println("Some other integer");
case 42 -> // allowed
System.out.println("42");
}
Если используется, метка case null, default должна стоять в конце блока switch.
Ошибка компиляции, если в блоке switch, состоящем из групп операторов с метками switch, оператор помечен меткой case, которая объявляет одну или несколько переменных шаблона (§6.3.3), и выполняется одно из следующих условий:
-
Непосредственно предшествующий оператор в блоке switch может завершиться нормально (§14.22), или
-
Оператор помечен более чем одной меткой switch.
Первое условие предотвращает «провал» группы операторов к другой группе операторов без инициализации переменных шаблона. Например, если бы оператор, помеченный case Integer i, был достижим из предыдущей группы операторов, переменная шаблона i не была бы инициализирована:
Object o = "Hello";
switch (o) {
case String s:
System.out.println("String: " + s ); // No break!
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
Блоки switch, состоящие из групп операторов с метками switch, позволяют применять несколько меток к группе операторов. Второе условие предотвращает выполнение группы операторов на основе одной метки без инициализации переменных шаблонов другой метки. Например:
Object o = "Hello World";
switch (o) {
case String s:
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
Object obj = null;
switch (obj) {
case null:
case String s:
System.out.println(s); // Error! Can be reached
// without matching the
// pattern `String s`
default:
}
Оба этих условия применяются только тогда, когда шаблон case объявляет переменные шаблона. Следующие примеры, наоборот, не вызывают проблем:
record R() {}
record S() {}
Object o = "Hello World";
switch (o) {
case String s:
System.out.println(s); // No break
case R(): // No pattern variables declared
System.out.println("It's either an R or a string");
break;
default:
}
Object ob = new R();
switch (ob) {
case R():
case S(): // Multiple case labels
System.out.println("Either R or an S");
break;
default:
}
Object obj = null;
switch (obj) {
case null:
case R(): // Multiple case labels
System.out.println("Either null or an R");
break;
default:
}
Блок switch в выражении switch или операторе switch считается исчерпывающим для выражения-селектора e, если выполняется одно из следующих условий:
-
В блоке switch есть метка
default. -
В блоке switch есть метка
casenull,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 обозначает класс
abstractsealedили интерфейсsealedC, и для каждого допустимого непосредственного подкласса или подинтерфейса D от C выполняется одно из двух условий:-
Нет типа, который обозначает D и является подтипом T, или
-
Существует тип U, который обозначает D и является подтипом T, и P охватывает U.
Метка
defaultдопустима, но необязательна в случае, если блок switch исчерпывает все допустимые непосредственные подклассы и подинтерфейсы классаabstractsealedили интерфейса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 является исчерпывающим для выражения-селектора.
Как выполнение оператора switch (§14.11.3), так и вычисление выражения switch (§15.28.2) требуют определения, соответствует ли метка switch, связанная с блоком switch, значению выражения-селектора. Это выполняется следующим образом:
-
Если значение равно null-ссылке, то применяется метка
caseсо литераломnull. -
Если значение не равно null-ссылке, то мы определяем первую (если есть) метку
caseв блоке switch, которая применяется к значению следующим образом:-
Метка
caseсо константойcasecприменяется к значению типаCharacter,Byte,ShortилиInteger, если значение сначала подвергается преобразованию распаковки (§5.1.8) и константаcравна распакованному значению.Любое преобразование распаковки завершится нормально, так как гарантируется, что значение, которое распаковывается, не является null-ссылкой.
Равенство определяется с помощью оператора
==(§15.21). -
Метка
caseс константойcasecприменяется к значению типаchar,byte,short,intилиString, или типу перечисления, если константаcравна значению.Равенство определяется с помощью оператора
==, за исключением случая, когда значение являетсяString, в этом случае равенство определяется методомequalsклассаString. -
Определение того, что метка
caseс шаблонамиcase, ...,pn(n≥1) применяется к значению, заключается в поиске первой (если есть) меткиcaseшаблонаpi(1≤i≤n), которая применяется к значению.Определение того, что шаблон
caseприменяется к значению, происходит сначала путем проверки соответствия значения шаблону (§14.30.2). Затем:-
Если сопоставление с шаблоном завершается внезапно, то весь процесс определения метки switch завершается внезапно по той же причине.
-
Если сопоставление с шаблоном успешно и метка
caseне защищена, то применяется этот шаблонcase. -
Если сопоставление с шаблоном успешно, и метка
caseзащищена, то вычисляется условие. Если результат является типомBoolean, то к нему применяется преобразование распаковки (§5.1.8).Если вычисление условия или последующее преобразование распаковки (если есть) завершается внезапно по какой-либо причине, то весь процесс определения метки switch завершается внезапно по той же причине.
В противном случае, если полученное значение равно
true, то применяется шаблонcase.
-
-
Метка
casenull,defaultприменяется к каждому значению.
-
-
Если значение не равно 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);
}
К счастью, этот трюк, похоже, не так широко известен или используется. Кроме того, он сейчас меньше нужен; такой вид преобразования кода правильно находится в компетенции современных оптимизирующих компиляторов.
Помимо общих правил для блоков switch (§14.11.1), существуют дополнительные правила для блоков switch в операторах switch.
Оператор switch с расширенным switch — это оператор, в котором либо (i) тип выражения-селектора не является char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления, либо (ii) существует шаблон case или литерал null, связанный с блоком switch.
Для блока switch оператора switch должны выполняться все следующие условия, иначе произойдёт ошибка компиляции:
-
Каждое выражение правила switch в блоке switch является выражением-высказыванием (§14.8).
Операторы
switchотличаются от выраженийswitchпо тем выражениям, которые могут располагаться справа от стрелки (->) в блоке switch, то есть, какие выражения могут использоваться как выражения правил switch. В оператореswitchв качестве выражения правила switch может использоваться только выражение-высказывание, а в выраженииswitchможет использоваться любое выражение (§15.28.1). -
Если оператор
switchявляется расширенным операторомswitch, то он должен быть полным (§14.11.1.1).
До Java SE 21 операторы switch (и выражения switch) были ограничены двумя способами: (i) тип выражения-селектора был ограничен целочисленным типом (исключая long), типом перечисления или String, и (ii) не поддерживались метки case null. Более того, в отличие от выражений switch, операторы switch не должны были быть полными. Это часто является причиной трудно обнаруживаемых ошибок, когда ни одна метка switch не применяется, и оператор switch молча ничего не делает. Например:
enum E { A, B, C }
E e = ...;
switch (e) {
case A -> System.out.println("A");
case B -> System.out.println("B");
// No case for C!
}
В Java SE 21, помимо поддержки шаблонов case, два ограничения операторов switch (и выражений switch), перечисленные выше, были ослаблены до (i) разрешения выражения-селектора любого ссылочного типа и (ii) разрешения метки case с литералом null. Разработчики языка программирования Java также решили, что операторы switch с расширенным switch должны соответствовать выражениям switch и должны быть полными. Это часто достигается добавлением тривиальной метки default. Например, следующий расширенный оператор switch не является полным:
Object o = ...;
switch (o) { // Error - non-exhaustive switch!
case String s -> System.out.println("A string!");
}
но его легко сделать полным:
Object o = ...;
switch (o) {
case String s -> System.out.println("A string!");
default -> {}
}
По соображениям совместимости, операторы switch, которые не являются расширенными операторами switch, не обязаны быть полными.
Оператор switch выполняется путём сначала вычисления выражения-селектора. Если вычисление выражения-селектора завершается внезапно, то весь оператор switch завершается внезапно по той же причине.
Если вычисление выражения-селектора завершается нормально, то выполнение оператора switch продолжается путём определения, применяется ли метка switch, связанная с блоком switch, к значению выражения-селектора (§14.11.1.2). Затем:
-
Если процесс определения, какая метка switch применяется, завершается внезапно, то весь оператор
switchзавершается внезапно по той же причине. -
Если никакая метка switch не применяется, то справедливо одно из следующих утверждений:
-
Если значение выражения-селектора является
null, то выбрасывается исключениеNullPointerException, и весь операторswitchзавершается внезапно по этой причине. -
Если оператор
switchявляется расширенным операторомswitch, то выбрасывается исключениеMatchException, и весь операторswitchзавершается внезапно по этой причине. -
Если значение выражения-селектора не является
null, и операторswitchне является расширенным операторомswitch, то весь операторswitchзавершается нормально.
-
-
Если применяется метка switch, то справедливо одно из следующих утверждений:
-
Если это метка switch для выражения правила switch, то выражение правила switch обязательно является выражением-высказыванием (§14.11.2). Вычисляется выражение-высказывание. Если вычисление завершается нормально, то оператор
switchзавершается нормально. Если результат вычисления является значением, оно игнорируется. -
Если это метка switch для блока правила switch, то выполняется блок. Если этот блок завершается нормально, то оператор
switchзавершается нормально. -
Если это метка switch для оператора правила switch
throw, то выполняется операторthrow. -
Если это метка switch для группы меток оператора switch, то все операторы в блоке switch, которые следуют за меткой switch, выполняются в порядке следования. Если эти операторы завершаются нормально, то оператор
switchзавершается нормально. -
В противном случае в блоке switch нет операторов, которые следуют за применившейся меткой switch, и оператор
switchзавершается нормально.
-
Если выполнение любого оператора или выражения в блоке switch завершается внезапно, оно обрабатывается следующим образом:
-
Если выполнение оператора завершается внезапно из-за
breakбез метки, то никаких дальнейших действий не предпринимается, и операторswitchзавершается нормально.Внезапное завершение из-за
breakс меткой обрабатывается по общему правилу для меченых операторов (§14.7). -
Если выполнение оператора или выражения завершается внезапно по любой другой причине, то оператор
switchзавершается внезапно по той же причине.Внезапное завершение из-за оператора
yieldобрабатывается по общему правилу для выражений switch (§15.28.2).
Пример 14.11.3-1. Пропуск в операторе switch
Когда применяется метка switch, и эта метка относится к правилу switch, то выражение или оператор правила switch, введённые с помощью метки switch, выполняются, и ничего больше. В случае метки switch для группы операторов выполняются все операторы блока switch, следующие за меткой switch, включая те, что следуют за последующими метками switch. Эффект заключается в том, что, как в C и C++, выполнение операторов может "проходить через метки".
Например, программа:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
содержит блок switch, в котором код каждого case передаётся в код следующего case. В результате программа выводит:
many too many one too many
Пропуск может стать причиной скрытых ошибок. Если код не должен пропускать case в case таким образом, то операторы break могут использоваться для обозначения того, когда должен передаваться контроль, или могут использоваться правила switch, как в программе:
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
static void howManyAgain(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
howManyAgain(1);
howManyAgain(2);
howManyAgain(3);
}
}
Эта программа выводит:
one two many one two many
Оператор while выполняет Выражение и Оператор многократно до тех пор, пока значение Выражения не станет false.
Выражение должно иметь тип boolean или Boolean, иначе произойдёт ошибка компиляции.
Оператор while выполняется путём сначала вычисления Выражения. Если результат имеет тип Boolean, он подвергается преобразованию развёртывания (§5.1.8).
Если вычисление Выражения или последующее преобразование развёртывания (если таковое имеется) завершается аномально по какой-либо причине, оператор while завершается аномально по той же причине.
В противном случае выполнение продолжается, основываясь на результирующем значении:
-
Если значение равно
true, то выполняемый оператор Оператор. Затем идёт выбор:-
Если выполнение оператора Оператор завершается нормально, тогда весь оператор
whileвыполняется снова, начиная с повторного вычисления Выражения. -
Если выполнение оператора Оператор завершается аномально, см. §14.12.1.
-
-
Если (возможно, развёрнутое) значение Выражения равно
false, никаких дальнейших действий не выполняется, и операторwhileзавершается нормально.Если (возможно, развёрнутое) значение Выражения равно
falseпри первом вычислении, то Оператор не выполняется.
Аномальное завершение вложенного оператора Оператор обрабатывается следующим образом:
-
Если выполнение оператора Оператор завершается аномально из-за
breakбез метки, никаких дальнейших действий не выполняется, и операторwhileзавершается нормально. -
Если выполнение оператора Оператор завершается аномально из-за
continueбез метки, то весь операторwhileвыполняется снова. -
Если выполнение оператора Оператор завершается аномально из-за
continueс меткойL, тогда происходит выбор:-
Если оператор
whileимеет меткуL, то весь операторwhileвыполняется снова. -
Если оператор
whileне имеет меткиL, операторwhileзавершается аномально из-заcontinueс меткойL.
-
-
Если выполнение оператора Оператор завершается аномально по любой другой причине, оператор
whileзавершается аномально по той же причине.Случай аномального завершения из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Оператор do выполняет Оператор и Выражение повторно до тех пор, пока значение Выражения не станет false.
У Выражения должен быть тип boolean или Boolean; в противном случае произойдёт ошибка компиляции.
Оператор do выполняется путём сначала выполнения Оператора. Далее происходит выбор:
-
Если выполнение Оператора завершилось нормально, то вычисляется Выражение. Если результат имеет тип
Boolean, он преобразуется к нему (§5.1.8).Если вычисление Выражения или последующее преобразование в другой тип (если таковое имеется) завершается аварийно по какой-либо причине, то оператор
doзавершается аварийно по той же причине.В противном случае, выбор основывается на полученном значении:
-
Если значение равно
true, то весь операторdoвыполняется снова. -
Если значение равно
false, то никаких дополнительных действий не предпринимается, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершилось аварийно, см. §14.13.1.
Выполнение оператора do всегда выполняет содержащийся в нём Оператор хотя бы один раз.
Аварийное завершение содержащегося Оператора обрабатывается следующим образом:
-
Если выполнение Оператора завершается аварийно из-за
breakбез метки, то никаких дальнейших действий не предпринимается, и операторdoзавершается нормально. -
Если выполнение Оператора завершается аварийно из-за
continueбез метки, то вычисляется Выражение. Далее происходит выбор, основанный на полученном значении:-
Если значение равно
true, то весь операторdoвыполняется снова. -
Если значение равно
false, то никаких дальнейших действий не предпринимается, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершается аварийно из-за
continueс меткойL, то происходит выбор:-
Если оператор
doимеет меткуL, то вычисляется Выражение. Далее происходит выбор:-
Если значение Выражения равно
true, то весь операторdoвыполняется снова. -
Если значение Выражения равно
false, то никаких дальнейших действий не предпринимается, и операторdoзавершается нормально.
-
-
Если оператор
doне имеет меткиL, то операторdoзавершается аварийно из-заcontinueс меткойL.
-
-
Если выполнение Оператора завершается аварийно по любой другой причине, оператор
doзавершается аварийно по той же причине.Случай аварийного завершения из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Пример 14.13-1. Оператор do
Следующий код представляет собой возможную реализацию метода toHexString класса Integer:
public static String toHexString(int i) {
StringBuffer buf = new StringBuffer(8);
do {
buf.append(Character.forDigit(i & 0xF, 16));
i >>>= 4;
} while (i != 0);
return buf.reverse().toString();
}
Поскольку необходимо сгенерировать хотя бы одну цифру, оператор do является подходящей управляющей конструкцией.
Оператор цикла for имеет две формы:
-
Базовый оператор цикла
for. -
Расширенный оператор цикла
for
Базовый оператор цикла for выполняет некоторый код инициализации, затем выполняет выражение, оператор и код обновления многократно, до тех пор, пока значение выражения не станет false.
Тип выражения должен быть boolean или Boolean, в противном случае произойдет ошибка компиляции.
Область действия и перекрытие локальной переменной, объявленной в части ForInit базового оператора цикла for, описаны в §6.3 и §6.4.
Ссылки на локальную переменную, объявленную в части ForInit базового оператора цикла for из вложенного класса или интерфейса, или лямбда-выражения, ограничены, как указано в §6.5.6.1.
Оператор цикла for выполняется путем выполнения кода ForInit:
-
Если код ForInit представляет собой список операторских выражений (§14.8), выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются.
Если выполнение любого выражения завершается аномально по какой-либо причине, оператор цикла
forзавершается по той же причине; любые операторские выражения ForInit справа от завершившегося аномально выражения не вычисляются. -
Если код ForInit представляет собой объявление локальной переменной (§14.4), он выполняется так, как если бы это был оператор объявления локальной переменной, расположенный в блоке (§14.4.2).
Если выполнение объявления локальной переменной завершается аномально по какой-либо причине, оператор цикла
forзавершается по той же причине. -
Если часть ForInit отсутствует, никаких действий не выполняется.
Далее выполняется шаг итерации цикла for, следующим образом:
-
Если выражение присутствует, оно вычисляется. Если результат имеет тип
Boolean, он подвергается преобразованию распаковки (§5.1.8).Если вычисление выражения или последующее преобразование распаковки (если применимо) завершается аномально, оператор цикла
forзавершается по той же причине.В противном случае, выбор зависит от наличия и значения выражения; см. следующий пункт.
-
Если выражение отсутствует или присутствует и значение, полученное в результате его вычисления (включая возможную распаковку), равно
true, то содержимое оператора выполняется. После этого есть выбор:-
Если выполнение оператора завершается нормально, выполняются следующие два шага последовательно:
-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются. Если выполнение любого выражения завершается аномально по какой-либо причине, оператор цикла
forзавершается по той же причине; любые операторские выражения ForUpdate справа от завершившегося аномально выражения не вычисляются.Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется еще один шаг итерации
for.
-
-
Если выполнение оператора завершается аномально, см. §14.14.1.3.
-
-
Если выражение присутствует и значение, полученное в результате его вычисления (включая возможную распаковку), равно
false, дальнейшие действия не выполняются, и оператор циклаforзавершается нормально.Если значение (возможно, распакованное) выражения равно
falseв первый раз, когда оно вычисляется, то оператор не выполняется.
Если выражение отсутствует, единственный способ, которым оператор цикла for может завершиться нормально, — это с помощью оператора break.
Прерывание выполнения содержащегося оператора обрабатывается следующим образом:
-
Если выполнение оператора прерывается из-за
breakбез метки, дальнейших действий не предпринимается, и операторforвыполняется нормально. -
Если выполнение оператора прерывается из-за
continueбез метки, выполняются следующие два шага последовательно:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если часть ForUpdate отсутствует, никаких действий не предпринимается.
-
Во-вторых, выполняется ещё один шаг итерации
for.
-
-
Если выполнение оператора прерывается из-за
continueс меткойL, возникает выбор:-
Если оператор
forимеет меткуL, выполняются следующие два шага последовательно:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если ForUpdate отсутствует, никаких действий не предпринимается.
-
Во-вторых, выполняется ещё один шаг итерации
for.
-
-
Если оператор
forне имеет меткиL, операторforпрерывается из-заcontinueс меткойL.
-
-
Если выполнение оператора прерывается по любой другой причине, оператор
forпрерывается по той же причине.Обратите внимание, что случай прерывания из-за
breakс меткой обрабатывается общим правилом для операторов с метками (§14.7).
Улучшенный цикл for имеет вид:
Ниже для удобства показаны следующие правила из §4.3, §8.3, §8.4.1 и §14.4:
Тип Выражения должен быть типом массива (§10.1) или подтипом базового типа Iterable, иначе произойдёт ошибка компиляции.
Заголовок улучшенного цикла for либо объявляет локальную переменную, имя которой задано VariableDeclaratorId, либо объявляет безымянную локальную переменную (§6.3). При выполнении улучшенного цикла for локальная переменная инициализируется на каждой итерации цикла последовательными элементами массива или массива, полученного в результате выражения.
Правила для локальной переменной, объявленной в заголовке улучшенного цикла for, указаны в §14.4, при этом игнорируются правила из этой части, которые применяются, когда LocalVariableType является var. Кроме того, должны выполняться все следующие условия, иначе произойдёт ошибка компиляции:
-
VariableDeclaratorList состоит из единственного VariableDeclarator.
-
VariableDeclarator не имеет инициализатора.
-
VariableDeclaratorId не содержит пар квадратных скобок, если LocalVariableType является
var.
Область действия и перекрытие локальной переменной, объявленной в заголовке улучшенного цикла for, указаны в §6.3 и §6.4.
Доступ к локальной переменной из вложенного класса или интерфейса или лямбда-выражения ограничен, как указано в §6.5.6.1.
Тип T локальной переменной, объявленной в заголовке улучшенного цикла for, определяется следующим образом:
-
Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId отсутствуют пары квадратных скобок, то T — это тип, обозначаемый UnannType.
-
Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId присутствуют пары квадратных скобок, то T определяется в §10.2.
-
Если LocalVariableType является
var, то пусть R выводится из типа Expression следующим образом:-
Если Expression имеет тип массива, то R — это компонентный тип типа массива.
-
В противном случае, если Expression имеет тип, являющийся подтипом
Iterable<X>для некоторого типа X, то R — это X. -
В противном случае, Expression имеет тип, являющийся подтипом базового типа
Iterable, и R — этоObject.
T — это проекция R вверх относительно всех синтетических типов переменных, упомянутых в R (§4.10.5).
-
Точное значение улучшенного цикла for задаётся переводом в базовый цикл for следующим образом:
-
Если тип Выражения является подтипом
Iterable, то основное утверждениеforимеет такой вид:for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T VarDeclId = (TargetType) #i.next(); Statement }
где:
-
Если тип Выражения является подтипом
Iterable<X>для некоторого типа аргумента X, то I — типjava.util.Iterator<X>. В противном случае, I — это исходный типjava.util.Iterator. -
#i— это автоматически сгенерированный идентификатор, который отличается от всех других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости (§6.3) в момент, когда происходит расширенное утверждениеfor. -
{МодификаторПеременной} указан в заголовке расширенного утверждения
for. -
T — тип локальной переменной, определённый выше.
-
Если T — это тип ссылки, то TargetType — T. В противном случае, TargetType — это верхняя граница преобразования захвата (§5.1.10) типа аргумента I, или
Object, если I — это исходный тип. -
Если объявление локальной переменной в заголовке включает идентификатор, то VarDeclId определяется как Identifier , как указано в заголовке; в противном случае VarDeclId определяется как
_(подчеркивание).
-
-
В противном случае, Выражение обязательно имеет тип массива, S
[], и основное утверждениеforимеет такой вид:S
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T VarDeclId = #a[#i]; Statement }где:
-
L1...Lm— это (возможно, пустая) последовательность меток, непосредственно предшествующих расширенному утверждениюfor. -
#aи#i— это автоматически сгенерированные идентификаторы, которые отличаются от любых других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости в момент, когда происходит расширенное утверждениеfor. -
{МодификаторПеременной} указан в заголовке расширенного утверждения
for. -
T — тип локальной переменной, определённый выше.
-
Если объявление локальной переменной в заголовке включает идентификатор, то VarDeclId определяется как Identifier , как указано в заголовке; в противном случае VarDeclId определяется как
_(подчеркивание).
-
Например, этот код:
List<? extends Integer> l = ... for (float i : l) ...
будет переведён в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
float #i0 = (Integer)#i.next();
...
Пример 14.14-1. Расширенное for и Массивы
Следующая программа, вычисляющая сумму целочисленного массива, демонстрирует, как работает расширенное for для массивов:
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
Пример 14.14-2. Расширенное for и Преобразование Автоматического Развертывания
Следующая программа объединяет утверждение расширенного for с автоматическим развёртыванием для преобразования гистограммы в таблицу частот:
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
Утверждение 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.
Оператор continue может использоваться только в операторе while, операторе do или операторе for; операторы этих трёх типов называются операторными циклами. Управление передаётся точке продолжения цикла оператора цикла.
Существуют два вида оператора continue:
-
Оператор
continueбез метки. -
Оператор
continueс меткой Идентификатор.
Оператор continue без метки пытается передать управление ближайшему включающему оператору while, оператору do или оператору for; этот включающий оператор, который называется целью continue, немедленно завершает текущую итерацию и начинает новую.
Оператор continue с меткой Идентификатор пытается передать управление включающему оператору с меткой (§14.7), имеющему ту же самую метку Идентификатор; этот включающий оператор, который называется целью continue, немедленно завершает текущую итерацию и начинает новую. В этом случае цель continue должна быть оператором while, do или for, иначе возникает ошибка компиляции.
Если у оператора continue нет цели continue, это ошибка компиляции.
Если цель continue содержит любой метод, конструктор, инициализатор экземпляра, статический инициализатор, лямбда-выражение или выражение switch, которое включает оператор continue, то это ошибка компиляции. То есть, нет переходов на нелокальные участки кода.
Выполнение оператора continue без метки всегда завершается внезапно, по причине continue без метки.
Выполнение оператора continue с меткой Идентификатор всегда завершается внезапно, по причине continue с меткой Идентификатор.
Таким образом, оператор continue всегда завершается внезапно.
См. описания оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки внезапного завершения из-за continue.
В предыдущих описаниях используется "пытается передать управление", а не просто "передаёт управление", потому что если есть операторы try (§14.20) внутри цели continue, блоки try или клаузы catch которых содержат оператор continue, то все клаузы finally этих операторов try выполняются в порядке от вложенного к внешнему, прежде чем управление передаётся цели continue. Внезапное завершение клаузы finally может нарушить передачу управления, начатую оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего оператора цикла for. Этот оператор break можно заменить на оператор continue, если сам цикл for помечен:
class Graph {
int[][] edges;
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int[] ne = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Выбор, какой из них использовать, если вообще, в значительной степени зависит от стиля программирования.
Оператор return возвращает управление вызывающей стороне метода (§8.4, §15.12) или конструктора (§8.8, §15.9).
Существуют два вида оператора return:
-
Оператор
returnбез значения. -
Оператор
returnсо значением Выражение.
Оператор return пытается передать управление вызывающей стороне ближайшего включающего конструктора, метода или лямбда-выражения; это включающее объявление или выражение называется целью return. В случае оператора return со значением Выражение, значение Выражения становится значением вызова.
Если у оператора return нет цели return, это ошибка компиляции.
Это ошибка компиляции, если цель return содержит (i) инициализатор экземпляра или статический инициализатор, который включает оператор return, или (ii) выражение switch, которое включает оператор return.
Это ошибка компиляции, если целью return оператора return без значения является метод, и этот метод не объявлен void.
Это ошибка компиляции, если целью return оператора return со значением Выражение является конструктор или метод, который объявлен void.
Это ошибка компиляции, если целью return оператора return со значением Выражение является метод с объявленным типом возврата T, а тип Выражения не совместим для присваивания (§5.2) с T.
Выполнение оператора return без значения всегда завершается внезапно, по причине return без значения.
Выполнение оператора return со значением Выражение сначала вычисляет Выражение. Если вычисление Выражения завершается внезапно по какой-либо причине, то и оператор return завершается по этой причине. Если вычисление Выражения завершается нормально, произведя значение V, то оператор return завершается внезапно, по причине return со значением V.
Таким образом, оператор return всегда завершается внезапно.
В предыдущих описаниях используется "пытается передать управление", а не просто "передаёт управление", потому что если есть операторы try (§14.20) внутри метода или конструктора, блоки try или клаузы catch которых содержат оператор return, то клаузы finally этих операторов try будут выполнены в порядке от вложенного к внешнему, прежде чем управление передаётся вызывающей стороне метода или конструктора. Внезапное завершение клаузы finally может нарушить передачу управления, начатую оператором return.
Оператор throw вызывает исключение (§11 (Исключения)) для обработки. Результатом является немедленный перенос управления (§11.3), который может выйти из нескольких операторов и нескольких инициализаций конструкторов, инициализаций экземпляров, статических инициализаций и инициализаций полей, а также вызовов методов, пока не будет найден оператор try (§14.20), который перехватывает сгенерированное значение. Если такой оператор try не найден, выполнение потока (§17 (Потоки и блокировки)), который выполнил оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит этот поток.
Выражение Выражение в операторе throw должно обозначать переменную или значение ссылочного типа, присваиваемое (§5.2) типу Throwable, или обозначать нулевую ссылку; в противном случае возникает ошибка компиляции.
Ссылочный тип Выражение всегда будет типом класса (так как типы интерфейсов не присваиваются типу Throwable), который не параметризован (так как подкласс Throwable не может быть обобщённым (§8.1.2)).
Должно выполняться хотя бы одно из следующих трёх условий, иначе возникает ошибка компиляции:
-
Тип Выражение — это класс необрабатываемого исключения (§11.1.1) или тип null (§4.1).
-
Оператор
throwнаходится внутри блокаtryоператораtry(§14.20), и операторtryне может выбросить исключение типа Выражение. (В этом случае мы говорим, что выброшенное значение перехвачено операторомtry). -
Оператор
throwнаходится внутри объявления метода или конструктора, и тип Выражение присваивается (§5.2) хотя бы одному типу, указанному в предложенииthrows(§8.4.6, §8.8.5) объявления.
Типы исключений, которые может выбросить оператор throw, указаны в §11.2.2.
Оператор throw сначала вычисляет Выражение. Затем:
-
Если вычисление Выражение завершается прерыванием по какой-либо причине, то оператор
throwзавершается прерыванием по этой причине. -
Если вычисление Выражение завершается нормально, генерируя ненулевое значение
V, то операторthrowзавершается прерыванием, причиной которого является исключениеthrowсо значениемV. -
Если вычисление Выражение завершается нормально, генерируя нулевое значение, то создаётся экземпляр
V'классаNullPointerExceptionи выбрасывается вместоnull. Операторthrowзавершается прерыванием, причиной которого является исключениеthrowсо значениемV'.
Таким образом, оператор throw всегда завершается прерыванием.
Если существуют какие-либо окружающие операторы try (§14.20), блоки try которых содержат оператор throw, то любые предложения finally этих операторов try будут выполнены по мере передачи управления наружу, пока выброшенное значение не будет перехвачено. Обратите внимание, что прерывание выполнения предложения finally может нарушить передачу управления, начатую оператором throw.
Если оператор throw содержится в объявлении метода или лямбда-выражении, но его значение не перехвачено каким-либо оператором try, который его содержит, то вызов метода завершается прерыванием из-за исключения throw.
Если оператор throw содержится в объявлении конструктора, но его значение не перехвачено каким-либо оператором try, который его содержит, то выражение создания экземпляра класса, вызвавшее конструктор, завершится прерыванием из-за исключения throw (§15.9.4).
Если оператор throw содержится в статической инициализации (§8.7), то проверка во время компиляции (§11.2.3) гарантирует, что его значение всегда является необрабатываемым исключением или его значение всегда перехватывается каким-либо оператором try, который его содержит. Если во время выполнения, несмотря на эту проверку, значение не перехвачено каким-либо оператором try, содержащим оператор throw, то значение повторно выбрасывается, если это экземпляр класса Error или одного из его подклассов; в противном случае оно оборачивается в объект ExceptionInInitializerError, который затем выбрасывается (§12.4.2).
Если оператор throw содержится в инициализации экземпляра (§8.6), то проверка во время компиляции (§11.2.3) гарантирует, что либо его значение всегда является необрабатываемым исключением, либо его значение всегда перехватывается каким-либо оператором try, содержащим его, либо тип выброшенного исключения (или один из его суперклассов) указан в предложении throws каждого конструктора класса.
По соглашению, объявленные пользователем типы исключений обычно должны быть подклассами класса Exception, который является подклассом класса Throwable (§11.1.1).
Оператор `synchronized` приобретает блокировку взаимного исключения (§17.1) от имени выполняющейся нити, выполняет блок, а затем освобождает блокировку. Пока выполняющаяся нить владеет блокировкой, никакая другая нить не может ее получить.
Тип Выражения должен быть ссылочным типом, в противном случае возникает ошибка компиляции.
Оператор `synchronized` выполняется путём сначала вычисления Выражения. Затем:
-
Если вычисление Выражения прерывается по какой-либо причине, то оператор `synchronized` прерывается по той же причине.
-
В противном случае, если значение Выражения равно
null, выбрасывается исключениеNullPointerException. -
В противном случае, пусть не-
nullзначение Выражения будетV. Выполняющаяся нить блокирует монитор, связанный сV. Затем выполняется Блок, а затем есть выбор:-
Если выполнение Блока завершается нормально, то монитор разблокируется, и оператор `synchronized` завершается нормально.
-
Если выполнение Блока прерывается по любой причине, то монитор разблокируется, и оператор `synchronized` завершается прерыванием по той же причине.
-
Блокировки, приобретаемые операторами `synchronized`, совпадают с теми, которые неявно приобретаются методами `synchronized` (§8.4.3.6). Одна нить может приобретать блокировку более одного раза.
Приобретение блокировки, связанной с объектом, само по себе не препятствует другим нитям обращаться к полям объекта или вызывать необязательные методы объекта. Другие нити также могут использовать методы или оператор `synchronized` обычным образом для достижения взаимного исключения.
Пример 14.19-1. Оператор `synchronized`
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
Эта программа выводит:
made it!
Обратите внимание, что эта программа зависнет, если одной нити не разрешено блокировать монитор более одного раза.
Оператор try выполняет блок кода. Если происходит выбрасывание значения, и оператор try содержит один или несколько блоков catch, способных перехватить это значение, то управление передаётся первому такому блоку catch. Если оператор try содержит блок finally, то выполняется другой блок кода, независимо от того, завершился ли блок try нормально или прервано, и независимо от того, был ли блок catch обработан в первую очередь.
См. §8.3 для UnannClassType. Следующие правила из §4.3, §8.3 и §8.4.1 приведены здесь для удобства:
Блок Block сразу после ключевого слова try называется блоком try оператора try.
Блок Block сразу после ключевого слова finally называется блоком finally оператора try.
Оператор try может содержать блоки catch, также называемые обработчиками исключений.
Блок catch объявляет ровно один параметр, который называется параметром исключения.
Если final появляется более одного раза как модификатор для объявления параметра исключения, то это ошибка компиляции.
Область действия и перекрытие параметра исключения задаются в §6.3 и §6.4.
Ссылки на параметр исключения из вложенного класса или интерфейса, или лямбда-выражения ограничены, как указано в §6.5.6.1.
Параметр исключения может обозначать свой тип как единственный тип класса или объединение двух или более типов классов (называемые альтернативами). Альтернативы объединения разделяются синтаксически |.
Блок catch, в котором параметр исключения обозначен как единственный тип класса, называется однотипным блоком перехвата.
Блок catch, в котором параметр исключения обозначен как объединение типов, называется многотипным блоком перехвата.
Каждый тип класса, используемый в обозначении типа параметра исключения, должен быть классом Throwable или подклассом Throwable, в противном случае произойдёт ошибка компиляции.
Использование переменной типа в обозначении типа параметра исключения является ошибкой компиляции.
Объединение типов содержит две альтернативы Di и Dj (i ≠ j), где Di является подтипом Dj (§4.10.2), является ошибкой компиляции.
Объявленный тип параметра исключения, обозначающего его тип одним классом, — это тип класса.
Объявленный тип параметра исключения, обозначающего его тип как объединение альтернатив D1 | D2 | ... | Dn, — это lub(D1, D2, ..., Dn).
Параметр исключения многотипного блока перехвата неявно объявлен final, если не объявлен явно final.
Если параметр исключения, неявно или явно объявленный final, присваивается в теле блока catch, это ошибка компиляции.
Параметр исключения однотипного блока перехвата никогда неявно объявляется final, но может быть явно объявлен final или являться эффективно final (§4.12.4).
Неявно final параметр исключения является final по своему определению, в то время как эффективно final — параметр является (как бы) final из-за того, как он используется. Параметр исключения многотипного блока перехвата неявно объявлен final, поэтому он никогда не будет левым операндом оператора присваивания, но он не считается эффективно final.
Если параметр исключения эффективно final (в однотипном блоке перехвата) или неявно final (в многотипном блоке перехвата), добавление явного модификатора final к его объявлению не приведёт к ошибкам компиляции. С другой стороны, если параметр исключения однотипного блока перехвата объявлен явно final, то удаление модификатора final может привести к ошибкам компиляции, потому что параметр исключения, теперь считающийся эффективно final, больше не может быть использован в декларациях анонимных и локальных классов в теле блока catch. Если ошибок компиляции нет, то программу можно изменить так, чтобы параметр исключения был повторно присвоен в теле блока catch, и таким образом он больше не будет считаться эффективно final.
Типы исключений, которые может вызвать оператор try, указаны в §11.2.2.
Взаимосвязь между исключениями, которые выбрасывает блок try оператора try, и блоками перехвата catch (если таковые имеются) оператора try описана в §11.2.3.
Обработчики исключений рассматриваются в порядке следования слева направо: самый ранний возможный блок catch перехватывает исключение, получая в качестве аргумента объект выброшенного исключения, как указано в §11.3.
Многоточечный catch можно рассматривать как последовательность одноточечных catch. То есть, catch, где тип параметра исключения обозначается как объединение D1|D2|...|Dn эквивалентно последовательности из n catch, где типы параметров исключения — классовые типы D1, D2, ..., Dn соответственно. В блоке каждой из n catch объявленный тип параметра исключения равен lub(D1, D2, ..., Dn). Например, следующий код:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
семантически эквивалентен следующему коду:
try {
... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
final ReflectiveOperationException ex = ex1;
... body ...
}
catch (final IllegalAccessException ex2) {
final ReflectiveOperationException ex = ex2;
... body ...
}
где многоточечный catch с двумя альтернативами был преобразован в две одноточечные catch, по одной для каждой альтернативы. Компилятор Java не обязан и не рекомендуется компилировать многоточечный catch, дублируя код таким образом, поскольку можно представить многоточечный catch в файле class без дублирования.
finally гарантирует, что finally блок выполняется после try блока и любого catch блока, который может быть выполнен, независимо от того, как управление покидает try блок или catch блок. Обработка finally блока довольно сложна, поэтому два случая оператора try с и без finally блока описываются отдельно (§14.20.1, §14.20.2).
Оператор try может опустить catch блоки и finally блок, если это оператор try-с-ресурсами (§14.20.3).
Оператор try без finally блока выполняется путём сначала выполнения try блока. Затем возникает выбор:
-
Если выполнение
tryблока завершается нормально, то никаких дальнейших действий не предпринимается, и операторtryзавершается нормально. -
Если выполнение
tryблока завершается прерывисто из-заthrowзначенияV, то возникает выбор:-
Если тип времени выполнения
Vсовместим по присваиванию (§5.2) с любым из класса исключений, которые можно перехватить, в любомcatchблоке оператораtry, то выбирается первый (левый) такойcatchблок. ЗначениеVприсваивается параметру выбранногоcatchблока, и выполняется блок этогоcatchблока, после чего возникает выбор:-
Если этот блок завершается нормально, то оператор
tryзавершается нормально. -
Если этот блок завершается прерывисто по любой причине, то оператор
tryзавершается прерывисто по той же причине.
-
-
Если тип времени выполнения
Vне совместим по присваиванию с классом любого исключения, которое можно перехватить, в любом изcatchблоков оператораtry, то операторtryзавершается прерывисто из-заthrowзначенияV.
-
-
Если выполнение
tryблока завершается прерывисто по любой другой причине, то операторtryзавершается прерывисто по той же причине.
Пример 14.20.1-1. Перехват исключения
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет два catch блока. Тип времени выполнения исключения — BlewIt, который не присваивается переменной типа RuntimeException, но присваивается переменной типа BlewIt, поэтому вывод примера:
Caught BlewIt
Оператор try с блоком finally выполняется путём сначала выполнения блока try. Затем происходит выбор:
-
Если выполнение блока
tryзавершается нормально, то выполняется блокfinally, и затем происходит выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается прерывисто по причинеS, то операторtryзавершается прерывисто по причинеS.
-
-
Если выполнение блока
tryзавершается прерывисто из-за исключенияthrowзначенияV, то происходит выбор:-
Если тип времени выполнения
Vсовместим по присваиванию с классом обрабатываемого исключения любого блокаcatchоператораtry, то выбирается первый (самый левый) такой блокcatch. ЗначениеVприсваивается параметру выбранного блокаcatch, и выполняется блок Block этого блокаcatch. Затем происходит выбор:-
Если блок
catchзавершается нормально, то выполняется блокfinally. Затем происходит выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается прерывисто по любой причине, то операторtryзавершается прерывисто по той же причине.
-
-
Если блок
catchзавершается прерывисто по причинеR, то выполняется блокfinally. Затем происходит выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается прерывисто по причинеR. -
Если блок
finallyзавершается прерывисто по причинеS, то операторtryзавершается прерывисто по причинеS(и причинаRотбрасывается).
-
-
-
Если тип времени выполнения
Vне совместим по присваиванию с классом обрабатываемого исключения любого блокаcatchоператораtry, то выполняется блокfinally. Затем происходит выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается прерывисто из-за исключенияthrowзначенияV. -
Если блок
finallyзавершается прерывисто по причинеS, то операторtryзавершается прерывисто по причинеS(и исключениеthrowзначенияVотбрасывается и забывается).
-
-
-
Если выполнение блока
tryзавершается прерывисто по любой другой причинеR, то выполняется блокfinally, и затем происходит выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается прерывисто по причинеR. -
Если блок
finallyзавершается прерывисто по причинеS, то операторtryзавершается прерывисто по причинеS(и причинаRотбрасывается).
-
Пример 14.20.2-1. Обработка неперехваченного исключения с finally
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt {
throw new NullPointerException();
}
public static void main(String[] args) {
try {
blowUp();
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
} finally {
System.out.println("Uncaught Exception");
}
}
}
Эта программа выводит:
Uncaught Exception
Exception in thread "main" java.lang.NullPointerException
at Test.blowUp(Test.java:7)
at Test.main(Test.java:11)
Исключение NullPointerException (которое является видом RuntimeException), сгенерированное методом blowUp, не перехватывается оператором try в main, так как NullPointerException не может быть присвоено переменной типа BlewIt. Это вызывает выполнение блока finally, после чего поток, выполняющий main, который является единственным потоком тестовой программы, завершается из-за неперехваченного исключения, что обычно приводит к выводу имени исключения и краткому отладочному сообщению. Однако в этом спецификации не требуется отладочного сообщения.
Проблема с требованием отладочного сообщения заключается в том, что исключение может быть создано в одной точке программы и брошено в другой. Сохранение стека вызовов в исключении является очень ресурсоёмким, если оно не было реально брошено (в этом случае сообщение может быть сгенерировано при разворачивании стека). Поэтому мы не требуем отладочного сообщения в каждом исключении.
Оператор try-with-resources параметризуется переменными (известными как ресурсы), которые инициализируются до выполнения блока try и автоматически закрываются в обратном порядке инициализации после выполнения блока try. Обычно, когда ресурсы закрываются автоматически, необязательны блоки catch и finally.
Следующие правила из §4.3, §8.3, §8.4.1 и §14.4 приведены здесь для удобства:
См. §8.3 для UnannType.
Спецификация ресурсов определяет ресурсы оператора try-with-resources, либо объявляя локальные переменные с инициализаторами, либо ссылаясь на существующие переменные. Существующая переменная указывается именем выражения (§6.5.6) или выражением доступа к полю (§15.11).
Правила объявленной локальной переменной в спецификации ресурсов указаны в §14.4. Кроме того, должны быть выполнены все следующие условия, в противном случае возникает ошибка компиляции:
-
Список объявления переменной (VariableDeclaratorList) состоит из одной переменной (VariableDeclarator).
-
Переменная (VariableDeclarator) имеет инициализатор.
-
Идентификатор объявления переменной (VariableDeclaratorId) не содержит квадратных скобок.
Область видимости и перекрытие локальной переменной, объявленной в спецификации ресурсов, указаны в §6.3 и §6.4.
Доступ к локальной переменной из вложенного класса или интерфейса, или лямбда-выражения, ограничен, как указано в §6.5.6.1.
Тип локальной переменной, объявленной в спецификации ресурсов, указан в §14.4.1.
Тип локальной переменной, объявленной в спецификации ресурсов, или тип существующей переменной, на которую ссылается спецификация ресурсов, должен быть подтипом AutoCloseable, в противном случае произойдет ошибка компиляции.
Объявление двух локальных переменных с одинаковым именем в спецификации ресурсов является ошибкой компиляции.
Обратите внимание, что спецификация ресурсов может объявлять более одной безымянной локальной переменной (§6.1).
Ресурсы являются final, в том числе:
-
Локальная переменная, объявленная в спецификации ресурсов, неявно объявляется
final, если она не объявлена явноfinal(§4.12.4). -
Существующая переменная, на которую ссылается спецификация ресурсов, должна быть
finalили эффективноfinalпеременной, которая определенно присваивается до оператора try-with-resources (§16 (Определённое присвоение)), в противном случае произойдет ошибка компиляции.
Ресурсы инициализируются слева направо. Если ресурс не может быть инициализирован (то есть, его выражение-инициализатор выбрасывает исключение), то все ресурсы, инициализированные до этого оператором try-with-resources, закрываются. Если все ресурсы были инициализированы успешно, блок try выполняется обычным образом, а затем все не нулевые ресурсы оператора try-with-resources закрываются.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он инициализирован ненулевым значением. Исключение при закрытии одного ресурса не препятствует закрытию других ресурсов. Такое исключение считается подавляемым если ранее было выброшено исключение инициализатором, блоком try или закрытием другого ресурса.
Оператор try-with-resources, в спецификации которого указано несколько ресурсов, обрабатывается так, как если бы это были несколько вложенных операторов try-with-resources, каждый из которых содержит спецификацию одного ресурса. При переводе оператора try-with-resources с n ресурсами (n > 1) результатом является оператор try-with-resources с n-1 ресурсами. После n таких переводов, получаем n вложенных операторов try-with-resources, и весь перевод завершен.
A try-with-resources statement with no catch clauses or finally clause is called a basic try-with-resources statement.
If a basic try-with-resources statement is of the form:
try (VariableAccess ...)
Block
then the resource is first converted to a local variable declaration by the following translation:
try (T #r = VariableAccess ...) {
Block
}
T is the type of the variable denoted by VariableAccess and #r is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs. The try-with-resources statement is then translated according to the rest of this section.
The meaning of a basic try-with-resources statement of the form:
try ({VariableModifier} R VariableDeclaratorId = Expression ...) Block
is given by the following translation to a local variable declaration and a try-catch-finally statement:
{
final {VariableModifierNoFinal} R Identifier = Expression;
Throwable #primaryExc = null;
try ResourceSpecification_tail
Block
catch (Throwable #t) {
#primaryExc = #t;
throw #t;
} finally {
if (Identifier != null) {
if (#primaryExc != null) {
try {
Identifier.close();
} catch (Throwable #suppressedExc) {
#primaryExc.addSuppressed(#suppressedExc);
}
} else {
Identifier.close();
}
}
}
}
{VariableModifierNoFinal} is defined as {VariableModifier} without final, if present.
If VariableDeclaratorId is an identifier then Identifier is defined to be that identifier, otherwise Identifier is defined to be an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs.
#t, #primaryExc, and #suppressedExc are automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs.
If the resource specification indicates one resource, then ResourceSpecification_tail is empty (and the try-catch-finally statement is not itself a try-with-resources statement).
If the resource specification indicates n > 1 resources, then ResourceSpecification_tail consists of the 2nd, 3rd, ..., n'th resources indicated in the resource specification, in the same order (and the try-catch-finally statement is itself a try-with-resources statement).
Reachability and definite assignment rules for the basic try-with-resources statement are implicitly specified by the translation above.
In a basic try-with-resources statement that manages a single resource:
-
If the initialization of the resource completes abruptly because of a
throwof a valueV, then thetry-with-resources statement completes abruptly because of athrowof the valueV. -
If the initialization of the resource completes normally, and the
tryblock completes abruptly because of athrowof a valueV, then:-
If the automatic closing of the resource completes normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closing of the resource completes abruptly because of a
throwof a valueV2, then thetry-with-resources statement completes abruptly because of athrowof the valueV, withV2added to the suppressed exception list ofV.
-
-
If the initialization of the resource completes normally, and the
tryblock completes normally, and the automatic closing of the resource completes abruptly because of athrowof a valueV, then thetry-with-resources statement completes abruptly because of athrowof the valueV.
In a basic try-with-resources statement that manages multiple resources:
-
If the initialization of a resource completes abruptly because of a
throwof a valueV, then:-
If the automatic closings of all successfully initialized resources (possibly zero) complete normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closings of all successfully initialized resources (possibly zero) complete abruptly because of
throws of valuesV1...Vn, then thetry-with-resources statement completes abruptly because of athrowof the valueV, with any remaining valuesV1...Vnadded to the suppressed exception list ofV.
-
-
If the initialization of all resources completes normally, and the
tryblock completes abruptly because of athrowof a valueV, then:-
If the automatic closings of all initialized resources complete normally, then the
try-with-resources statement completes abruptly because of athrowof the valueV. -
If the automatic closings of one or more initialized resources complete abruptly because of
throws of valuesV1...Vn, then thetry-with-resources statement completes abruptly because of athrowof the valueV, with any remaining valuesV1...Vnadded to the suppressed exception list ofV.
-
-
If the initialization of every resource completes normally, and the
tryblock completes normally, then:-
If one automatic closing of an initialized resource completes abruptly because of a
throwof valueV, and all other automatic closings of initialized resources complete normally, then thetry-with-resources statement completes abruptly because of athrowof the valueV. -
If more than one automatic closing of an initialized resource completes abruptly because of
throws of valuesV1...Vn(whereV1is the exception from the rightmost resource failing to close andVnis the exception from the leftmost resource failing to close), then thetry-with-resources statement completes abruptly because of athrowof the valueV1, with any remaining valuesV2...Vnadded to the suppressed exception list ofV1.
-
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.
Оператор yield передает управление, вызывая выполнение окружающего выражения switch (§15.28), для выдачи указанного значения.
Оператор 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;
}
};
}
}
Ошибка компиляции, если оператор не может быть выполнен, потому что он недостижим.
Этот раздел посвящен точному объяснению слова "достижимый". Идея заключается в том, что должен существовать некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, к самому оператору. Анализ учитывает структуру операторов. За исключением специального обращения с операторами while, do и for, условное выражение которых имеет константное значение true, значения выражений не учитываются в анализе потока.
Например, компилятор Java примет код:
{
int n = 5;
while (n > 7) k = 2;
}
даже если значение n известно во время компиляции, и в принципе известно, что присвоение k никогда не будет выполнено.
Правила в этом разделе определяют два технических термина:
-
является ли оператор достижимым
-
может ли оператор завершиться нормально
Правила допускают нормальное завершение оператора только если он достижим.
Используются два дополнительных технических термина:
-
Достижимый оператор
breakвыходит из оператора, если внутри точки разрыва нет операторовtry, блоки которых содержат операторbreak, или существуют операторыtry, блоки которых содержат операторbreak, и все операторыfinallyэтих операторовtryмогут завершиться нормально.Это определение основано на логике "попытки передачи управления" в §14.15.
-
Оператор
continueпродолжает операторdo, если внутри оператораdoнет операторовtry, блоки которых содержат операторcontinue, или существуют операторыtry, блоки которых содержат операторcontinue, и все операторыfinallyэтих операторовtryмогут завершиться нормально.
Правила следующие:
-
The block that is the body of a constructor, method, instance initializer, static initializer, lambda expression, or
switchexpression is reachable. -
An empty block that is not a switch block can complete normally iff it is reachable.
A non-empty block that is not a switch block can complete normally iff the last statement in it can complete normally.
The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.
Every other statement
Sin a non-empty block that is not a switch block is reachable iff the statement precedingScan complete normally. -
A local class declaration statement can complete normally iff it is reachable.
-
A local variable declaration statement can complete normally iff it is reachable.
-
An empty statement can complete normally iff it is reachable.
-
A labeled statement can complete normally if at least one of the following is true:
-
The contained statement can complete normally.
-
There is a reachable
breakstatement that exits the labeled statement.
The contained statement is reachable iff the labeled statement is reachable.
-
-
An expression statement can complete normally iff it is reachable.
-
An
if-thenstatement can complete normally iff it is reachable.The
then-statement is reachable iff theif-thenstatement is reachable.An
if-then-elsestatement can complete normally iff thethen-statement can complete normally or theelse-statement can complete normally.The
then-statement is reachable iff theif-then-elsestatement is reachable.The
else-statement is reachable iff theif-then-elsestatement is reachable.This handling of an
ifstatement, whether or not it has anelsepart, is rather unusual. The rationale is given at the end of this section. -
An
assertstatement can complete normally iff it is reachable. -
A
switchstatement whose switch block is empty, or contains only switch labels, can complete normally. -
A
switchstatement whose switch block consists of switch labeled statement groups can complete normally iff at least one of the following is true:-
The last statement in the switch block can complete normally.
-
There is at least one switch label after the last switch block statement group.
-
There is a reachable
breakstatement that exits theswitchstatement. -
The
switchstatement is not enhanced (§14.11.2) and its switch block does not contain adefaultlabel.
-
-
A
switchstatement whose switch block consists of switch rules can complete normally iff at least one of the following is true:-
One of the switch rules introduces a switch rule expression (which is necessarily a statement expression).
-
One of the switch rules introduces a switch rule block that can complete normally.
-
One of the switch rules introduces a switch rule block that contains a reachable
breakstatement which exits theswitchstatement. -
The
switchstatement is not enhanced (§14.11.2) and its switch block does not contain adefaultlabel.
-
-
A switch block is reachable iff its
switchstatement is reachable. -
A statement in a switch block that consists of switch labeled statement groups is reachable iff the switch block is reachable and at least one of the following is true:
-
It bears a
caseordefaultlabel. -
There is a statement preceding it in the switch block and that preceding statement can complete normally.
-
-
A switch rule block in a switch block is reachable iff the switch block is reachable.
-
A switch rule
throwstatement in a switch block is reachable iff the switch block is reachable. -
A
whilestatement can complete normally iff at least one of the following is true:-
The
whilestatement is reachable and the condition expression is not a constant expression (§15.29) with valuetrue. -
There is a reachable
breakstatement that exits thewhilestatement.
The contained statement is reachable iff the
whilestatement is reachable and the condition expression is not a constant expression whose value isfalse. -
-
A
dostatement can complete normally iff at least one of the following is true:-
The contained statement can complete normally and the condition expression is not a constant expression (§15.29) with value
true. -
The
dostatement contains a reachablecontinuestatement with no label, and thedostatement is the innermostwhile,do, orforstatement that contains thatcontinuestatement, and thecontinuestatement continues thatdostatement, and the condition expression is not a constant expression with valuetrue. -
The
dostatement contains a reachablecontinuestatement with labelL, and thedostatement has labelL, and thecontinuestatement continues thatdostatement, and the condition expression is not a constant expression with valuetrue. -
There is a reachable
breakstatement that exits thedostatement.
The contained statement is reachable iff the
dostatement is reachable. -
-
A basic
forstatement can complete normally iff at least one of the following is true:-
The
forstatement is reachable, there is a condition expression, and the condition expression is not a constant expression (§15.29) with valuetrue. -
There is a reachable
breakstatement that exits theforstatement.
The contained statement is reachable iff the
forstatement is reachable and the condition expression is not a constant expression whose value isfalse. -
-
An enhanced
forstatement can complete normally iff it is reachable. -
A
break,continue,return,throw, oryieldstatement cannot complete normally. -
A
synchronizedstatement can complete normally iff the contained statement can complete normally.The contained statement is reachable iff the
synchronizedstatement is reachable. -
A
trystatement can complete normally iff both of the following are true:-
The
tryblock can complete normally or anycatchblock can complete normally. -
If the
trystatement has afinallyblock, then thefinallyblock can complete normally.
-
-
The
tryblock is reachable iff thetrystatement is reachable.
-
A
catchblockCможет быть достигнут, если оба следующих условия истинны:-
Или тип параметра
Cявляется типом исключения, которое не проверяется, или типомException, или его суперклассом, или некоторым выражением или инструкциейthrowв блокеtry, который может вызвать проверяемое исключение, тип которого совместим по присваиванию (§5.2) с типом параметраC. (Выражение достижимо, если самая внутренняя инструкция, содержащая его, достижима.)См. §15.6 для нормального и прерывистого завершения выражений.
-
Нет предыдущего блока
catchAв инструкцииtry, такой, что тип параметраCсовпадает или является подклассом типа параметраA.
-
-
Блок Block блока
catchдостижим, если достижим блокcatch. -
Если блок
finallyприсутствует, он достижим, если достижима инструкцияtry.
Можно было бы ожидать, что инструкция if обрабатывается следующим образом:
-
Инструкция
if-thenможет завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Инструкция
if-thenдостижима, и выражение условия не является константным выражением, значение которого равноtrue. -
Инструкция
thenможет завершиться нормально.
Инструкция
thenдостижима, если инструкцияif-thenдостижима, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Инструкция
if-then-elseможет завершиться нормально, если инструкцияthenили инструкцияelseможет завершиться нормально.Инструкция
thenдостижима, если инструкцияif-then-elseдостижима, и выражение условия не является константным выражением, значение которого равноfalse.Инструкция
elseдостижима, если инструкцияif-then-elseдостижима, и выражение условия не является константным выражением, значение которого равноtrue.
Этот подход был бы согласован с обработкой других управляющих структур. Однако, чтобы разрешить использовать инструкцию if для удобной «условной компиляции», фактические правила отличаются.
Например, следующая инструкция приводит к ошибке компиляции:
while (false) { x=3; }
потому что инструкция x=3; недостижима; но по внешнему виду аналогичный случай:
if (false) { x=3; }
не приводит к ошибке компиляции. Оптимизирующая компилятор может понять, что инструкция x=3; никогда не будет выполнена, и может выбрать опустить код этой инструкции из сгенерированного файла class, но инструкция x=3; не считается «недостижимой» в техническом смысле, описанном здесь.
Обоснование этого различающегося обращения заключается в том, чтобы позволить программистам определять переменные «флагов», такие как:
static final boolean DEBUG = false;
а затем писать код, такой как:
if (DEBUG) { x=3; }
Идея заключается в том, что можно изменить значение DEBUG с false на true или с true на false, а затем скомпилировать код без других изменений в тексте программы.
Условные компиляции есть свой нюанс. Если набор классов, использующих переменную «флаг» — или точнее, любую переменную константу static (§4.12.4) — компилируется, и условный код опускается, то позже недостаточно просто распространить новую версию класса или интерфейса, содержащего определение флага. Классы, которые используют флаг, не увидят его нового значения, поэтому их поведение может быть неожиданным. По существу, изменение значения флага двоично совместимо с существующими бинарными файлами (не происходит LinkageError), но не совместимо по поведению.
Еще одна причина «встраивания» значений переменной константы static связана с инструкциями switch. Они единственный вид инструкций, которые зависят от константных выражений, а именно, что каждый метка case инструкции switch должна быть константным выражением, значение которого отличается от любого другого метки case. Метки case часто являются ссылками на переменные константы static, поэтому не сразу очевидно, что все метки имеют разные значения. Если доказано, что дубликатов меток нет на этапе компиляции, то встраивание значений в файл class гарантирует отсутствие дубликатов меток и во время выполнения — очень желательное свойство.
Пример 14.22-1. Условная компиляция
Если пример:
class Flags { static final boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
скомпилируется и выполнится, он выведет:
DEBUG is true
Предположим, что создана новая версия класса Flags:
class Flags { static final boolean DEBUG = false; }
Если Flags перекомпилируется, но не Test, то при запуске нового двоичного файла с существующим двоичным файлом Test выводится:
DEBUG is true
потому что DEBUG — это переменная константы static, поэтому ее значение может использоваться при компиляции Test без ссылки на класс Flags.
Это поведение также произойдет, если Flags был интерфейсом, как в изменённом примере:
interface Flags { boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
Поскольку поля интерфейсов всегда являются static и final, рекомендуется, чтобы только константные выражения присваивались полям интерфейсов. Заметим, но не рекомендуем, что если поле примитивного типа интерфейса может изменяться, его значение можно выразить следующим образом:
interface Flags {
boolean debug = Boolean.valueOf(true).booleanValue();
}
убеждаясь, что это значение не является константным выражением. Существуют аналогичные выражения для других примитивных типов.
Шаблон описывает тест, который можно выполнить со значением. Шаблоны появляются в качестве операндов инструкций и выражений, которые предоставляют значения для проверки. Шаблоны объявляют ноль или более локальных переменных, также известных как переменные шаблона.
Процесс проверки значения на соответствие шаблону называется сопоставлением шаблонов. Если значение успешно соответствует шаблону, процесс сопоставления шаблонов инициализирует переменные шаблона, если таковые объявлены шаблоном.
Переменные шаблона находятся в области действия (§6.3), только там, где успешно выполняется сопоставление шаблонов, а значит, переменные шаблона были инициализированы. Использовать переменную шаблона, которая не была инициализирована, невозможно.
Шаблон типа используется для проверки, является ли значение экземпляром типа, указанного в шаблоне. Шаблон записи используется для проверки, является ли значение экземпляром типа класса записи, и, если это так, для рекурсивного применения сопоставления шаблонов к значениям компонентов записи.
_ Ниже приведены следующие производства из §4.3, §8.3, §8.4.1 и §14.4 для удобства:
См. §8.3 для ТипБезАннотаций.
Шаблон вложен в шаблоне записи, если (1) он появляется непосредственно в списке компонентных шаблонов шаблона записи, или (2) он вложен в шаблоне записи, который появляется непосредственно в списке компонентных шаблонов шаблона записи. Шаблон главный, если он не вложен в шаблон записи.
Шаблон типа объявляет одну локальную переменную, известную как переменная шаблона. Если объявление включает идентификатор, то это указывает имя переменной шаблона, в противном случае переменная шаблона называется безымянной переменной шаблона.
Правила локальной переменной, объявленной в шаблоне типа, указаны в §14.4. Кроме того, должны быть выполнены все следующие условия, в противном случае возникает ошибка компиляции:
-
ТипЛокальнойПеременной в главном шаблоне типа обозначает тип ссылки (и, более того, не является
var). -
СписокИнициализаторовПеременных состоит из одного ИнициализатораПеременной.
-
У ИнициализатораПеременной нет инициализатора.
-
У ИдентификаторИнициализатора нет пар скобок.
Тип переменной шаблона, объявленной в главном шаблоне типа, является типом ссылки, обозначаемым ТипЛокальнойПеременной.
Тип переменной шаблона, объявленной во вложенном шаблоне типа, определяется следующим образом:
-
Если ТипЛокальнойПеременной является ТипБезАннотаций, то тип переменной шаблона обозначается ТипБезАннотаций.
-
Если ТипЛокальнойПеременной является
var, то шаблон типа должен появиться непосредственно в списке компонентных шаблонов шаблона записи, в противном случае возникает ошибка компиляции.Пусть R — тип шаблона записи, а T — тип соответствующего компонента поля в R (§8.10.3). Тип переменной шаблона — это проекция вверх T по отношению ко всем синтетическим типам, упомянутым в T.
Рассмотрим следующее объявление класса записи:
record R<T>(ArrayList<T> a){}В данном случае шаблон записи
R<String>(var b), тип переменной шаблонаbсоставляетArrayList<String>.
Шаблон типа называется сопоставлением с null, если он появляется непосредственно в списке компонентных шаблонов шаблона записи типа R, где соответствующий компонент записи R имеет тип U, и шаблон типа является безусловным для типа U (§14.30.3).
Обратите внимание, что это свойство шаблонов типов во время компиляции используется в процессе сопоставления шаблонов во время выполнения (§14.30.2), поэтому оно ассоциируется с шаблоном типа для использования во время выполнения.
Шаблон записи состоит из ТипСсылка и списка компонентных шаблонов, содержащих компонентные шаблоны (если таковые имеются). Если ТипСсылка не является типом класса записи (§8.10), то возникает ошибка компиляции.
Если ТипСсылка является сырым типом, то тип шаблона записи определяется неявно, как описано в §18.5.5. Возникает ошибка компиляции, если тип шаблона записи нельзя определить неявно.
Если ТипСсылка (или любая его часть) аннотирована, возникает ошибка компиляции.
В будущих версиях Java Programming Language это ограничение на аннотации может быть снято.
В противном случае, тип шаблона записи — ТипСсылка.
Длина списка компонентных шаблонов шаблона записи должна быть такой же, как длина списка компонентов в объявлении класса записи, указанного ТипСсылка, в противном случае возникает ошибка компиляции.
Шаблон записи напрямую не объявляет переменные шаблонов, но может содержать объявления переменных шаблонов в списке компонентных шаблонов.
Ошибка компиляции возникает, если шаблон записи содержит более одного объявления переменной шаблона с одинаковым именем.
Шаблон сопоставления всех — это специальный шаблон, который не объявляет переменные шаблонов и может появиться только непосредственно в списке компонентных шаблонов шаблона записи r.
Пусть R — тип шаблона записи r, а T — тип соответствующего компонента поля в R (§8.10.3). Тип шаблона сопоставления всех — это проекция вверх T по отношению ко всем синтетическим типам, упомянутым в T.
Можно заметить, что шаблон сопоставления всему соответствует вложенному шаблону типа, который объявляет переменную шаблона без имени, и у которой LocalVariableType — var.
Сопоставление с образцом — это процесс проверки значения на соответствие образцу во время выполнения. Сопоставление с образцом отличается от выполнения операторов (§14.1) и вычисления выражений (§15.1). Если значение успешно соответствует образцу, то процесс сопоставления с образцом инициализирует все переменные шаблона, объявленные в образце, если таковые имеются.
Процесс сопоставления с образцом может включать вычисление выражений или выполнение операторов. Соответственно, говорят, что сопоставление с образцом прерывается внезапно, если вычисление выражения или выполнение оператора прерывается внезапно. Прерывистое завершение всегда имеет связанную причину, которая всегда является throw с заданным значением. Сопоставление с образцом считается нормально завершённым, если оно не прерывается внезапно.
Правила определения соответствия значения образцу и инициализации переменных шаблона следующие:
-
Нулевое значение ссылки соответствует шаблону типа, если шаблон типа является нулевым (§14.30.1); и не соответствует в противном случае.
Если нулевое значение ссылки соответствует, то переменная шаблона, объявленная шаблоном типа, инициализируется нулевой ссылкой.
Если нулевое значение ссылки не соответствует, то переменная шаблона, объявленная шаблоном типа, не инициализируется.
-
Значение
v, которое не является нулевой ссылкой, соответствует шаблону типа типа T, еслиvможет быть преобразовано проверкой преобразования (§5.7) к целевому типу T без повышенияClassCastException; и не соответствует в противном случае.Если
vсоответствует, то переменная шаблона, объявленная шаблоном типа, инициализируетсяv.Если
vне соответствует, то переменная шаблона, объявленная шаблоном типа, не инициализируется. -
Нулевая ссылка не соответствует шаблону записи.
В этом случае какие-либо переменные шаблона, появляющиеся в объявлениях, содержащихся в шаблоне записи, не инициализируются.
-
Значение
v, которое не является нулевой ссылкой, соответствует шаблону записи типа R и списку шаблонов компонентовL, если (i)vможет быть преобразовано проверкой преобразования (§5.7) к целевому типу R без повышенияClassCastException; и (ii) каждый компонент записиvсоответствует соответствующему шаблону компонента вL; и не соответствует в противном случае.Каждый компонент записи
vопределяется путём вызова метода доступаv, соответствующего этому компоненту. Если выполнение вызова метода доступа прерывается внезапно по причине S, то сопоставление с образцом прерывается внезапно путём выбросаMatchExceptionс причиной S.Переменная шаблона, объявленная шаблоном, появляющимся в списке шаблонов компонентов шаблона записи, инициализируется только в том случае, если все шаблоны в списке соответствуют.
-
Каждое значение соответствует шаблону сопоставления всему.
Шаблон p называется применимым для типа T, если применяется одно из следующих правил:
-
Шаблон типа, объявляющий переменную шаблона с типом ссылки U, применим к типу ссылки T, если существует преобразование проверки (§5.7) из типа T в тип U.
-
Шаблон типа, объявляющий переменную шаблона с примитивным типом P, применим к типу P.
-
Шаблон записи с типом R и списком шаблонов
Lприменим к типу T, если (i) существует преобразование проверки (§5.7) из типа T в тип R, и (ii) для каждого шаблона компонентаp, появляющегося вL, если таковой имеется,pприменим к типу соответствующего поля компонента в R. -
Шаблон сопоставления всему применим к любому типу T.
Шаблон p называется безусловным для типа T, если в момент компиляции можно определить, что каждое значение типа T будет соответствовать p, и поэтому проверка соответствия шаблона во время выполнения может быть исключена. Определяется следующим образом:
-
Шаблон типа, объявляющий переменную шаблона с типом ссылки S, безусловен для типа ссылки T, если стёртый тип T является подтипом стёртого типа S.
-
Шаблон типа, объявляющий переменную шаблона с примитивным типом P, безусловен для типа P.
-
Шаблон сопоставления всему безусловен для любого типа T.
Обратите внимание, что ни один шаблон записи не является безусловным, потому что нулевая ссылка не соответствует ни одному шаблону записи.
Шаблон p называется доминирующим над другим шаблоном q, если каждое значение, которое соответствует q, также соответствует p, и определяется следующим образом:
-
Шаблон
pдоминирует над шаблоном типа, который объявляет переменную шаблона типа T, еслиpбезусловен для T. -
Шаблон
pдоминирует над шаблоном записи с типом R, еслиpбезусловен для R. -
Шаблон записи с типом R и списком шаблонов компонентов
Lдоминирует над другим шаблоном записи с типом S и списком шаблонов компонентовM, если (i) R и S называют один и тот же класс записи, и (ii) каждый шаблон компонента, если таковой имеется, вLдоминирует над соответствующим шаблоном компонента вM. -
Шаблон
pдоминирует над шаблоном сопоставления всему с типом T, еслиpбезусловен для T.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.