Глава 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) описывает шаблоны, которые используются в операторах и выражениях для условного объявления и инициализации локальных переменных. Шаблон дает краткое описание того, как одно значение, например, объект, может быть составлено из одного или нескольких других значений, обозначенных объявлениями переменных. Сопоставление с шаблоном пытается извлечь одно или несколько значений из данного значения, как будто для разложения его, и использует извлеченные значения для инициализации переменных, объявленных шаблоном.
Каждый оператор имеет нормальный режим выполнения, в котором выполняются определённые вычислительные шаги. В следующих разделах описывается нормальный режим выполнения для каждого типа оператора.
Если все шаги выполняются как описано, без признаков прерывистого завершения, то оператор считается завершенным нормально. Однако, некоторые события могут помешать оператору завершиться нормально:
-
Операторы
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++, оператор if языка программирования Java страдает так называемой проблемой "висящего else", иллюстрируемой этим вводящим в заблуждение примером:
if (door.isOpen())
if (resident.isVisible())
resident.greet("Hello!");
else door.bell.ring(); // A "dangling else"
Проблема заключается в том, что как внешний оператор if, так и внутренний оператор if могут потенциально владеть else. В этом примере можно предположить, что программист намеревался, чтобы else относился к внешнему оператору if.
Язык программирования Java, как C и C++, а также многие другие языки программирования до них, произвольно устанавливает, что else относится к самому внутреннему if, к которому оно может потенциально относиться. Это правило зафиксировано в следующем грамматическом правиле:
Следующие правила из §14.9 представлены здесь для удобства:
Таким образом, операторы грамматически делятся на две категории: те, которые могут заканчиваться оператором if, у которого нет else (оператором "короткого if") и те, которые определённо не заканчиваются им.
Только операторы, которые определённо не заканчиваются оператором короткого if, могут быть непосредственным под-оператором перед ключевым словом else в операторе if, у которого есть else.
Это простое правило предотвращает проблему "висящего else". Поведение оператора с ограничением "нет короткого if" идентично поведению того же типа оператора без ограничения "нет короткого if"; различие введено исключительно для решения синтаксических трудностей.
Пустой оператор ничего не делает.
; Выполнение пустого оператора всегда завершается успешно.
Операторы могут иметь префиксы метки.
Идентификатор объявляется меткой немедленно содержащегося оператора.
В отличие от C и C++, язык программирования Java не имеет оператора goto; метки операторов-идентификаторов используются с операторами break или continue (раздел §14.15, раздел §14.16) где угодно в меткированном операторе.
Область действия метки меткированного оператора — немедленно содержащийся оператор.
Ошибка компиляции, если имя метки меткированного оператора используется в области действия метки в качестве метки другого меткированного оператора.
Нет ограничений на использование одного и того же идентификатора в качестве метки и имени пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора для маркировки оператора не скрывает (§6.4.2) пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же именем. Использование идентификатора в качестве класса, интерфейса, метода, поля, локальной переменной или в качестве параметра обработчика исключений (§14.20) не скрывает метку оператора с тем же именем.
Меткарованный оператор выполняется путём выполнения немедленно содержащегося оператора.
Если оператор помечен идентификатором и содержащийся оператор завершается внезапно из-за break с тем же идентификатором, то меткированный оператор завершается нормально. Во всех остальных случаях внезапного завершения оператора меткированный оператор завершается внезапно по той же причине.
Пример 14.7-1. Метки и идентификаторы
Следующий код был взят из версии класса String и его метода indexOf, где метка изначально называлась test. Изменение метки на то же имя, что и локальная переменная i, не скрывает метку в области действия объявления i. Таким образом, код корректен.
class Test {
char[] value;
int offset, count;
int indexOf(TestString str, int fromIndex) {
char[] v1 = value, v2 = str.value;
int max = offset + (count - str.count);
int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
i:
for (int i = start; i <= max; i++) {
int n = str.count, j = i, k = str.offset;
while (n-- != 0) {
if (v1[j++] != v2[k++])
continue i;
}
return i - offset;
}
return -1;
}
}
Идентификатор max также мог быть использован в качестве метки оператора; метка не скрыла бы локальную переменную max в пределах меткированного оператора.
Некоторые виды выражений могут использоваться в качестве операторов, если после них следует точка с запятой.
Оператор выражения выполняется путём вычисления выражения; если у выражения есть значение, оно отбрасывается.
Выполнение оператора выражения завершается нормально тогда и только тогда, когда вычисление выражения завершается нормально.
В отличие от C и C++, в языке программирования Java разрешены только определённые формы выражений, которые могут использоваться в качестве операторов выражений. Например, разрешено использовать выражение вызова метода (§15.12):
System.out.println("Hello world"); // OK
но не разрешено использовать выражение в скобках (§15.8.5):
(System.out.println("Hello world")); // illegal
Обратите внимание, что язык программирования Java не позволяет «привести к void» - void не является типом - поэтому традиционный трюк C, заключающийся в написании оператора выражения, такого как:
(void)... ; // incorrect!
не работает. С другой стороны, язык программирования Java допускает все самые полезные виды выражений в операторах выражения, и он не требует, чтобы вызов метода, используемый в качестве оператора выражения, вызывал метод void, поэтому такой трюк почти никогда не нужен. Если нужен трюк, можно использовать оператор присваивания (§15.26) или оператор объявления локальной переменной (§14.4) вместо него.
Оператор if позволяет условно выполнить оператор или условно выбрать один из двух операторов, выполнив один из них, но не оба.
Выражение Выражение должно иметь тип boolean или Boolean, в противном случае произойдет ошибка компиляции.
Оператор if-then выполняется путём сначала оценки Выражения. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если оценка Выражения или последующее преобразование распаковки (если таковое имеется) завершается абортом по какой-либо причине, то оператор if-then завершается абортом по той же причине.
В противном случае выполнение продолжается с выбором на основе полученного значения:
-
Если значение равно
true, то содержащийся оператор Оператор выполняется; операторif-thenзавершается успешно тогда и только тогда, когда выполнение оператора Оператора завершается успешно. -
Если значение равно
false, то никаких дальнейших действий не выполняется, и операторif-thenзавершается успешно.
Оператор if-then-else выполняется путём сначала оценки Выражения. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если оценка Выражения или последующее преобразование распаковки (если таковое имеется) завершается абортом по какой-либо причине, то оператор if-then-else завершается абортом по той же причине.
В противном случае выполнение продолжается с выбором на основе полученного значения:
-
Если значение равно
true, то первый содержащийся оператор Оператор (тот, что перед ключевым словомelse) выполняется; операторif-then-elseзавершается успешно тогда и только тогда, когда выполнение этого оператора завершается успешно. -
Если значение равно
false, то второй содержащийся оператор Оператор (тот, что после ключевого словаelse) выполняется; операторif-then-elseзавершается успешно тогда и только тогда, когда выполнение этого оператора завершается успешно.
Утверждение — это оператор assert, содержащий булево выражение. Утверждение может быть активировано или деактивировано. Если утверждение активировано, его выполнение приводит к оценке булева выражения, и при получении результата false, сообщается об ошибке. Если утверждение деактивировано, выполнение утверждения не оказывает никакого эффекта.
Для упрощения представления первое Выражение в обоих формах оператора assert называется Expression1. Во второй форме оператора assert второе Выражение называется Expression2.
Если Expression1 не имеет тип boolean или Boolean, возникает ошибка компиляции.
Если во второй форме оператора assert Expression2 имеет тип void (§15.1), возникает ошибка компиляции.
Оператор assert, который выполняется после завершения инициализации его класса или интерфейса, активируется тогда и только тогда, когда хост-система определила, что верхний уровень класса или интерфейса, лексически содержащий оператор assert, активирует утверждения.
То, активирует ли верхний уровень класса или интерфейса утверждения, определяется не позднее, чем в одном из следующих случаев: (i) инициализация верхнего уровня класса или интерфейса; и (ii) инициализация любого класса или интерфейса, вложенного в верхнем уровне класса или интерфейса. Статус активации утверждений верхнего уровня класса или интерфейса не может быть изменён после того, как он был определён.
Оператор assert, который выполняется до завершения инициализации его класса или интерфейса, активирован.
Данное правило мотивировано случаем, требующим особого обращения. Вспомните, что статус утверждения класса устанавливается не позднее времени его инициализации. Возможно, хотя и нежелательно, выполнение методов или конструкторов до инициализации. Это может произойти, когда иерархия классов содержит цикличность в своей статической инициализации, как в следующем примере:
public class Foo {
public static void main(String[] args) {
Baz.testAsserts();
// Will execute after Baz is initialized.
}
}
class Bar {
static {
Baz.testAsserts();
// Will execute before Baz is initialized!
}
}
class Baz extends Bar {
static void testAsserts() {
boolean enabled = false;
assert enabled = true;
System.out.println("Asserts " +
(enabled ? "enabled" : "disabled"));
}
}
Вызов Baz.testAsserts() приводит к инициализации Baz. Прежде чем это произойдёт, необходимо инициализировать Bar. Статический инициализатор Bar снова вызывает Baz.testAsserts(). Поскольку инициализация Baz уже происходит в текущей нити, второй вызов выполняется немедленно, хотя Baz ещё не инициализирован (§12.4.2).
Из-за вышеприведённого правила, если программа выше выполняется без активации утверждений, она должна вывести:
Asserts enabled Asserts disabled
Деактивированный оператор assert ничего не делает. В частности, ни Expression1, ни Expression2 (если оно присутствует) не оцениваются. Выполнение деактивированного оператора assert всегда завершается нормально.
Активированный оператор assert выполняется путём сначала оценки Expression1. Если результат имеет тип Boolean, он подвергается неявной конверсии (§5.1.8).
Если оценка Expression1 или последующая неявная конвертация (если таковая имеется) завершается аномально по какой-либо причине, оператор assert завершается аномально по той же причине.
В противном случае выполнение продолжается, основываясь на значении Expression1:
-
Если значение равно
true, никаких дальнейших действий не предпринимается, и операторassertзавершается нормально. -
Если значение равно
false, поведение выполнения зависит от наличия Expression2:-
Если Expression2 присутствует, оно оценивается. Затем:
-
Если оценка завершается аномально по какой-либо причине, оператор
assertзавершается аномально по той же причине. -
Если оценка завершается нормально, создаётся объект типа
AssertionError, "сообщение об ошибке" которого соответствует результату оценки Expression2. Затем:-
Если создание объекта завершается аномально по какой-либо причине, оператор
assertзавершается аномально по той же причине. -
Если создание объекта завершается нормально, оператор
assertзавершается аномально путём выбрасывания созданного объекта типаAssertionError.
-
-
-
Если Expression2 отсутствует, создаётся объект типа
AssertionErrorбез "сообщения об ошибке". Затем:-
Если создание объекта завершается аномально по какой-либо причине, оператор
assertзавершается аномально по той же причине. -
Если создание объекта завершается нормально, оператор
assertзавершается аномально путём выбрасывания созданного объекта типаAssertionError.
-
-
Обычно проверка утверждений активируется во время разработки и тестирования программы, и деактивируется для развертывания, чтобы улучшить производительность.
Поскольку утверждения могут быть деактивированы, программы не должны предполагать, что выражения, содержащиеся в утверждениях, будут оцениваться. Таким образом, эти булевы выражения, как правило, должны быть свободны от побочных эффектов. Оценка такого булева выражения не должна влиять на какой-либо состояние, видимое после завершения оценки. Не запрещается, чтобы булево выражение, содержащееся в утверждении, имело побочный эффект, но это, как правило, не рекомендуется, так как это может привести к изменению поведения программы в зависимости от того, были ли утверждения активированы или деактивированы.
В свете этого утверждения не следует использовать для проверки аргументов в методах public. Проверка аргументов обычно является частью контракта метода, и этот контракт должен соблюдаться независимо от того, активированы ли утверждения или деактивированы.
Вторая проблема использования утверждений для проверки аргументов заключается в том, что ошибочные аргументы должны приводить к соответствующему исключению времени выполнения (например, IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Ошибка утверждения не сгенерирует соответствующее исключение. Опять же, не запрещено использовать утверждения для проверки аргументов в методах public, но это, как правило, не рекомендуется. Предполагается, что исключение AssertionError никогда не будет перехвачено, но это возможно, поэтому правила для операторов try должны обрабатывать утверждения, появляющиеся в блоке try аналогично текущему обработке операторов throw.
Оператор switch передает управление одному из нескольких операторов или выражений в зависимости от значения выражения.
Выражение Выражение называется выражением-селектором. Тип выражения-селектора должен быть char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления (§8.9), в противном случае произойдет ошибка компиляции.
Тело как оператора switch, так и выражения switch (§15.28) называется блоком switch. В этом подразделе представлены общие правила, которые применяются ко всем блокам switch, независимо от того, появляются ли они в операторах switch или выражениях switch. Другие подразделы содержат дополнительные правила, которые применяются к блокам switch в операторах switch (§14.11.2) или в выражениях switch (§15.28.1).
Блок switch может состоять из:
-
Правила switch, которые используют
->для введения либо выражения правила switch, либо блока правила switch, либо оператора правила switchthrow; или -
Группы операторов с метками switch, которые используют
:для введения операторов блоков с метками switch.
Каждое правило switch и каждая группа операторов с метками switch начинаются с метки switch, которая является либо меткой case, либо меткой default. Допускается несколько меток switch для одной группы операторов.
Метка case имеет одну или несколько case констант. Каждая case константа должна быть либо константным выражением (§15.29), либо именем константы перечисления (§8.9.1), в противном случае произойдет ошибка компиляции.
Метки switch и их case константы называются связанными с блоком switch. Ни две из case констант, связанных с блоком switch, не могут иметь одинаковое значение, в противном случае произойдет ошибка компиляции.
Блок switch оператора switch или выражения switch совместим с типом выражения-селектора T, если оба следующих утверждения верны:
-
Если T не является типом перечисления, то каждая
caseконстанта, связанная с блоком switch, совместима по присваиванию с T (§5.2). -
Если T является типом перечисления, то каждая
caseконстанта, связанная с блоком switch, является константой перечисления типа T.
Блок switch оператора switch или выражения switch должен быть совместим с типом выражения-селектора, в противном случае произойдет ошибка компиляции.
Как выполнение оператора switch (§14.11.3), так и вычисление выражения switch (§15.28.2) требуют определения, соответствует ли метка switch значению выражения-селектора. Чтобы определить, соответствует ли метка switch в блоке switch заданному значению, значение сравнивается с case константами, связанными с блоком switch. Затем:
-
Если одна из
caseконстант равна значению, то мы говорим, что меткаcase, содержащаяcaseконстанту, соответствует.Равенство определяется с помощью оператора
==(§15.21), за исключением случая, когда значение являетсяString, в этом случае равенство определяется методомequalsклассаString. -
Если ни одна метка
caseне соответствует, но есть меткаdefault, то мы говорим, что меткаdefaultсоответствует.
Метка case может содержать несколько case констант. Метка соответствует значению выражения-селектора, если любая из ее констант соответствует значению выражения-селектора. Например, в следующем коде метка case соответствует, если переменная перечисления day является одной из показанных констант перечисления:
switch (day) {
...
case SATURDAY, SUNDAY :
System.out.println("It's the weekend!");
break;
...
}
null не может быть использована в качестве case константы, потому что она не является константным выражением. Даже если бы case null было разрешено, это было бы нежелательно, так как код в этом case никогда не выполнялся бы. Это связано с тем, что при заданном выражении-селекторе ссылочного типа (то есть String или упакованного примитивного типа или типа перечисления) произойдет исключение, если выражение-селектор вычисляет null во время выполнения. По мнению разработчиков языка программирования Java, распространение исключения является лучшим результатом, чем отсутствие соответствия ни одной метки case или соответствие метке default.
Компилятор Java рекомендуется (но не обязательно) выдать предупреждение, если оператор switch с выражением-селектором типа перечисления не содержит метки default и не содержит меток case для одной или нескольких констант перечисления. Такой оператор switch молча ничего не делает, если выражение принимает одно из отсутствующих значений.
В C и C++ тело оператора switch может быть оператором, и операторы с метками case не должны быть немедленно содержаться этим оператором. Рассмотрим простой цикл:
for (i = 0; i < n; ++i) foo();
где известно, что n положительно. С помощью трюка, известного как устройство Даффа, можно развернуть цикл в C или C++, но это не допустимый код на языке программирования Java:
int q = (n+7)/8;
switch (n%8) {
case 0: do { foo(); // Great C hack, Tom,
case 7: foo(); // but it's not valid here.
case 6: foo();
case 5: foo();
case 4: foo();
case 3: foo();
case 2: foo();
case 1: foo();
} while (--q > 0);
}
К счастью, этот трюк, похоже, не очень широко известен или используется. Кроме того, он сейчас менее нужен; подобная трансформация кода надлежащим образом находится в компетенции современных оптимизирующих компиляторов.
В дополнение к общим правилам для блоков оператора switch (§14.11.1), существуют дополнительные правила для блоков оператора switch в операторах switch. А именно, для блока оператора switch оператора switch должны выполняться все следующие условия, в противном случае возникает ошибка компиляции:
-
Не более одной метки
defaultассоциирована с блокомswitch. -
Каждый выражение правила оператора switch в блоке оператора switch является выражением-оператором (§14.8).
Операторы switch отличаются от выражений switch тем, какие выражения могут находиться справа от стрелки (->) в блоке оператора switch, то есть, какие выражения могут использоваться как выражения правил оператора switch. В операторе switch в качестве выражения правила оператора switch может использоваться только выражение-оператор, а в выражении switch может использоваться любое выражение (§15.28.1).
Оператор switch выполняется путём сначала вычисления выражения-селектора. Затем:
-
Если вычисление выражения-селектора завершается внезапно, то весь оператор
switchзавершается внезапно по той же причине. -
В противном случае, если результат вычисления выражения-селектора равен
null, то генерируется исключениеNullPointerExceptionи весь операторswitchзавершается внезапно по этой причине. -
В противном случае, если результат вычисления выражения-селектора имеет тип
Character,Byte,ShortилиInteger, он преобразуется к примитивному типу (§5.1.8). Если это преобразование завершается внезапно, то весь операторswitchзавершается внезапно по той же причине.
Если вычисление выражения-селектора завершается нормально и результат является не-null, а последующее преобразование к примитивному типу (если таковое имеется) завершается нормально, то выполнение оператора switch продолжается путём определения, соответствует ли метка оператора switch, связанная с блоком оператора switch, значению выражения-селектора (§14.11.1). Тогда:
-
Если ни одна метка оператора switch не совпадает, то весь оператор
switchзавершается нормально. -
Если метка оператора switch совпадает, то выполняется одно из следующих действий:
-
Если это метка для выражения правила оператора switch, то выражение правила оператора switch обязательно является выражением-оператором (§14.11.2). Вычисляется выражение-оператор. Если вычисление завершается нормально, то оператор
switchзавершается нормально. Если результат вычисления имеет значение, оно игнорируется. -
Если это метка для блока правила оператора switch, то выполняется блок. Если этот блок завершается нормально, то оператор
switchзавершается нормально. -
Если это метка для оператора правила оператора switch
throw, то выполняется операторthrow. -
Если это метка для группы меченых операторов, то все операторы в блоке switch, следующие за меткой оператора switch, выполняются в порядке следования. Если эти операторы завершаются нормально, то оператор
switchзавершается нормально. -
В противном случае, операторов, следующих за совпавшей меткой оператора switch в блоке оператора switch, нет, и оператор
switchзавершается нормально.
-
Если выполнение любого оператора или выражения в блоке оператора switch завершается внезапно, оно обрабатывается следующим образом:
-
Если выполнение оператора завершается внезапно из-за
breakбез метки, то дальнейшие действия не выполняются, и операторswitchзавершается нормально.Внезапное завершение из-за
breakс меткой обрабатывается по общему правилу для меченых операторов (§14.7). -
Если выполнение оператора или выражения завершается внезапно по любой другой причине, то оператор
switchзавершается внезапно по той же причине.Внезапное завершение из-за оператора
yieldобрабатывается по общему правилу для выражений switch (§15.28.2).
Пример 14.11.3-1. Пропуск в операторе switch
Когда выражение-селектор соответствует метке оператора switch для правила оператора switch, вычисляется выражение или оператор, введенный этой меткой, и ничего больше. В случае метки оператора switch для группы операторов выполняются все операторы в блоке оператора switch, следующие за этой меткой, включая те, которые появляются после последующих меток оператора switch. Эффект аналогичен C и C++, когда выполнение операторов может "проваливаться" через метки.
Например, программа:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
содержит блок switch, в котором код для каждого case проваливается в код для следующего case. В результате программа выводит:
many too many one too many
Пропуск может быть причиной скрытых ошибок. Если код не должен проваливаться case в case таким образом, то можно использовать операторы break, чтобы указать, когда управление должно передаваться, или можно использовать правила оператора switch, как в программе:
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
static void howManyAgain(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
howManyAgain(1);
howManyAgain(2);
howManyAgain(3);
}
}
Эта программа выводит:
one two many one two many
Оператор while выполняет Выражение и Оператор многократно до тех пор, пока значение Выражения не станет ложным.
Выражение должно иметь тип boolean или тип, преобразуемый к boolean; в противном случае произойдёт ошибка компиляции.
Оператор while выполняется путём вычисления Выражения. Если результат имеет тип примитивного boolean, он подвергается преобразованию к типу boolean.
Если вычисление Выражения или последующее преобразование (если таковое имеется) завершится с ошибкой, то оператор while завершится с ошибкой по той же причине.
В противном случае выполнение продолжается в зависимости от результирующего значения:
-
Если значение равно true, то содержимый Оператор выполняется. Затем выполняется выбор:
-
Если выполнение Оператора завершится нормально, то оператор while выполняется снова, начиная с повторного вычисления Выражения.
-
Если выполнение Оператора завершится с ошибкой, см. §14.12.1.
-
-
Если (возможно, преобразованное) значение Выражения равно false, то никаких дальнейших действий не предпринимается, и оператор while завершается нормально.
Если (возможно, преобразованное) значение Выражения равно false в первый раз, то Оператор не выполняется.
Прерывание выполнения содержащегося Оператора обрабатывается следующим образом:
-
Если выполнение Оператора завершилось с ошибкой из-за оператора break без метки, то никаких дальнейших действий не предпринимается, и оператор while завершается нормально.
-
Если выполнение Оператора завершилось с ошибкой из-за оператора continue без метки, то оператор while выполняется снова.
-
Если выполнение Оператора завершилось с ошибкой из-за оператора continue с меткой
L, то выполняется выбор:-
Если оператор while имеет метку
L, то оператор while выполняется снова. -
Если оператор while не имеет метки
L, то оператор while завершается с ошибкой из-за оператора continue с меткойL.
-
-
Если выполнение Оператора завершилось с ошибкой по любой другой причине, то оператор while завершается с ошибкой по той же причине.
Случай прерывания выполнения из-за оператора continue с меткой обрабатывается общим правилом для помеченных операторов (§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.
Прерывание выполнения содержащегося оператора обрабатывается следующим образом:
-
Если выполнение оператора прервано из-за исключения без метки, дальнейших действий не выполняется, и оператор цикла завершается нормально.
-
Если выполнение оператора прервано из-за исключения без метки, то выполняются следующие два шага последовательно:
-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется другой шаг итерации цикла.
-
-
Если выполнение оператора прервано из-за исключения с меткой
L, то существует выбор:-
Если оператор цикла имеет метку
L, то выполняются следующие два шага последовательно:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, отбрасываются.
Если ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется другой шаг итерации цикла.
-
-
Если оператор цикла не имеет метки
L, оператор цикла прерывается из-за исключения с меткойL.
-
-
Если выполнение оператора прервано по любой другой причине, оператор цикла прерывается по той же причине.
Обратите внимание, что случай прерывания выполнения из-за исключения с меткой обрабатывается общим правилом для операторов с метками (§14.7).
Улучшенный оператор цикла имеет вид:
Следующие производства из §4.3, §8.3, §8.4.1 и §14.4 показаны здесь для удобства:
Тип Выражение должен быть типом массива (§10.1) или подтипом исходного типа Iterable, в противном случае возникает ошибка компиляции.
Заголовок улучшенного оператора цикла объявляет локальную переменную, имя которой задаётся идентификатором ИдентификаторОбъявленияПеременной. При выполнении оператора цикла локальная переменная инициализируется на каждой итерации последовательными элементами массива или массива, полученного из выражения.
Правила для локальной переменной, объявленной в заголовке улучшенного оператора цикла, указаны в §14.4, игнорируя любые правила в этом разделе, которые применяются, когда ТипЛокальнойПеременной является var. Кроме того, должны выполняться все следующие условия, в противном случае возникает ошибка компиляции:
-
СписокОбъявленийПеременных состоит из одной ОбъявленияПеременной.
-
ОбъявленияПеременной не имеет инициализатора.
-
ИдентификаторОбъявленияПеременной не имеет пар скобок, если ТипЛокальнойПеременной является
var.
Область действия и перекрытие локальной переменной, объявленной в заголовке улучшенного оператора цикла, указаны в §6.3 и §6.4.
Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения ограничены, как указано в §6.5.6.1.
Тип T локальной переменной, объявленной в заголовке улучшенного оператора цикла, определяется следующим образом:
-
Если ТипЛокальнойПеременной является НетипизированныйТип, и в НетипизированныйТип или ИдентификаторОбъявленияПеременной нет пар скобок, то T — это тип, обозначенный НетипизированныйТип.
-
Если ТипЛокальнойПеременной является НетипизированныйТип, и в НетипизированныйТип или ИдентификаторОбъявленияПеременной есть пары скобок, то T определяется в §10.2.
-
Если ТипЛокальнойПеременной является
var, то пусть R выводится из типа Выражение следующим образом:-
Если Выражение имеет тип массива, то R — это компонентный тип типа массива.
-
В противном случае, если Выражение имеет тип, являющийся подтипом
Iterable<X>для некоторого типа X, то R — это X. -
В противном случае, Выражение имеет тип, являющийся подтипом исходного типа
Iterable, и R — этоObject.
T является проекцией R по отношению ко всем синтетическим переменным типов, упомянутым в R (§4.10.5).
-
Точное значение улучшенного оператора цикла задаётся переводом в базовый оператор цикла следующим образом:
-
Если тип Выражения является подтипом
Iterable, то основное утверждениеforимеет следующий вид:for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} T Identifier = (TargetType) #i.next(); Statement }
где:
-
Если тип Выражения является подтипом
Iterable<X>для некоторого типа аргумента X, то I — это типjava.util.Iterator<X>. В противном случае, I — это исходный типjava.util.Iterator. -
#i— это автоматически сгенерированный идентификатор, отличный от других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости (§6.3) в момент, когда происходит расширенное утверждениеfor. -
{VariableModifier} задается в заголовке расширенного утверждения
for. -
T — это тип локальной переменной, определенный выше.
-
Если T — это тип ссылки, то TargetType — это T. В противном случае, TargetType — это верхняя граница преобразования захвата (§5.1.10) типа аргумента I, или
Object, если I является исходным.
-
-
В противном случае, Выражение обязательно имеет тип массива S
[], и основное утверждениеforимеет следующий вид:S
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} T Identifier = #a[#i]; Statement }где:
-
L1...Lm— это (возможно, пустая) последовательность меток, непосредственно предшествующих расширенному утверждениюfor. -
#aи#i— это автоматически сгенерированные идентификаторы, отличные от других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости в момент, когда происходит расширенное утверждениеfor. -
{VariableModifier} задается в заголовке расширенного утверждения
for. -
T — это тип локальной переменной, определенный выше.
-
Например, этот код:
List<? extends Integer> l = ... for (float i : l) ...
будет переведен в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
float #i0 = (Integer)#i.next();
...
Пример 14.14-1. Расширенное утверждение for и массивы
Следующая программа, которая вычисляет сумму целочисленного массива, демонстрирует, как расширенное утверждение for работает с массивами:
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
Пример 14.14-2. Расширенное утверждение for и преобразование авто-распаковки
Следующая программа объединяет расширенное утверждение for с преобразованием авто-распаковки для перевода гистограммы в таблицу частот:
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
Оператор 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 может использоваться только в операторе цикла, операторе цикла с условием или операторе цикла с параметрами; операторы этих трёх типов называются операторами итерации. Управление передаётся к точке продолжения цикла оператора итерации.
Существуют два типа оператора continue:
-
Оператор continue без метки.
-
Оператор continue с меткой Идентификатор.
Оператор continue без метки пытается передать управление ближайшему содержащему оператору цикла с условием, цикла с параметрами или оператору цикла с условием; этот содержащий оператор, который называется целью continue, затем немедленно завершает текущую итерацию и начинает новую.
Оператор continue с меткой Идентификатор пытается передать управление содержащему оператору с меткой (§14.7), имеющему тот же Идентификатор в качестве метки; этот содержащий оператор, который называется целью continue, затем немедленно завершает текущую итерацию и начинает новую. В этом случае цель continue должна быть оператором цикла с условием, оператором цикла с параметрами или оператором цикла с условием, иначе возникает ошибка компиляции.
Ошибка компиляции возникает, если у оператора continue нет цели continue.
Ошибка компиляции возникает, если цель continue содержит любой метод, конструктор, инициализатор экземпляра, статический инициализатор, лямбда-выражение или выражение switch, которое включает оператор continue. То есть, нет нелокальных переходов.
Выполнение оператора continue без метки всегда завершается прерыванием, причина - оператор continue без метки.
Выполнение оператора continue с меткой Идентификатор всегда завершается прерыванием, причина - оператор continue с меткой Идентификатор.
Таким образом, оператор continue всегда завершается прерыванием.
См. описания оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки прерывания из-за оператора continue.
В предшествующих описаниях используется "попытка передачи управления", а не просто "передача управления", потому что если существуют операторы try (§14.20) в пределах цели continue, блоки try или операторы catch которых содержат оператор continue, затем любые операторы finally этих операторов try будут выполнены в порядке, от вложенного к внешнему, прежде чем управление передаётся цели continue. Прерывание выполнения оператора finally может нарушить передачу управления, начатую оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла. Этот оператор break можно заменить на оператор continue, если сам цикл помечен меткой:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Выбор между ними, если таковой имеется, в значительной степени зависит от стиля программирования.
Оператор 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 со значением Выражение сначала оценивает Выражение. Если оценка Выражения завершается прерыванием по какой-либо причине, то оператор return завершается прерыванием по этой причине. Если оценка Выражения завершается нормально, порождая значение V, то оператор return завершается прерыванием, причиной является return со значением V.
Таким образом, оператор return всегда завершается прерыванием.
В предшествующих описаниях используется "попытка передачи управления", а не просто "передача управления", потому что если существуют операторы try (§14.20) внутри метода или конструктора, блоки try или операторы catch которых содержат оператор return, затем любые операторы finally этих операторов try будут выполнены в порядке, от вложенного к внешнему, перед тем как управление передаётся вызывающей стороне метода или конструктора. Прерывание выполнения оператора finally может нарушить передачу управления, начатую оператором return.
Оператор throw вызывает исключение (§11 (Исключения)) для обработки. Результатом является мгновенный переход управления (§11.3), который может выйти из нескольких операторов и нескольких инициализаций конструкторов, экземпляров, статических инициализаторов и инициализаторов полей, а также вызовов методов, до тех пор, пока не будет найден оператор try (§14.20), который перехватывает брошенное значение. Если такой оператор try не найден, то выполнение потока (§17 (Потоки и блокировки)), выполнившего оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.
Выражение Выражение в операторе throw должно либо обозначать переменную или значение ссылочного типа, которое присваиваемо (§5.2) типу Throwable, либо обозначать нулевую ссылку, в противном случае возникает ошибка компиляции.
Тип ссылки Выражение всегда будет типом класса (поскольку типы интерфейсов не могут быть присвоены типу Throwable), который не является параметризованным (поскольку подкласс Throwable не может быть обобщённым (§8.1.2)).
Должно выполняться по крайней мере одно из следующих трёх условий, в противном случае возникает ошибка компиляции:
-
Тип Выражение — это класс исключения без проверки (§11.1.1) или тип null (§4.1).
-
Оператор
throwсодержится в блокеtryоператораtry(§14.20), и при этом операторtryне может выбросить исключение типа Выражение. (В этом случае мы говорим, что брошенное значение перехвачено операторомtry). -
Оператор
throwсодержится в объявлении метода или конструктора, и тип Выражение присваивается (§5.2) по крайней мере одному из типов, перечисленных в пунктеthrows(§8.4.6, §8.8.5) объявления.
Типы исключений, которые может выбросить оператор throw, указаны в §11.2.2.
Оператор throw сначала вычисляет Выражение. Затем:
-
Если вычисление Выражения завершается прерывисто по какой-либо причине, то оператор
throwзавершается прерывисто по той же причине. -
Если вычисление Выражения завершается нормально, произведя значение, отличное от
null,V, то операторthrowзавершается прерывисто, причиной чего являетсяthrowсо значениемV. -
Если вычисление Выражения завершается нормально, произведя значение
null, то создаётся экземплярV'классаNullPointerExceptionи выбрасывается вместоnull. Операторthrowзатем завершается прерывисто, причиной чего являетсяthrowсо значениемV'.
Таким образом, оператор throw всегда завершается прерывисто.
Если есть какие-либо окружающие операторы try (§14.20), блоки try которых содержат оператор throw, то любые блоки finally этих операторов try выполняются по мере передачи управления наружу, пока брошенное значение не будет перехвачено. Обратите внимание, что прерывистое завершение блока finally может нарушить передачу управления, начатую оператором throw.
Если оператор throw содержится в объявлении метода или лямбда-выражении, но его значение не перехвачено каким-либо оператором try, который его содержит, то вызов метода завершается прерывисто из-за throw.
Если оператор throw содержится в объявлении конструктора, но его значение не перехвачено каким-либо оператором try, который его содержит, то выражение создания экземпляра класса, вызвавшее конструктор, завершится прерывисто из-за throw (§15.9.4).
Если оператор throw содержится в статическом инициализаторе (§8.7), то проверка во время компиляции (§11.2.3) гарантирует, что либо его значение всегда является исключением без проверки, либо его значение всегда перехватывается каким-либо оператором try, который его содержит. Если во время выполнения, несмотря на эту проверку, значение не перехватывается каким-либо оператором try, который содержит оператор throw, то значение повторно выбрасывается, если оно является экземпляром класса Error или одного из его подклассов; в противном случае оно упаковывается в объект ExceptionInInitializerError, который затем выбрасывается (§12.4.2).
Если оператор throw содержится в инициализаторе экземпляра (§8.6), то проверка во время компиляции (§11.2.3) гарантирует, что либо его значение всегда является исключением без проверки, либо его значение всегда перехватывается каким-либо оператором try, который его содержит, или тип выброшенного исключения (или один из его суперклассов) присутствует в пункте throws каждого конструктора класса.
По соглашению, объявленные пользователем типы исключений обычно должны объявляться подклассами класса Exception, который является подклассом класса Throwable (§11.1.1).
Оператор 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 соответственно. В блоке каждой из n catch, объявленный тип параметра исключения — lub(D1, D2, ..., Dn). Например, следующий код:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
семантически эквивалентен следующему коду:
try {
... throws ReflectiveOperationException ...
}
catch (final ClassNotFoundException ex1) {
final ReflectiveOperationException ex = ex1;
... body ...
}
catch (final IllegalAccessException ex2) {
final ReflectiveOperationException ex = ex2;
... body ...
}
где многострочное catch с двумя альтернативами было преобразовано в две однострочные catch, по одной для каждой альтернативы. Компилятор Java не обязан и не рекомендуется компилировать многострочное catch путём дублирования кода таким образом, так как возможно представить многострочное catch в class файле без дублирования.
finally обеспечивает выполнение блока finally после блока try и любого блока catch, который может быть выполнен, независимо от того, как управление покидает блок try или блок catch. Обработка блока finally достаточно сложна, поэтому два случая оператора try с блоком finally и без него описаны отдельно (§14.20.1, §14.20.2).
Оператор try может опустить catch и finally, если это оператор try-with-resources (§14.20.3).
Оператор try без блока finally выполняется путём сначала выполнения блока try. Затем появляется выбор:
-
Если выполнение блока
tryзавершается нормально, то никаких дальнейших действий не предпринимается, и операторtryзавершается нормально. -
Если выполнение блока
tryзавершается прерывисто из-заthrowзначенияV, то появляется выбор:-
Если тип времени выполнения
Vсовместим с присваиванием (§5.2) с любым классом-исключением, которое можно перехватить, в любойcatchстроке оператораtry, то выбирается первая (самая левая) такаяcatchстрока. ЗначениеVприсваивается параметру выбраннойcatchстроки, и выполняется блок этойcatchстроки, а затем появляется выбор:-
Если этот блок завершается нормально, то оператор
tryзавершается нормально. -
Если этот блок завершается прерывисто по любой причине, то оператор
tryзавершается прерывисто по той же причине.
-
-
Если тип времени выполнения
Vне совместим с присваиванием с классом-исключением, который можно перехватить, в любойcatchстроке оператораtry, то операторtryзавершается прерывисто из-заthrowзначенияV.
-
-
Если выполнение блока
tryзавершается прерывисто по любой другой причине, то операторtryзавершается прерывисто по той же причине.
Пример 14.20.1-1. Перехват исключения
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет две catch строки. Тип времени выполнения исключения — BlewIt, который не может быть присвоен переменной типа RuntimeException, но может быть присвоен переменной типа BlewIt, поэтому выходной результат примера:
Caught BlewIt
Оператор try с блоком finally выполняется путём сначала выполнения блока try. Затем следует выбор:
-
Если выполнение блока
tryзавершается нормально, то выполняется блокfinally, и снова следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается внезапно по причинеS, то операторtryзавершается внезапно по причинеS.
-
-
Если выполнение блока
tryзавершается внезапно из-заthrowзначенияV, то следует выбор:-
Если тип времени выполнения
Vсовместим для присваивания с классом исключения, которое можно перехватить, любого блокаcatchв оператореtry, тогда выбирается первый (самый левый) такой блокcatch. ЗначениеVприсваивается параметру выбранного блокаcatch, и выполняется Блок этого блока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-с-ресурсами параметризуется переменными (известными как ресурсы), которые инициализируются перед выполнением блока try и автоматически закрываются в обратном порядке инициализации после выполнения блока try. Обычно в данном случае, в случае автоматического закрытия ресурсов, необязательны дополнительные пункты catch и пункт finally.
Ниже приведены следующие правила из §4.3, §8.3, §8.4.1 и §14.4 для удобства:
См. §8.3 для НемаркированныйТип.
Спецификация ресурсов обозначает ресурсы оператора try-с-ресурсами, объявляя локальные переменные с инициализаторами или ссылаясь на существующие переменные. Существующая переменная ссылается именем выражения (§6.5.6) или выражением доступа к полю (§15.11).
Правила для локальной переменной, объявленной в спецификации ресурсов, указаны в §14.4. Кроме того, должны выполняться все следующие условия, иначе возникает ошибка компиляции:
-
СписокОбъявленийПеременных состоит из одной ОбъявлениеПеременной.
-
ОбъявлениеПеременной имеет инициализатор.
-
ИдентификаторПеременной не содержит пар скобок.
Область действия и перекрытие локальной переменной, объявленной в спецификации ресурсов, указаны в §6.3 и §6.4.
Ссылки на локальную переменную из вложенного класса или интерфейса, или лямбда-выражения, ограничены, как указано в §6.5.6.1.
Тип локальной переменной, объявленной в спецификации ресурсов, указан в §14.4.1.
Тип локальной переменной, объявленной в спецификации ресурсов, или тип существующей переменной, на которую ссылается спецификация ресурсов, должен быть подтипом AutoCloseable, иначе возникает ошибка компиляции.
Ошибка компиляции возникает, если в спецификации ресурсов объявлены две локальные переменные с одинаковым именем.
Ресурсы final, в том числе:
-
Локальная переменная, объявленная в спецификации ресурсов, неявно объявляется
final, если она не объявлена явноfinal(§4.12.4). -
Существующая переменная, на которую ссылается спецификация ресурсов, должна быть
finalили фактическиfinalпеременной, которая определенно присвоена до оператораtry-с-ресурсами (§16 (Определённое присваивание)), иначе возникает ошибка компиляции.
Ресурсы инициализируются слева направо. Если ресурс не удаётся инициализировать (то есть, выражение его инициализатора вызывает исключение), то все ресурсы, инициализированные до этого оператором try-с-ресурсами, закрываются. Если все ресурсы инициализируются успешно, выполняется блок try, а затем закрываются все не нулевые ресурсы оператора try-с-ресурсами.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он инициализирован ненулевым значением. Исключение при закрытии одного ресурса не препятствует закрытию других ресурсов. Такое исключение подавляется, если ранее было выброшено исключение инициализатором, блоком try или при закрытии ресурса.
Оператор try-с-ресурсами, в котором спецификация ресурсов указывает на несколько ресурсов, обрабатывается так, как будто это несколько операторов try-с-ресурсами, каждый из которых имеет спецификацию ресурсов, указывающую на один ресурс. При переводе оператора try-с-ресурсами с n ресурсами (n > 1) результатом является оператор try-с-ресурсами с n-1 ресурсом. После n таких переводов появляются n вложенных операторов try-catch-finally, и весь перевод завершается.
A try-with-resources statement with no catch clauses or finally clause is called a basic try-with-resources statement.
If a basic try-with-resource statement is of the form:
try (VariableAccess ...)
Block
then the resource is first converted to a local variable declaration by the following translation:
try (T #r = VariableAccess ...) {
Block
}
T is the type of the variable denoted by VariableAccess and #r is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs. The try-with-resources statement is then translated according to the rest of this section.
The meaning of a basic try-with-resources statement of the form:
try ({VariableModifier} R Identifier = Expression ...) Block
is given by the following translation to a local variable declaration and a try-catch-finally statement:
{
final {VariableModifierNoFinal} R Identifier = Expression;
Throwable #primaryExc = null;
try ResourceSpecification_tail
Block
catch (Throwable #t) {
#primaryExc = #t;
throw #t;
} finally {
if (Identifier != null) {
if (#primaryExc != null) {
try {
Identifier.close();
} catch (Throwable #suppressedExc) {
#primaryExc.addSuppressed(#suppressedExc);
}
} else {
Identifier.close();
}
}
}
}
{VariableModifierNoFinal} is defined as {VariableModifier} without final, if present.
#t, #primaryExc, and #suppressedExc are automatically generated identifiers that are distinct from any other identifiers (automatically generated or otherwise) that are in scope at the point where the try-with-resources statement occurs.
If the resource specification indicates one resource, then ResourceSpecification_tail is empty (and the try-catch-finally statement is not itself a try-with-resources statement).
If the resource specification indicates n > 1 resources, then ResourceSpecification_tail consists of the 2nd, 3rd, ..., n'th resources indicated in the resource specification, in the same order (and the try-catch-finally statement is itself a try-with-resources statement).
Reachability and definite assignment rules for the basic try-with-resources statement are implicitly specified by the translation above.
In a basic try-with-resources statement that manages a single resource:
-
If the initialization of the resource completes abruptly because of a
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 valueVwithV2added 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 valueVwith 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 valueVwith 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, then thetry-with-resources statement completes abruptly because of athrowof the valueV1with any remaining valuesV2...Vnadded to the suppressed exception list ofV1(whereV1is the exception from the rightmost resource failing to close andVnis the exception from the leftmost resource failing to close).
-
A try-with-resources statement with at least one catch clause and/or a finally clause is called an extended try-with-resources statement.
The meaning of an extended try-with-resources statement:
try ResourceSpecification Block [Catches] [Finally]
is given by the following translation to a basic try-with-resources statement nested inside a try-catch or try-finally or try-catch-finally statement:
try {
try ResourceSpecification
Block
}
[Catches]
[Finally]
The effect of the translation is to put the resource specification "inside" the try statement. This allows a catch clause of an extended try-with-resources statement to catch an exception due to the automatic initialization or closing of any resource.
Furthermore, all resources will have been closed (or attempted to be closed) by the time the finally block is executed, in keeping with the intent of the finally keyword.
Оператор yield передает управление, заставляя включающее выражение switch (§15.28) генерировать указанное значение.
Оператор yield пытается передать управление во внутреннее включающее выражение switch; это включающее выражение, которое называется целью yield, затем немедленно завершается нормально, и значение Выражения становится значением выражения switch.
Если у оператора yield нет цели yield, возникает ошибка компиляции.
Если цель yield содержит методы, конструкторы, инициализаторы экземпляров, статические инициализаторы или лямбда-выражения, которые включают оператор yield, возникает ошибка компиляции. То есть, нет переходов на нелокальные метки.
Если Выражение оператора yield имеет тип void (§15.1), возникает ошибка компиляции.
Выполнение оператора yield сначала вычисляет Выражение. Если вычисление Выражения завершается прерывисто по какой-либо причине, то оператор yield завершается прерывисто по этой причине. Если вычисление Выражения завершается нормально, произведя значение V, то оператор yield завершается прерывисто, причиной является yield со значением V.
Таким образом, оператор yield всегда завершается прерывисто.
Пример 14.21-1. Оператор yield
В следующем примере оператор yield используется для генерации значения для включающих выражений switch.
class Test {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY, SUNDAY
}
public int calculate(Day d) {
return switch (d) {
case SATURDAY, SUNDAY -> d.ordinal();
default -> {
int len = d.toString().length();
yield len*len;
}
};
}
}
Если оператор не может быть выполнен, потому что он недостижим, возникает ошибка компиляции.
Этот раздел посвящен точному объяснению слова "достижимый". Идея в том, что должен быть некоторый возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, к самому оператору. Анализ учитывает структуру операторов. За исключением специального обращения с операторами while, do и for, условное выражение которых имеет постоянное значение true, значения выражений не учитываются при анализе потока выполнения.
Например, компилятор Java примет код:
{
int n = 5;
while (n > 7) k = 2;
}
даже если значение n известно во время компиляции, и в принципе можно знать во время компиляции, что присваивание k никогда не будет выполнено.
Правила в этом разделе определяют два технических термина:
-
является ли оператор достижимым
-
может ли оператор завершиться нормально
Правила допускают завершение оператора нормально только если он достижим.
Используются ещё два технических термина:
-
Достижимый оператор
breakвыходит из оператора, если внутри цели разрыва нет операторовtry, блоки которых содержат операторbreak, или есть операторыtry, блоки которых содержат операторbreak, и все операторыfinallyэтих операторовtryмогут завершиться нормально.Это определение основано на логике "попытки передачи управления" в §14.15.
-
Оператор
continueпродолжает операторdo, если внутри оператораdoнет операторовtry, блоки которых содержат операторcontinue, или есть операторыtry, блоки которых содержат операторcontinue, и все операторыfinallyэтих операторовtryмогут завершиться нормально.
Правила следующие:
-
Блок, являющийся телом конструктора, метода, инициализатора экземпляра, статического инициализатора, лямбда-выражения или выражения
switch, достижим. -
Пустой блок, не являющийся блоком switch, может завершиться нормально только если он достижим.
Непустой блок, не являющийся блоком switch, может завершиться нормально только если последняя инструкция в нём может завершиться нормально.
Первая инструкция в непустом блоке, не являющемся блоком switch, достижима только если блок достижим.
Любая другая инструкция
Sв непустом блоке, не являющемся блоком switch, достижима только если предшествующая ейSинструкция может завершиться нормально. -
Оператор объявления локального класса может завершиться нормально только если он достижим.
-
Оператор объявления локальной переменной может завершиться нормально только если он достижим.
-
Пустой оператор может завершиться нормально только если он достижим.
-
Меткированные операторы могут завершиться нормально, если выполняется хотя бы одно из следующих условий:
-
Вложенный оператор может завершиться нормально.
-
Существует достижимая
breakинструкция, которая выходит из меткированного оператора.
Вложенный оператор достижим, если достижим меткированный оператор.
-
-
Оператор выражения может завершиться нормально только если он достижим.
-
Оператор
if-thenможет завершиться нормально, только если он достижим.Оператор
thenдостижим, если операторif-thenдостижим.Оператор
if-then-elseможет завершиться нормально, только если операторthenможет завершиться нормально или операторelseможет завершиться нормально.Оператор
thenдостижим, если операторif-then-elseдостижим.Оператор
elseдостижим, если операторif-then-elseдостижим.Обработка оператора
if, независимо от наличия частиelse, является довольно необычной. Обоснование приведено в конце этого раздела. -
Оператор
assertможет завершиться нормально только если он достижим. -
Оператор
switch, у которого блок switch пуст или содержит только метки switch, может завершиться нормально. -
Оператор
switch, блок switch которого состоит из групп операторов с метками switch может завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Последняя инструкция в блоке switch может завершиться нормально.
-
Существует по крайней мере одна метка switch после последней группы операторов блока switch.
-
Существует достижимая
breakинструкция, которая выходит из оператораswitch. -
Блок switch не содержит метки
default.
-
-
Оператор
switch, блок switch которого состоит из правил switch может завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Одно из правил switch вводит выражение правила switch (которое обязательно является выражением-инструкцией).
-
Одно из правил switch вводит блок правила switch, который может завершиться нормально.
-
Одно из правил switch вводит блок правила switch, который содержит достижимую инструкцию
break, которая выходит из оператораswitch. -
Блок switch не содержит метки
default.
-
-
Блок switch достижим, если достижима инструкция
switch. -
Инструкция в блоке switch , состоящем из групп операторов с метками switch достижима, если блок switch достижим и выполняется хотя бы одно из следующих условий:
-
Она имеет метку
caseилиdefault. -
Существует предшествующая ей инструкция в блоке switch, и эта предшествующая инструкция может завершиться нормально.
-
-
Блок правила switch в блоке switch достижим, если достижим блок switch.
-
Оператор правила switch
throwв блоке switch достижим, если достижим блок switch. -
Оператор
whileможет завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Оператор
whileдостижим, и выражение условия не является константным выражением (§15.29) со значениемtrue. -
Существует достижимая
breakинструкция, которая выходит из оператораwhile.
Вложенная инструкция достижима, если оператор
whileдостижим и выражение условия не является константным выражением, значение которого равноfalse. -
-
Оператор
doможет завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Вложенный оператор может завершиться нормально, и выражение условия не является константным выражением (§15.29) со значением
true. -
Оператор
doсодержит достижимый операторcontinueбез метки, и операторdoявляется самым вложенным операторомwhile,doилиfor, содержащим этот операторcontinue, и операторcontinueпродолжает этот операторdo, и выражение условия не является константным выражением со значениемtrue. -
Оператор
doсодержит достижимый операторcontinueс меткойL, и операторdoимеет меткуL, и операторcontinueпродолжает этот операторdo, и выражение условия не является константным выражением со значениемtrue. -
Существует достижимая
breakинструкция, которая выходит из оператораdo.
Вложенный оператор достижим, если оператор
doдостижим. -
-
Основной оператор
forможет завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Оператор
forдостижим, существует выражение условия, и выражение условия не является константным выражением (§15.29) со значениемtrue. -
Существует достижимая
breakинструкция, которая выходит из оператораfor.
Вложенный оператор достижим, если оператор
forдостижим, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Расширенный оператор
forможет завершиться нормально только если он достижим. -
Оператор
break,continue,return,throwилиyieldне может завершиться нормально. -
Оператор
synchronizedможет завершиться нормально, если вложенный оператор может завершиться нормально.Вложенный оператор достижим, если оператор
synchronizedдостижим. -
Оператор
tryможет завершиться нормально, если оба следующих условия верны:-
Блок
tryможет завершиться нормально или любой блокcatchможет завершиться нормально. -
Если у оператора
tryесть блокfinally, то блокfinallyможет завершиться нормально.
-
-
Блок
tryдостижим, если операторtryдостижим.
-
Блок
catchCдостижим, если оба следующих условия верны:-
Тип параметра
C— это тип unchecked exception, илиException, или его суперкласс, или какая-то выражение или операторthrowв блокеtryдостижимо и может выбросить checked exception, тип которого совместим с типом параметраC(согласно §5.2). (Выражение достижимо, если достижим самый внутренний оператор, содержащий это выражение.)См. §15.6 для информации о нормальном и прерывистом завершении оценки выражений.
-
Нет более раннего блока
catchAв оператореtry, в котором тип параметраCсовпадает или является подклассом типа параметраA.
-
-
Блок Block блока
catchдостижим, если достижим блокcatch. -
Если блок
finallyприсутствует, то он достижим, если достижим операторtry.
Можно было бы ожидать, что оператор if обрабатывается следующим образом:
-
Оператор
if-thenможет завершиться нормально, если выполняется хотя бы одно из следующих условий:-
Оператор
if-thenдостижим, и выражение условия — это не константное выражение, значение которого равноtrue. -
Оператор
thenможет завершиться нормально.
Оператор
thenдостижим, если операторif-thenдостижим, и выражение условия — это не константное выражение, значение которого равноfalse. -
-
Оператор
if-then-elseможет завершиться нормально, если операторthenможет завершиться нормально или операторelseможет завершиться нормально.Оператор
thenдостижим, если операторif-then-elseдостижим, и выражение условия — это не константное выражение, значение которого равноfalse.Оператор
elseдостижим, если операторif-then-elseдостижим, и выражение условия — это не константное выражение, значение которого равноtrue.
Этот подход был бы согласован с обработкой других управляющих структур. Однако, чтобы позволить использовать оператор if для удобства «условной компиляции», фактические правила отличаются.
Например, следующий оператор приводит к ошибке компиляции:
while (false) { x=3; }
потому что оператор x=3; недостижим; но в аналогичном случае:
if (false) { x=3; }
ошибка компиляции не возникает. Оптимизирующая компилятор может понять, что оператор x=3; никогда не будет выполнен, и может выбрать исключить код этого оператора из сгенерированного файла class, но оператор x=3; не считается «недостижимым» в техническом смысле, указанном здесь.
Обоснование этого различия в обработке заключается в том, чтобы позволить программистам определять переменные «флагов», такие как:
static final boolean DEBUG = false;
и затем писать код, например:
if (DEBUG) { x=3; }
Идея состоит в том, что должно быть возможно изменить значение DEBUG с false на true или с true на false и затем скомпилировать код правильно без других изменений в тексте программы.
Условной компиляции есть один нюанс. Если набор классов, использующих переменную «флаг» — или точнее, любую переменную константу static (§4.12.4) — компилируется, а условный код опускается, то позже недостаточно просто распространить новую версию класса или интерфейса, содержащую определение флага. Классы, использующие флаг, не увидят его нового значения, поэтому их поведение может быть неожиданным. По сути, изменение значения флага бинарно совместимо с существующими двоичными файлами (никакого LinkageError не происходит), но не поведенчески совместимо.
Еще одна причина «встраивания» значений константных переменных static связана с операторами switch. Они являются единственным типом операторов, которые полагаются на константные выражения, а именно, что каждый метка case оператора switch должна быть константным выражением, значение которого отличается от всех остальных меток case. Метки case часто являются ссылками на переменные константы static, поэтому может быть не сразу очевидно, что все метки имеют разные значения. Если доказано, что дубликатов меток нет во время компиляции, то встраивание значений в файл class гарантирует отсутствие дубликатов меток во время выполнения — очень желательное свойство.
Пример 14.22-1. Условная компиляция
Если пример:
class Flags { static final boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
скомпилирован и запущен, он выведет:
DEBUG is true
Предположим, что создана новая версия класса Flags:
class Flags { static final boolean DEBUG = false; }
Если Flags перекомпилирован, но не Test, то запуск нового двоичного файла с существующим двоичным файлом класса Test выведет:
DEBUG is true
потому что DEBUG — это константная переменная, поэтому её значение могло быть использовано при компиляции 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 для UnannType.
Шаблон типа объявляет одну локальную переменную, известную как переменная шаблона. Идентификатор в объявлении локальной переменной задаёт имя переменной шаблона.
Правила для локальной переменной, объявленной в шаблоне типа, указаны в §14.4. Кроме того, должны быть истинны все следующие утверждения, в противном случае возникает ошибка компиляции:
-
LocalVariableType обозначает тип ссылок (и, кроме того, не является
var). -
VariableDeclaratorList состоит из одного VariableDeclarator.
-
VariableDeclarator не имеет инициализатора.
-
VariableDeclaratorId не имеет пар скобок.
Тип переменной шаблона — это тип ссылки, обозначенный LocalVariableType.
Тип шаблона типа — это тип его переменной шаблона.
Выражение e совместимо с шаблоном типа T, если e может быть приведено к типу T без возникновения исключения (§5.5).
Совместимость выражения с шаблоном используется оператором сопоставления шаблонов instanceof (§15.20.2).
Сопоставление шаблонов — это процесс проверки значения по отношению к шаблону во время выполнения. Сопоставление шаблонов отличается от выполнения операторов (§14.1) и вычисления выражений (§15.1).
Правила определения того, соответствует ли значение шаблону, и инициализации переменных шаблонов таковы:
-
Значение
v, которое не является нулевой ссылкой, соответствует шаблону типа T, еслиvможет быть приведено к T без возникновения исключения; и не соответствует в противном случае.Если
vсоответствует, то переменная шаблона, объявленная шаблоном типа, инициализируетсяv.Если
vне соответствует, то переменная шаблона, объявленная шаблоном типа, не инициализируется.
Нет правила, охватывающего значение, являющееся нулевой ссылкой. Это связано с тем, что единственный конструкт, выполняющий сопоставление шаблонов, оператор сопоставления шаблонов instanceof (§15.20.2), выполняет это только тогда, когда значение не является нулевой ссылкой. В будущих версиях Java возможно добавление сопоставления шаблонов в другие выражения и операторы.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.