Глава 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 появляется более одного раза в качестве модификатора для объявления локальной переменной.
Ошибка компиляции, если ТипЛокальнойПеременной является 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.
Каждое объявление в объявлении локальной переменной объявляет одну локальную переменную, имя которой является Идентификатором, указанным в этом объявлении.
Если ключевое слово final появляется в начале объявления, объявленная переменная является переменной типа final (§4.12.4).
Тип объявленной локальной переменной определяется следующим образом:
-
Если ТипЛокальнойПеременной является ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторПеременной нет пар скобок, то тип локальной переменной обозначается как ТипБезАннотаций.
-
Если ТипЛокальнойПеременной является ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторПеременной есть пары скобок, то тип локальной переменной задается согласно §10.2.
-
Если ТипЛокальнойПеременной является
var, пусть T — тип выражения-инициализатора, рассматриваемого как самостоятельное выражение (без учёта присваивания) (§15.2). Тип локальной переменной — это проекция T вверх по отношению ко всем синтетическим параметрам типов, упомянутым в T (§4.10.5).Ошибка компиляции, если T — тип null.
Поскольку инициализатор рассматривается как самостоятельное выражение, возникает ошибка, если это лямбда-выражение (§15.27) или выражение ссылки на метод (§15.13).
Область видимости и перекрытие локального объявления переменной задаются в §6.3 и §6.4.
Ссылки на локальную переменную из вложенного класса или интерфейса или лямбда-выражения ограничены, как указано в §6.5.6.1.
Пример 14.4.1-1. Тип локальных переменных, объявленных с var
Следующий код иллюстрирует типизацию переменных, объявленных с var:
var a = 1; // a has type 'int'
var b = java.util.List.of(1, 2); // b has type 'List<Integer>'
var c = "x".getClass(); // c has type 'Class<? extends String>'
// (see JLS 15.12.2.6)
var d = new Object() {}; // d has the type of the anonymous class
var e = (CharSequence & Comparable<String>) "x";
// e has type CharSequence & Comparable<String>
var f = () -> "hello"; // Illegal: lambda not in an assignment context
var g = null; // Illegal: null type
Обратите внимание, что некоторые переменные, объявленные с var, не могут быть объявлены с явным типом, поскольку тип переменной не может быть обозначен.
Проекция вверх применяется к типу инициализатора при определении типа переменной. Если тип инициализатора содержит переменные захвата, эта проекция отображает тип инициализатора на супертип, не содержащий переменных захвата.
Хотя было бы возможно разрешить упоминание переменных захвата в типе переменной, исключение этих переменных через проекцию обеспечивает привлекательное свойство, заключающееся в том, что область видимости переменной захвата никогда не будет больше, чем оператор, содержащий выражение, тип которого захвачен. Неформально, переменные захвата не могут «просочиться» в последующие операторы.
Объявление локальной переменной состоит из объявления локальной переменной.
Каждое объявление локальной переменной немедленно содержится в блоке, в то время как другие виды операторов (§14.5) могут быть немедленно содержатся либо в блоке, либо в другом операторе.
В содержащем блоке операторы объявления локальных переменных могут свободно чередоваться с другими видами операторов и с объявлениями локальных классов и интерфейсов.
Оператор объявления локальной переменной является исполняемым оператором. Каждый раз при его выполнении деклараторы обрабатываются в порядке слева направо. Если декларатор имеет инициализатор, инициализатор вычисляется, а его значение присваивается переменной.
Если у декларатора нет инициализатора, то каждое обращение к переменной должно предшествовать выполнению присваивания переменной, иначе возникает ошибка компиляции по правилам §16 (Определённое присваивание).
Каждый инициализатор (кроме первого) вычисляется только в том случае, если вычисление предыдущего инициализатора завершается нормально.
Выполнение оператора объявления локальной переменной завершается нормально только в том случае, если вычисление последнего инициализатора завершается нормально.
Если ни один из деклараторов в операторе объявления локальной переменной не имеет инициализатора, то выполнение оператора всегда завершается нормально.
В языке программирования Java есть много видов операторов. Большинство из них соответствуют операторам языков C и C++, но некоторые являются уникальными.
Как и в C и C++, оператор языка программирования Java страдает от так называемой проблемы «висящего else», проиллюстрированной этим вводящим в заблуждение примером:
if (door.isOpen())
if (resident.isVisible())
resident.greet("Hello!");
else door.bell.ring(); // A "dangling else"
Проблема заключается в том, что как оператор внешнего if, так и оператор внутреннего if могут потенциально владеть фразой else. В этом примере можно предположить, что программист намеревался, чтобы фраза else относилась к оператору внешнего if.
Язык программирования Java, подобно C и C++ и многим языкам программирования до них, произвольно объявляет, что фраза else принадлежит самому внутреннему оператору if, которому она потенциально может принадлежать. Это правило отражено в следующей грамматике:
Следующие порождающие правила из §14.9 приведены здесь для удобства:
Таким образом, операторы грамматически делятся на две категории: те, которые могут заканчиваться оператором без фразы (оператором «короткий 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завершается успешно тогда и только тогда, когда выполнение этого оператора завершается успешно.
Утверждение — это оператор, содержащий булево выражение. Утверждение может быть активным или неактивным. Если утверждение активно, выполнение утверждения приводит к оценке булева выражения, и при оценке в значение false, будет сообщено об ошибке. Если утверждение неактивно, выполнение утверждения не имеет никакого эффекта.
Для упрощения представления, первое Выражение в обоих формах оператора assert называется Expression1. Во второй форме оператора assert, второе Выражение называется Expression2.
Если Expression1 не имеет тип boolean или Boolean, это ошибка времени компиляции.
Если во второй форме оператора assert, Expression2 имеет тип void (§15.1), это ошибка времени компиляции.
Оператор утверждения, выполняемый после завершения инициализации его класса или интерфейса, активен тогда и только тогда, когда хост-система определила, что верхний уровень класса или интерфейса, лексически содержащий оператор утверждения, включает утверждения.
Определение того, включает ли верхний уровень класса или интерфейса утверждения, производится не позднее, чем в (i) момент инициализации верхнего уровня класса или интерфейса, и (ii) момент инициализации любого вложенного класса или интерфейса в верхнем уровне класса или интерфейса. После определения того, включает ли верхний уровень класса или интерфейса утверждения, это свойство больше не может быть изменено.
Оператор утверждения, выполняемый до завершения инициализации его класса или интерфейса, активен.
Это правило мотивировано случаем, требующим специального обращения. Вспомните, что статус утверждения класса устанавливается не позднее момента его инициализации. Возможно, хотя и нежелательно, выполнить методы или конструкторы до инициализации. Это может произойти, когда иерархия классов содержит цикличность в статической инициализации, как в следующем примере:
public class Foo {
public static void main(String[] args) {
Baz.testAsserts();
// Will execute after Baz is initialized.
}
}
class Bar {
static {
Baz.testAsserts();
// Will execute before Baz is initialized!
}
}
class Baz extends Bar {
static void testAsserts() {
boolean enabled = false;
assert enabled = true;
System.out.println("Asserts " +
(enabled ? "enabled" : "disabled"));
}
}
Вызов Baz.testAsserts() приводит к инициализации Baz. Прежде чем это произойдет, необходимо инициализировать Bar. Статический инициализатор Bar снова вызывает Baz.testAsserts(). Поскольку инициализация Baz уже происходит в текущей потоке, второй вызов выполняется немедленно, хотя Baz еще не инициализирован (§12.4.2).
Из-за правила выше, если программа выше выполняется без включения утверждений, она должна вывести:
Asserts enabled Asserts disabled
Неактивный оператор утверждения не делает ничего. В частности, ни Expression1, ни Expression2 (если он присутствует) не оцениваются. Выполнение неактивного оператора утверждения всегда завершается нормально.
Активный оператор утверждения выполняется путем сначала оценки Expression1. Если результат имеет тип Boolean, он подвергается преобразованию разворачивания (§5.1.8).
Если оценка Expression1 или последующее преобразование разворачивания (если таковое имеется) прерывается по какой-либо причине, оператор утверждения прерывается по той же причине.
В противном случае выполнение продолжается путем выбора, основанного на значении Expression1:
-
Если значение равно
true, никаких дальнейших действий не предпринимается, и оператор утверждения завершается нормально. -
Если значение равно
false, поведение выполнения зависит от наличия Expression2:-
Если Expression2 присутствует, он оценивается. Затем:
-
Если оценка прерывается по какой-либо причине, оператор утверждения прерывается по той же причине.
-
Если оценка завершается нормально, создается экземпляр
AssertionError, "сообщение детали" которого — результирующее значение Expression2. Затем:-
Если создание экземпляра прерывается по какой-либо причине, оператор утверждения прерывается по той же причине.
-
Если создание экземпляра завершается нормально, оператор утверждения прерывается, выбросив созданный объект
AssertionError.
-
-
-
Если Expression2 отсутствует, создается экземпляр
AssertionErrorбез "сообщения детали". Затем:-
Если создание экземпляра прерывается по какой-либо причине, оператор утверждения прерывается по той же причине.
-
Если создание экземпляра завершается нормально, оператор утверждения прерывается, выбросив созданный объект
AssertionError.
-
-
Обычно проверка утверждений включена во время разработки и тестирования программы, и выключена при развертывании для повышения производительности.
Поскольку утверждения могут быть выключены, программы не должны предполагать, что выражения, содержащиеся в утверждениях, будут оцениваться. Таким образом, эти булевы выражения, как правило, должны быть свободны от побочных эффектов. Оценка такого булева выражения не должна влиять на состояние, видимое после завершения оценки. Не является ошибкой, если булево выражение, содержащееся в утверждении, имеет побочный эффект, но, как правило, это нежелательно, поскольку это может привести к изменению поведения программы в зависимости от того, были ли утверждения включены или выключены.
С учетом этого, утверждения не должны использоваться для проверки аргументов в public методах. Проверка аргументов обычно является частью контракта метода, и этот контракт должен соблюдаться, независимо от того, включены или выключены утверждения.
Вторая проблема использования утверждений для проверки аргументов заключается в том, что ошибочные аргументы должны привести к соответствующему исключению во время выполнения (например, IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Ошибка утверждения не выбросит соответствующее исключение. Опять же, использование утверждений для проверки аргументов в public методах не является ошибкой, но, как правило, нежелательно. Предполагается, что AssertionError никогда не будет перехвачен, но это возможно, поэтому правила для операторов try должны рассматривать утверждения, встречающиеся в блоке try, аналогично текущему обращению с операторами throw.
Оператор switch передает управление одному из нескольких операторов или выражений в зависимости от значения выражения.
Выражение называется выражением-селектором. Тип выражения-селектора должен быть char, byte, short, int, или ссылочным типом, в противном случае возникает ошибка времени компиляции.
Тело как оператора switch, так и выражения switch (§15.28) называется блоком switch. В этом подразделе представлены общие правила, которые применяются ко всем блокам switch, независимо от того, появляются ли они в операторах switch или выражениях switch. Другие подразделы представляют дополнительные правила, которые применяются либо к блокам switch в операторах switch (§14.11.2), либо к блокам switch в выражениях switch (§15.28.1).
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; и фильтрованной в противном случае.
Метки 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доминируется, если в блоке переключателя имеется предшествующая недоминирующая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с константойcasecдоминируется, если выполняется одно из следующих условий:-
cявляется константным выражением примитивного типа S, и в блоке переключателя имеется предшествующая недоминирующаяcaseметка с шаблономcasep, гдеpявляется безусловным для обертывающего класса S. -
cявляется константным выражением ссылочного типа T, и в блоке переключателя имеется предшествующая недоминирующаяcaseметка с шаблономp, гдеpявляется безусловным для типа T. -
cобозначает константу перечисления перечисления E, и в блоке переключателя имеется предшествующая недоминирующаяcaseметка с шаблономp, гдеpявляется безусловным для типа E.
Например, метка
caseс шаблоном типаIntegerдоминирует над меткойcaseс целочисленной литеральной константой:Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); } -
-
Метка
defaultилиcasenull,defaultметка доминируется, если в блоке переключателя имеется предшествующая недоминирующаяcaseметка с шаблономcasep, гдеpявляется безусловным для типа выражения селектора (§14.30.3).Метка
caseс шаблономcase, который является безусловным для типа выражения селектора, как следует из названия, будет соответствовать каждому значению и поведёт себя как меткаdefault. В блоке переключателя не может быть более одной метки переключателя, которая действует какdefault.
Возникает ошибка компиляции, если выполняется любое из следующих условий:
-
В блоке переключателя имеется метка
default, которая предшествует меткеcaseс шаблономcase. -
В блоке переключателя имеется метка
default, которая предшествует меткеcaseс литераломnull. -
В блоке переключателя имеется метка
casenull,default, за которой следует любая другая метка переключателя.
Если используется, метка default должна стоять последней в блоке переключателя.
По соображениям совместимости, метка default может предшествовать меткам case, у которых нет литерала null или шаблона case.
int i = ...;
switch(i) {
default ->
System.out.println("Some other integer");
case 42 -> // allowed
System.out.println("42");
}
Если используется, метка case null, default должна стоять последней в блоке переключателя.
Возникает ошибка компиляции, если в блоке переключателя, состоящем из групп инструкций с метками переключателя, инструкция помечена шаблоном case, который объявляет одну или несколько переменных шаблона, и либо:
-
Непосредственно предшествующая инструкция в блоке переключателя может завершиться нормально (§14.22), или
-
Инструкция помечена более чем одной меткой переключателя.
Первое условие предотвращает «просеивание» группы инструкций в другую группу инструкций без инициализации переменных шаблона. Например, если бы инструкция, помеченная case Integer i, была доступна из предшествующей группы инструкций, переменная шаблона i не была бы инициализирована:
Object o = "Hello";
switch (o) {
case String s:
System.out.println("String: " + s ); // No break!
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
Блоки переключателей, состоящие из групп инструкций с метками переключателя, позволяют применять несколько меток к группе инструкций. Второе условие предотвращает выполнение группы инструкций по одной метке без инициализации переменных шаблона другой метки. Например:
Object o = "Hello World";
switch (o) {
case String s:
case Integer i:
System.out.println(i + 1); // Error! Can be reached
// without matching the
// pattern `Integer i`
default:
}
Object obj = null;
switch (obj) {
case null:
case String s:
System.out.println(s); // Error! Can be reached
// without matching the
// pattern `String s`
default:
}
Оба этих условия применимы только в том случае, когда шаблон case объявляет переменные шаблона. Напротив, следующие примеры не вызывают проблем:
record R() {}
record S() {}
Object o = "Hello World";
switch (o) {
case String s:
System.out.println(s); // No break
case R(): // No pattern variables declared
System.out.println("It's either an R or a string");
break;
default:
}
Object ob = new R();
switch (ob) {
case R():
case S(): // Multiple case labels
System.out.println("Either R or an S");
break;
default:
}
Object obj = null;
switch (obj) {
case null:
case R(): // Multiple case labels
System.out.println("Either null or an R");
break;
default:
}
Блок 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с шаблономcasepприменяется к значению, происходит сначала с проверкой соответствия значения шаблонуp(§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, улучшенный по сравнению с первоначальным, — это оператор, в котором (i) тип выражения-селектора не является типом char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления, или (ii) существует шаблон case или литерал null, связанный с блоком switch.
Для блока switch оператора switch должны выполняться все следующие условия, в противном случае возникает ошибка компиляции:
-
Каждое выражение switch-правила в блоке switch является выражением-оператором (§14.8).
Операторы
switchотличаются от выраженийswitchв том, какие выражения могут появляться справа от стрелки (->) в блоке switch, то есть какие выражения могут быть использованы как выражения switch-правила. В оператореswitchв качестве выражения switch-правила может использоваться только выражение-оператор, а в выраженииswitch— любое выражение (§15.28.1). -
Если оператор
switchпредставляет собой операторswitch, улучшенный по сравнению с первоначальным, то он должен быть исчерпывающим (§14.11.1.1).
До Java SE 21 операторы switch (и выражения switch) были ограничены двумя способами: (i) тип выражения-селектора был ограничен либо целочисленным типом (исключая long), типом перечисления или типом String, и (ii) не поддерживались метки case null. Кроме того, в отличие от выражений switch, операторы switch не должны были быть исчерпывающими. Это часто является причиной трудно обнаруживаемых ошибок, где ни одна метка switch не применяется, и оператор switch молча ничего не сделает. Например:
enum E { A, B, C }
E e = ...;
switch (e) {
case A -> System.out.println("A");
case B -> System.out.println("B");
// No case for C!
}
В Java SE 21, помимо поддержки шаблонов case, два ограничения операторов switch (и выражений switch) были ослаблены: (i) разрешить выражение-селектор любого ссылочного типа и (ii) разрешить метку case с литералом null. Разработчики языка программирования Java также решили, что улучшенные операторы switch должны соответствовать выражениям switch и должны быть исчерпывающими. Это часто достигается добавлением тривиальной метки default. Например, следующий улучшенный оператор switch не является исчерпывающим:
Object o = ...;
switch (o) { // Error - non-exhaustive switch!
case String s -> System.out.println("A string!");
}
но его легко сделать исчерпывающим:
Object o = ...;
switch (o) {
case String s -> System.out.println("A string!");
default -> {}
}
По соображениям совместимости, операторы switch, которые не являются улучшенными операторами switch, не обязаны быть исчерпывающими.
Оператор switch выполняется путём сначала вычисления выражения селектора. Если вычисление выражения селектора завершается прерыванием, то весь оператор switch завершается прерыванием по той же причине.
Если вычисление выражения селектора завершается нормально, то выполнение оператора switch продолжается путём определения, применим ли метка переключателя, связанная с блоком переключателя, к значению выражения селектора (§14.11.1.2). Затем:
-
Если процесс определения применяемой метки переключателя завершается прерыванием, то весь оператор
switchзавершается прерыванием по той же причине. -
Если никакая метка переключателя не применима, то выполняется одно из следующих условий:
-
Если значение выражения селектора является
null, то выбрасывается исключениеNullPointerException, и весь операторswitchзавершается прерыванием по этой причине. -
Если оператор
switchявляется расширенным операторомswitch, то выбрасывается исключениеMatchException, и весь операторswitchзавершается прерыванием по этой причине. -
Если значение выражения селектора не является
null, и операторswitchне является расширенным операторомswitch, то весь операторswitchзавершается нормально.
-
-
Если метка переключателя применима, то выполняется одно из следующих условий:
-
Если это метка переключателя для выражения правила переключателя, то выражение правила переключателя обязательно является выражением-оператором (§14.11.2). Вычисляется выражение-оператор. Если вычисление завершается нормально, то оператор
switchзавершается нормально. Если результат вычисления является значением, оно игнорируется. -
Если это метка переключателя для блока правила переключателя, то выполняется блок. Если этот блок завершается нормально, то оператор
switchзавершается нормально. -
Если это метка переключателя для оператора правила переключателя
throw, то выполняется операторthrow. -
Если это метка переключателя для группы операторов с меткой, тогда все операторы в блоке переключателя, которые следуют за меткой переключателя, выполняются в порядке. Если эти операторы завершаются нормально, то оператор
switchзавершается нормально. -
В противном случае в блоке переключателя нет операторов, которые следуют за применимой меткой переключателя, и оператор
switchзавершается нормально.
-
Если выполнение любого оператора или выражения в блоке переключателя завершается прерыванием, оно обрабатывается следующим образом:
-
Если выполнение оператора завершается прерыванием из-за
breakбез метки, то никаких дальнейших действий не предпринимается, и операторswitchзавершается нормально.Прерывание из-за
breakс меткой обрабатывается общим правилом для меченых операторов (§14.7). -
Если выполнение оператора или выражения завершается прерыванием по любой другой причине, то оператор
switchзавершается прерыванием по той же причине.Прерывание из-за оператора
yieldобрабатывается общим правилом для выражений switch (§15.28.2).
Пример 14.11.3-1. Пропуск в операторе switch
Когда метка переключателя применима, и эта метка переключателя предназначена для правила переключателя, выражение или оператор правила переключателя, введенные меткой переключателя, выполняются, и ничего более. В случае метки переключателя для группы операторов выполняются все операторы блока переключателя, которые следуют за меткой переключателя, включая те, что появляются после последующих меток переключателя. В результате, как в C и C++, выполнение операторов может «проходить сквозь метки».
Например, программа:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
содержит блок switch, в котором код для каждого case переходит в код для следующего case. В результате программа выводит:
many too many one too many
Пропуск может быть причиной скрытых ошибок. Если код не должен пропускать case к case таким образом, то могут быть использованы операторы break для указания, когда следует передать управление, или могут быть использованы правила переключателя, как в программе:
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
static void howManyAgain(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
howManyAgain(1);
howManyAgain(2);
howManyAgain(3);
}
}
Эта программа выводит:
one two many one two many
Оператор 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).
Оператор цикла с расширенной формой имеет следующий вид:
Ниже приведены следующие правила из §4.3, §8.3, §8.4.1 и §14.4 для удобства:
Тип Выражения должен быть типом массива (§10.1) или подтипом базового типа Iterable, в противном случае произойдёт ошибка компиляции.
Заголовок оператора цикла с расширенной формой объявляет локальную переменную, имя которой задаётся идентификатором VariableDeclaratorId. При выполнении оператора цикла с расширенной формой локальная переменная на каждой итерации цикла инициализируется последовательными элементами Iterable или массива, полученного из выражения.
Правила объявления локальной переменной в заголовке оператора цикла с расширенной формой описаны в §14.4, при этом игнорируются правила, относящиеся к случаю, когда LocalVariableType является var. Кроме того, должны выполняться все следующие условия, в противном случае произойдёт ошибка компиляции:
-
VariableDeclaratorList состоит из одной VariableDeclarator.
-
VariableDeclarator не имеет инициализатора.
-
VariableDeclaratorId не содержит пар скобок, если LocalVariableType является
var.
Область видимости и перекрытие локальной переменной, объявленной в заголовке оператора цикла с расширенной формой, указаны в §6.3 и §6.4.
Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения ограничены, как указано в §6.5.6.1.
Тип T локальной переменной, объявленной в заголовке оператора цикла с расширенной формой, определяется следующим образом:
-
Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId отсутствуют пары скобок, тогда T — это тип, обозначаемый UnannType.
-
Если LocalVariableType является UnannType, и в UnannType или VariableDeclaratorId присутствуют пары скобок, тогда T определяется в §10.2.
-
Если LocalVariableType является
var, тогда пусть R выводится из типа Expression следующим образом:-
Если Expression имеет тип массива, то R — это компонентный тип массива.
-
В противном случае, если Expression имеет тип, являющийся подтипом
Iterable<X>для некоторого типа X, то R — это X. -
В противном случае, Expression имеет тип, являющийся подтипом базового типа
Iterable, и R —Object.
T является повышающей проекцией R относительно всех синтетических типов, упомянутых в R (§4.10.5).
-
Точное значение оператора цикла с расширенной формой определяется переводом в базовый оператор for, как показано ниже:
-
Если тип Выражения является подтипом
Iterable, то базовая инструкцияforимеет следующий вид:for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T Identifier = (TargetType) #i.next(); Statement }
где:
-
Если тип Выражения является подтипом
Iterable<X>для некоторого типа аргумента X, то I — это типjava.util.Iterator<X>. В противном случае, I — это исходный типjava.util.Iterator. -
#i— это автоматически сгенерированный идентификатор, отличающийся от любого другого идентификатора (автоматически сгенерированного или иного), который находится в области видимости (§6.3) в момент появления улучшенной инструкцииfor. -
{МодификаторПеременной} указан в заголовке улучшенной инструкции
for. -
T — это тип локальной переменной, определенный выше.
-
Если T — это тип ссылки, то TargetType — это T. В противном случае, TargetType — это верхняя граница преобразования захвата (§5.1.10) типа аргумента I, или
Object, если I является исходным.
-
-
В противном случае, Выражение обязательно имеет тип массива S
[], и базовая инструкцияforимеет следующий вид:S
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T Identifier = #a[#i]; Statement }где:
-
L1...Lm— это (возможно, пустая) последовательность меток, непосредственно предшествующих улучшенной инструкцииfor. -
#aи#i— это автоматически сгенерированные идентификаторы, отличающиеся от любого другого идентификатора (автоматически сгенерированного или иного), находящегося в области видимости в момент появления улучшенной инструкцииfor. -
{МодификаторПеременной} указан в заголовке улучшенной инструкции
for. -
T — это тип локальной переменной, определенный выше.
-
Например, этот код:
List<? extends Integer> l = ... for (float i : l) ...
будет переведен в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
float #i0 = (Integer)#i.next();
...
Пример 14.14-1. Улучшенная for и массивы
Следующая программа, вычисляющая сумму целочисленного массива, демонстрирует, как улучшенная инструкция for работает для массивов:
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
Пример 14.14-2. Улучшенная for и преобразование разворачивания
Следующая программа объединяет улучшенную инструкцию for с автоматическим разворачиванием, чтобы перевести гистограмму в таблицу частот:
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
Инструкция 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.
В предыдущих описаниях используется фраза "пытается передать управление", а не просто "передает управление", потому что если внутри цели continue имеются операторы try (§14.20) блоки try или catch которых содержат оператор continue, то любые блоки finally этих операторов try будут выполнены в порядке, от вложенного к внешнему, перед передачей управления цели continue. Внезапное завершение блока finally может нарушить передачу управления, начатую оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла for. Этот break может быть заменен оператором continue, если сам цикл for помечен:
class Graph {
int[][] edges;
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int[] ne = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Выбор того, какой из них использовать (если какой-либо), в значительной степени зависит от стиля программирования.
Оператор return возвращает управление вызывающей стороне метода (§8.4, §15.12) или конструктора (§8.8, §15.9).
Существуют два вида оператора return:
-
Оператор
returnбез значения. -
Оператор
returnсо значением Выражение.
Оператор return пытается передать управление вызывающей стороне ближайшего вложенного конструктора, метода или лямбда-выражения; это вложенное объявление или выражение называется целью return. В случае оператора return со значением Выражение, значение Выражения становится значением вызова.
Если оператор return не имеет цели return, то возникает ошибка компиляции.
Если цель return содержит (i) инициализатор экземпляра или статический инициализатор, который включает в себя оператор return, или (ii) лямбда-выражение, которое включает в себя оператор return, то возникает ошибка компиляции.
Если цель return оператора return без значения является методом, и этот метод не объявлен void, то возникает ошибка компиляции.
Если цель return оператора return со значением Выражение является конструктором или методом, объявленным void, то возникает ошибка компиляции.
Если цель return оператора return со значением Выражение является методом с объявленным типом возвращаемого значения T, а тип Выражения не совместим для присваивания (§5.2) с T, то возникает ошибка компиляции.
Выполнение оператора return без значения всегда завершается внезапно, причиной является возврат без значения.
Выполнение оператора return со значением Выражение сначала вычисляет Выражение. Если вычисление Выражения завершается внезапно по какой-либо причине, то оператор return завершается по этой причине. Если вычисление Выражения завершается успешно, создавая значение V, то оператор return завершается внезапно, причиной является возврат со значением V.
Таким образом, оператор return всегда завершается внезапно.
В предыдущих описаниях используется фраза "пытается передать управление", а не просто "передает управление", потому что если внутри метода или конструктора имеются операторы try (§14.20) блоки try или catch которых содержат оператор return, то любые блоки finally этих операторов try будут выполнены в порядке, от вложенного к внешнему, перед передачей управления вызывающей стороне метода или конструктора. Внезапное завершение блока finally может нарушить передачу управления, начатую оператором return.
Оператор 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прерывается по той же причине. -
Если вычисление Выражения завершается нормально, производя значение, отличное от
null, то операторthrowпрерывается, причиной являетсяthrowсо значениемV. -
Если вычисление Выражения завершается нормально, производя значение
null, то создается экземплярV'классаNullPointerExceptionи бросается вместоnull. Операторthrowзатем прерывается, причиной являетсяthrowсо значениемV'.
Таким образом, оператор throw всегда завершается прерыванием.
Если существуют какие-либо окружающие операторы try (§14.20), блоки try которых содержат оператор throw, то любые блоки finally этих операторов try выполняются по мере переноса управления наружу, пока брошенное значение не будет перехвачено. Обратите внимание, что прерывание выполнения блока finally может нарушить перенос управления, инициированный оператором throw.
Если оператор throw содержится в объявлении метода или лямбда-выражении, но его значение не перехватывается каким-либо оператором try, содержащим его, то вызов метода прерывается из-за throw.
Если оператор throw содержится в объявлении конструктора, но его значение не перехватывается каким-либо оператором try, содержащим его, то выражение создания экземпляра класса, вызывающее конструктор, завершится прерыванием из-за throw (§15.9.4).
Если оператор throw содержится в статическом инициализаторе (§8.7), то проверка во время компиляции (§11.2.3) гарантирует, что его значение всегда является исключением без проверки, или его значение всегда перехватывается каким-либо оператором try, содержащим его. Если во время выполнения, несмотря на эту проверку, значение не перехватывается каким-либо оператором try, содержащим оператор throw, то значение повторно бросается, если оно является экземпляром класса Error или одного из его подклассов; в противном случае оно оборачивается в объект ExceptionInInitializerError, который затем бросается (§12.4.2).
Если оператор throw содержится в инициализаторе экземпляра (§8.6), то проверка во время компиляции (§11.2.3) гарантирует, что либо его значение всегда является исключением без проверки, либо его значение всегда перехватывается каким-либо оператором try, содержащим его, или тип выброшенного исключения (или один из его суперклассов) встречается в блоке throws каждого конструктора класса.
По соглашению, пользовательские типы, допускающие исключения, обычно объявляются как подклассы класса Exception, который является подклассом класса Throwable (§11.1.1).
Оператор synchronized приобретает блокировку взаимного исключения (§17.1) от имени потока выполнения, выполняет блок, а затем освобождает блокировку. Пока выполняющийся поток владеет блокировкой, никакой другой поток не может приобрести эту блокировку.
Тип Выражения должен быть ссылочным типом; в противном случае произойдёт ошибка компиляции.
Оператор synchronized выполняется путём сначала вычисления Выражения. Затем:
-
Если вычисление Выражения завершается прерывисто по какой-либо причине, то и оператор
synchronizedзавершается прерывисто по той же причине. -
В противном случае, если значение Выражения является
null, выбрасываетсяNullPointerException. -
В противном случае, пусть не-
nullзначение Выражения будетV. Выполняющийся поток блокирует монитор, связанный сV. Затем выполняется Блок, а затем есть выбор:-
Если выполнение Блока завершается нормально, то монитор разблокируется и оператор
synchronizedзавершается нормально. -
Если выполнение Блока завершается прерывисто по любой причине, то монитор разблокируется, и оператор
synchronizedзавершается прерывисто по той же причине.
-
Блокировки, приобретаемые операторами synchronized, совпадают с блокировками, которые неявно приобретаются synchronized методами (§8.4.3.6). Один поток может приобретать блокировку более чем один раз.
Приобретение блокировки, связанной с объектом, само по себе не препятствует другим потокам получать доступ к полям объекта или вызывать не-synchronized методы объекта. Другие потоки также могут использовать synchronized методы или оператор synchronized стандартным образом для достижения взаимного исключения.
Пример 14.19-1. Оператор synchronized
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
Эта программа выводит:
made it!
Обратите внимание, что эта программа зациклится, если одному потоку не разрешено блокировать монитор более одного раза.
Оператор try выполняет блок кода. Если значение выбрасывается и оператор try имеет одну или несколько catch инструкций обработки исключений, способных перехватить его, то управление будет передано первой такой catch инструкции. Если оператор try имеет finally блок, то выполняется другой блок кода, независимо от того, завершился ли блок try нормально или прервано, а также независимо от того, получила ли сначала управление catch инструкция.
См. §8.3 для ТипКласса. Следующие правила из §4.3, §8.3 и §8.4.1 приведены здесь для удобства:
Блок Блок сразу после ключевого слова try называется блоком try оператора try.
Блок Блок сразу после ключевого слова finally называется блоком finally оператора try.
Оператор try может содержать catch блоки обработки исключений.
Инструкция catch обработки исключения объявляет ровно один параметр, который называется параметром обработки исключения.
Если final появляется более одного раза в качестве модификатора объявления параметра обработки исключения, то это синтаксическая ошибка.
Область видимости и перекрытие параметра обработки исключения определяются в §6.3 и §6.4.
Ссылки на параметр обработки исключения из вложенного класса или интерфейса или лямбда-выражения ограничены, как указано в §6.5.6.1.
Параметр обработки исключения может обозначать свой тип как один класс или объединение двух или более типов классов (называемых альтернативами). Альтернативы объединения разделены |.
Блок catch, параметр обработки исключения которого обозначен как один тип класса, называется однотипным блоком catch.
Блок catch, параметр обработки исключения которого обозначен как объединение типов, называется многотипным блоком catch.
Каждый тип класса, используемый в обозначении типа параметра обработки исключения, должен быть классом Throwable или подклассом Throwable; в противном случае возникает ошибка компиляции.
Использование переменной типа в обозначении типа параметра обработки исключения является ошибкой компиляции.
Объединение типов с двумя альтернативами Di и Dj (i ≠ j), где Di является подтипом Dj (§4.10.2), является ошибкой компиляции.
Объявленный тип параметра обработки исключения, обозначающий его тип с помощью одного типа класса, — это этот тип класса.
Объявленный тип параметра обработки исключения, обозначающий его тип как объединение с альтернативами D1 | D2 | ... | Dn, — это lub(D1, D2, ..., Dn).
Параметр обработки исключения многотипного блока catch неявно объявлен как final, если не объявлен явно как final.
Если параметр обработки исключения, неявно или явно объявленный как final, присваивается в теле блока catch, то это ошибка компиляции.
Параметр обработки исключения однотипного блока catch никогда не объявляется неявно как final, но может быть явно объявлен как final или являться фактически конечным (§4.12.4).
Неявно объявленный параметр обработки исключения является конечным по своей природе, в то время как фактически конечный параметр обработки исключения, по сути, является конечным из-за своего использования. Параметр обработки исключения многотипного блока catch неявно объявлен как final, поэтому не может быть левым операндом оператора присваивания, но не считается фактически конечным.
Если параметр обработки исключения фактически конечный (в однотипном блоке catch) или неявно конечный (в многотипном блоке catch), то добавление явного модификатора final к его объявлению не приведет к ошибкам компиляции. С другой стороны, если параметр обработки исключения однотипного блока catch явно объявлен как final, то удаление модификатора final может привести к ошибкам компиляции, потому что параметр обработки исключения, теперь считающийся фактически конечным, больше не может быть использован в объявлениях анонимных и локальных классов в теле блока catch. При отсутствии ошибок компиляции можно изменить программу так, чтобы параметр обработки исключения был переприсвоен в теле блока catch, и он больше не будет считаться фактически конечным.
Типы исключений, которые может сгенерировать оператор try, описаны в §11.2.2.
Связь исключений, сгенерированных блоком try оператора try и перехваченных catch блоками (если таковые имеются) оператора try, описана в §11.2.3.
Блоки обработки исключений рассматриваются слева направо: первый подходящий catch блок принимает исключение, получая в качестве аргумента объект сгенерированного исключения, как описано в §11.3.
Многострочное catch предложение можно рассматривать как последовательность однострочных catch предложений. То есть, catch предложение, где тип параметра исключения обозначается как объединение D1|D2|...|Dn эквивалентно последовательности n catch предложений, где типы параметров исключения — классы D1, D2, ..., Dn соответственно. В блоке Block каждого из n catch предложений, объявленный тип параметра исключения — lub(D1, D2, ..., Dn). Например, следующий код:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
семантически эквивалентен следующему коду:
try {
... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
final ReflectiveOperationException ex = ex1;
... body ...
}
catch (final IllegalAccessException ex2) {
final ReflectiveOperationException ex = ex2;
... body ...
}
где многострочное catch предложение с двумя альтернативами было преобразовано в два однострочных catch предложения, по одному для каждой альтернативы. Компилятор Java не обязан и не рекомендуется компилировать многострочное catch предложение путём дублирования кода таким образом, поскольку многострочное catch предложение можно представить в файле class без дублирования.
Блок finally гарантирует, что блок finally выполняется после блока try и любого блока catch, который может быть выполнен, независимо от того, как управление покидает блок try или блок catch. Обработка блока finally довольно сложна, поэтому два случая оператора try с блоком finally и без него описаны отдельно (§14.20.1, §14.20.2).
Оператор try может опустить блоки catch и блок finally, если это оператор try-с-ресурсами (§14.20.3).
Оператор try без блока finally выполняется путём сначала выполнения блока try. Затем следует выбор:
-
Если выполнение блока
tryзавершается нормально, то никаких дальнейших действий не предпринимается, и операторtryзавершается нормально. -
Если выполнение блока
tryзавершается прерывисто из-заthrowзначенияV, то следует выбор:-
Если тип времени выполнения
Vсовместим с присваиванием (§5.2) классу исключения, которое можно перехватить, в любом блокеcatchпредложенияtry, то выбирается первый (самый левый) такой блокcatch. ЗначениеVприсваивается параметру выбранного блокаcatch, и блок Block этого блокаcatchвыполняется, а затем следует выбор:-
Если этот блок выполняется нормально, то оператор
tryзавершается нормально. -
Если этот блок завершается прерывисто по любой причине, то оператор
tryзавершается прерывисто по той же причине.
-
-
Если тип времени выполнения
Vне совместим с присваиванием с классом исключения, которое можно перехватить, в любом блокеcatchпредложенияtry, то операторtryзавершается прерывисто из-заthrowзначенияV.
-
-
Если выполнение блока
tryзавершается прерывисто по любой другой причине, то операторtryзавершается прерывисто по той же причине.
Пример 14.20.1-1. Перехват исключения
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет два блока catch. Тип времени выполнения исключения — BlewIt, который не может быть присвоен переменной типа RuntimeException, но может быть присвоен переменной типа BlewIt, поэтому вывод примера:
Caught BlewIt
Оператор 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. Обычно, в операторах с автоматическим закрытием ресурсов, не требуются операторы finally и catch.
Ниже приведены фрагменты из §4.3, §8.3, §8.4.1 и §14.4 для удобства:
См. §8.3 для UnannType.
Спецификация ресурсов определяет ресурсы оператора try-with-resources, либо объявляя локальные переменные с инициализаторами, либо ссылаясь на существующие переменные. Существующая переменная указывается именем выражения (§6.5.6) или выражением доступа к полю (§15.11).
Правила для локальной переменной, объявленной в спецификации ресурсов, указаны в §14.4. Кроме того, должны выполняться все следующие условия, иначе возникает ошибка компиляции:
-
Список объявляемых переменных состоит из одной объявляемой переменной.
-
Объявляемая переменная имеет инициализатор.
-
Идентификатор переменной не содержит пар квадратных скобок.
Область действия и перекрытие локальной переменной, объявленной в спецификации ресурсов, описываются в §6.3 и §6.4.
Доступ к локальной переменной из вложенного класса или интерфейса, или лямбда-выражения ограничен, как указано в §6.5.6.1.
Тип локальной переменной, объявленной в спецификации ресурсов, описывается в §14.4.1.
Тип локальной переменной, объявленной в спецификации ресурсов, или тип существующей переменной, на которую ссылается спецификация ресурсов, должен быть подтипом AutoCloseable, иначе возникает ошибка компиляции.
Если спецификация ресурсов объявляет две локальные переменные с одинаковым именем, возникает ошибка компиляции.
Ресурсы являются final, поскольку:
-
Локальная переменная, объявленная в спецификации ресурсов, неявно объявляется
final, если она не объявлена явноfinal(§4.12.4). -
Существующая переменная, на которую ссылается спецификация ресурсов, должна быть
finalили фактическиfinalпеременной, которая определённо присваивается до оператора try-with-resources (§16 (Определённое присваивание)), иначе возникает ошибка компиляции.
Ресурсы инициализируются слева направо. Если ресурс не может быть инициализирован (т.е., его выражение-инициализатор выбрасывает исключение), то все ресурсы, инициализированные до этого момента оператором try-with-resources, закрываются. Если все ресурсы инициализируются успешно, блок try выполняется как обычно, а затем все непустые ресурсы оператора try-with-resources закрываются.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он был инициализирован непустым значением. Исключение, возникшее при закрытии одного ресурса, не препятствует закрытию других ресурсов. Такое исключение считается подавленным, если ранее было брошено исключение инициализатором, блоком try или при закрытии другого ресурса.
Оператор try-with-resources, в котором спецификация ресурсов указывает на несколько ресурсов, обрабатывается так, как будто это несколько операторов try-with-resources, каждый из которых имеет спецификацию ресурсов, указывающую на один ресурс. При переводе оператора try-with-resources с n ресурсами (n > 1) результат - это оператор try-with-resources с n-1 ресурсами. После n таких переводов, получается n вложенных операторов try-with-resources, и общий перевод завершен.
A try-with-resources statement with no catch clauses or finally clause is called a basic try-with-resources statement.
If a basic try-with-resources statement is of the form:
try (VariableAccess ...)
Block
then the resource is first converted to a local variable declaration by the following translation:
try (T #r = VariableAccess ...) {
Block
}
T is the type of the variable denoted by VariableAccess and #r is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs. The try-with-resources statement is then translated according to the rest of this section.
The meaning of a basic try-with-resources statement of the form:
try ({VariableModifier} R Identifier = Expression ...) Block
is given by the following translation to a local variable declaration and a try-catch-finally statement:
{
final {VariableModifierNoFinal} R Identifier = Expression;
Throwable #primaryExc = null;
try ResourceSpecification_tail
Block
catch (Throwable #t) {
#primaryExc = #t;
throw #t;
} finally {
if (Identifier != null) {
if (#primaryExc != null) {
try {
Identifier.close();
} catch (Throwable #suppressedExc) {
#primaryExc.addSuppressed(#suppressedExc);
}
} else {
Identifier.close();
}
}
}
}
{VariableModifierNoFinal} is defined as {VariableModifier} without final, if present.
#t, #primaryExc, and #suppressedExc are automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs.
If the resource specification indicates one resource, then ResourceSpecification_tail is empty (and the try-catch-finally statement is not itself a try-with-resources statement).
If the resource specification indicates n > 1 resources, then ResourceSpecification_tail consists of the 2nd, 3rd, ..., n'th resources indicated in the resource specification, in the same order (and the try-catch-finally statement is itself a try-with-resources statement).
Reachability and definite assignment rules for the basic try-with-resources statement are implicitly specified by the translation above.
In a basic try-with-resources statement that manages a single resource:
-
If the initialization of the resource completes abruptly because of a
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выходит из оператора, если внутри цели break нет операторовtry, блоки которых содержат операторbreak, или есть операторыtry, блоки которых содержат операторbreak, и все операторыfinallyэтих операторовtryмогут завершиться нормально.Это определение основано на логике «попыток передачи управления» в §14.15.
-
Оператор
continueпродолжает операторdo, если внутри оператораdoнет операторовtry, блоки которых содержат операторcontinue, или есть операторыtry, блоки которых содержат операторcontinue, и все операторыfinallyэтих операторовtryмогут завершиться нормально.
Правила таковы:
-
Блок, который является телом конструктора, метода, инициализатора экземпляра, статического инициализатора, лямбда-выражения или выражения
switch, достижим. -
Пустой блок, который не является блоком switch, может завершиться нормально тогда и только тогда, когда он достижим.
Непустой блок, который не является блоком switch, может завершиться нормально тогда и только тогда, когда последний оператор в нем может завершиться нормально.
Первый оператор в непустом блоке, который не является блоком switch, достижим тогда и только тогда, когда блок достижим.
Каждый другой оператор
Sв непустом блоке, который не является блоком switch, достижим тогда и только тогда, когда оператор, предшествующийS, может завершиться нормально. -
Оператор объявления локального класса может завершиться нормально тогда и только тогда, когда он достижим.
-
Оператор объявления локальной переменной может завершиться нормально тогда и только тогда, когда он достижим.
-
Пустой оператор может завершиться нормально тогда и только тогда, когда он достижим.
-
Помеченный оператор может завершиться нормально, если верно хотя бы одно из следующих условий:
-
Содержащийся оператор может завершиться нормально.
-
Существует достижимый оператор
break, который выходит из помеченного оператора.
Содержащийся оператор достижим тогда и только тогда, когда помеченный оператор достижим.
-
-
Оператор выражения может завершиться нормально тогда и только тогда, когда он достижим.
-
Оператор
if-thenможет завершиться нормально тогда и только тогда, когда он достижим.Оператор
then-достижим тогда и только тогда, когда операторif-thenдостижим.Оператор
if-then-elseможет завершиться нормально тогда и только тогда, когда операторthenможет завершиться нормально или операторelseможет завершиться нормально.Оператор
then-достижим тогда и только тогда, когда операторif-then-elseдостижим.Оператор
else-достижим тогда и только тогда, когда операторif-then-elseдостижим.Эта обработка оператора
if, независимо от того, имеет ли он частьelse, довольно необычна. Обоснование приводится в конце этого раздела. -
Оператор
assertможет завершиться нормально тогда и только тогда, когда он достижим. -
Оператор
switch, switch-блок которого пуст или содержит только метки switch, может завершиться нормально. -
Оператор
switch, switch-блок которого состоит из групп операторов с метками switch, может завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Последний оператор в блоке switch может завершиться нормально.
-
Имеется хотя бы одна метка switch после последней группы операторов в блоке switch.
-
Существует достижимый оператор
break, который выходит из оператораswitch. -
Оператор
switchне расширен (§14.11.2), и его блок switch не содержит меткиdefault.
-
-
Оператор
switch, switch-блок которого состоит из правил switch, может завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Одно из правил switch вводит выражение правила switch (которое обязательно является выражением оператора).
-
Одно из правил switch вводит блок правил switch, который может завершиться нормально.
-
Одно из правил switch вводит блок правил switch, который содержит достижимый оператор
break, который выходит из оператораswitch. -
Оператор
switchне расширен (§14.11.2), и его блок switch не содержит меткиdefault.
-
-
Блок switch достижим тогда и только тогда, когда его оператор
switchдостижим. -
Оператор в блоке switch , который состоит из групп операторов с метками switch, достижим тогда и только тогда, когда блок switch достижим, и верно хотя бы одно из следующих условий:
-
Он содержит метку
caseилиdefault. -
Существует оператор, предшествующий ему в блоке switch, и этот предшествующий оператор может завершиться нормально.
-
-
Блок правил switch в блоке switch достижим тогда и только тогда, когда блок switch достижим.
-
Оператор
throwправила switch в блоке switch достижим тогда и только тогда, когда блок switch достижим. -
Оператор
whileможет завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Оператор
whileдостижим, и выражение условия не является константным выражением (§15.29) со значениемtrue. -
Существует достижимый оператор
break, который выходит из оператораwhile.
Содержащийся оператор достижим тогда и только тогда, когда оператор
whileдостижим, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Оператор
doможет завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Содержащийся оператор может завершиться нормально, и выражение условия не является константным выражением (§15.29) со значением
true. -
Оператор
doсодержит достижимый операторcontinueбез метки, и операторdoявляется самым внутренним операторомwhile,doилиfor, который содержит этот операторcontinue, и операторcontinueпродолжает этот операторdo, и выражение условия не является константным выражением со значениемtrue. -
Оператор
doсодержит достижимый операторcontinueс меткойL, и операторdoимеет меткуL, и операторcontinueпродолжает этот операторdo, и выражение условия не является константным выражением со значениемtrue. -
Существует достижимый оператор
break, который выходит из оператораdo.
Содержащийся оператор достижим тогда и только тогда, когда оператор
doдостижим. -
-
Базовый оператор
forможет завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Оператор
forдостижим, существует выражение условия, и выражение условия не является константным выражением (§15.29) со значениемtrue. -
Существует достижимый оператор
break, который выходит из оператораfor.
Содержащийся оператор достижим тогда и только тогда, когда оператор
forдостижим, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Расширенный оператор
forможет завершиться нормально тогда и только тогда, когда он достижим. -
Оператор
break,continue,return,throwилиyieldне может завершиться нормально. -
Оператор
synchronizedможет завершиться нормально тогда и только тогда, когда содержащийся оператор может завершиться нормально.Содержащийся оператор достижим тогда и только тогда, когда оператор
synchronizedдостижим. -
Оператор
tryможет завершиться нормально тогда и только тогда, когда верно следующее:-
Блок
tryможет завершиться нормально или любой блокcatchможет завершиться нормально. -
Если оператор
tryимеет блокfinally, то блокfinallyможет завершиться нормально.
-
-
Блок
tryдостижим тогда и только тогда, когда операторtryдостижим.
-
A
catchblockCдостижим, если выполняются оба следующих условия:-
Тип параметра
C— это тип непроверяемого исключения, илиException, или его суперкласс, или в блокеtryесть достижимое выражение или операторthrow, который может бросить проверяемое исключение, тип которого совместим по присваиванию (§5.2) с типом параметраC. (Выражение достижимо, если ближайший оператор, содержащий его, достижим.)См. §15.6 для нормального и прерывистого завершения вычисления выражений.
-
В операторе
tryнет более раннего блокаcatchA, в котором тип параметра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 это ограничение на аннотации может быть снято.
В противном случае, тип шаблона записи — это ТипСсылочногоТипа.
Длина списка шаблонов шаблона записи должна совпадать с длиной списка компонентов записи в объявлении класса записи, названного ТипСсылочногоТипа, в противном случае возникает ошибка компиляции.
Шаблон записи не объявляет напрямую переменных шаблона, но может содержать объявления переменных шаблона в списке шаблонов.
Если шаблон записи содержит более одного объявления переменной шаблона с одинаковым именем, возникает ошибка компиляции.
Сопоставление с образцом — это процесс проверки значения на соответствие образцу во время выполнения. Сопоставление с образцом отличается от выполнения операторов (§14.1) и вычисления выражений (§15.1). Если значение успешно соответствует образцу, то процесс сопоставления с образцом инициализирует все переменные образца, объявленные в образце, если таковые имеются.
Процесс сопоставления с образцом может включать вычисление выражений или выполнение операторов. Соответственно, сопоставление с образцом считается прерванным, если вычисление выражения или выполнение оператора завершается прерывисто. Прерывистое завершение всегда имеет связанное основание, которое всегда является throw с заданным значением. Сопоставление с образцом считается нормальным, если оно не завершается прерывисто.
Правила определения соответствия значения образцу и инициализации переменных образца следующие:
-
Нулевая ссылка соответствует образцу типа, если образец типа соответствует нулю (§14.30.1); и не соответствует в противном случае.
Если нулевая ссылка соответствует, то переменная образца, объявленная образцом типа, инициализируется нулевой ссылкой.
Если нулевая ссылка не соответствует, то переменная образца, объявленная образцом типа, не инициализируется.
-
Значение
v, которое не является нулевой ссылкой, соответствует образцу типа типа T, еслиvможет быть приведено к типу T без повышенияClassCastException; и не соответствует в противном случае.Если
vсоответствует, то переменная образца, объявленная образцом типа, инициализируетсяv.Если
vне соответствует, то переменная образца, объявленная образцом типа, не инициализируется. -
Нулевая ссылка не соответствует образцу записи.
В этом случае никакие переменные образца, появляющиеся в объявлениях, содержащихся в образце записи, не инициализируются.
-
Значение
v, которое не является нулевой ссылкой, соответствует образцу записи типа R и списку образцовL, если (i)vможет быть приведено к типу R без повышенияClassCastException; и (ii) каждый компонент записиvсоответствует соответствующему компоненту образца вL; и не соответствует в противном случае.Каждый компонент записи
vопределяется путём вызова метода-акцессораv, соответствующего этому компоненту. Если выполнение вызова метода-акцессора завершается прерывисто из-за причины S, то сопоставление с образцом завершается прерывисто сбросом исключенияMatchExceptionс причиной S.Переменная образца, объявленная образцом, появляющимся в списке образцов образца записи, инициализируется только тогда, когда все образцы в списке соответствуют.
Образец p считается применимым в типе T, если применимо одно из следующих правил:
-
Образец типа, объявляющий переменную образца ссылочного типа U, применим к ссылочному типу T, если T допускает приведение к U (§5.5).
-
Образец типа, объявляющий переменную образца примитивного типа P, применим к типу P.
-
Образец записи типа R и список образцов
Lприменим к типу T, если (i) T допускает приведение к R, и (ii) для каждого образца компонентаp, появляющегося вL, если таковой имеется,pприменим к типу соответствующего поля компонента в R.
Образец p считается условным для типа T, если любое значение типа T будет соответствовать p, и поэтому аспект проверки сопоставления с образцом можно исключить. Он определяется следующим образом:
-
Образец типа, объявляющий переменную образца ссылочного типа S, является безусловным для ссылочного типа T, если стирание T является подтипом стирания S.
-
Образец типа, объявляющий переменную образца примитивного типа P, является безусловным для типа P.
Обратите внимание, что никакой образец записи не является безусловным, поскольку нулевая ссылка не соответствует ни одному образцу записи.
Образец p считается доминирующим над другим образцом q, если любое значение, соответствующее q, также соответствует p, и определяется следующим образом:
-
Образец
pдоминирует над образцом типа, объявляющим переменную образца типа T, еслиpявляется безусловным для T. -
Образец
pдоминирует над образцом записи с типом R, еслиpявляется безусловным для R. -
Образец записи с типом R и списком образцов
Lдоминирует над другим образцом записи с типом S и списком образцовM, если (i) R и S обозначают один и тот же класс записи, и (ii) каждый образец компонента, если таковой имеется, вLдоминирует над соответствующим образцом компонента вM.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.