Глава 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. Недостижимые операторы
Последовательность выполнения программы контролируется операторами, которые выполняются для своего эффекта и не имеют значений.
Некоторые операторы содержат другие операторы как часть своей структуры; такие другие операторы являются подоператорами оператора. Мы говорим, что оператор S непосредственно содержит оператор U, если нет оператора T, отличного от S и U, такой что S содержит T и T содержит U. Аналогичным образом, некоторые операторы содержат выражения (§15 (Выражения)) как часть своей структуры.
В первом разделе этой главы рассматривается различие между нормальным и прерывистым завершением операторов (§14.1). Большинство оставшихся разделов объясняют различные виды операторов, подробно описывая как их нормальное поведение, так и специальное обращение с прерывистым завершением.
Сначала объясняются блоки (§14.2), а затем объявления локальных классов (§14.3) и операторы объявления локальных переменных (§14.4).
Далее объясняется грамматический прием, который обходит проблему «висящего else» (§14.5).
Последний раздел (§14.21) этой главы рассматривает требование о том, что каждый оператор должен быть досягаемым в определенном техническом смысле.
Каждый оператор имеет нормальный режим выполнения, в котором выполняются определенные вычислительные шаги. В следующих разделах описывается нормальный режим выполнения для каждого типа оператора.
Если все шаги выполняются как описано, без указания прерывистого завершения, оператор считается завершающимся нормально. Однако определенные события могут помешать оператору завершиться нормально:
-
Операторы
break(§14.15),continue(§14.16) иreturn(§14.17) вызывают передачу управления, которая может предотвратить нормальное завершение операторов, которые их содержат. -
Вычисление определенных выражений может генерировать исключения из виртуальной машины Java (§15.6). Явный оператор
throw(§14.18) также приводит к исключению. Исключение вызывает передачу управления, которая может предотвратить нормальное завершение операторов.
Если такое событие происходит, выполнение одного или нескольких операторов может быть прервано до завершения всех шагов их нормального режима выполнения; такие операторы считаются завершающимися прерывисто.
Прерывистое завершение всегда имеет связанное причину, которая является одной из следующих:
-
breakбез метки -
breakс заданной меткой -
continueбез метки -
continueс заданной меткой -
returnбез значения -
returnс заданным значением -
throwс заданным значением, включая исключения, сгенерированные виртуальной машиной Java
Термины «завершение нормально» и «завершение прерывисто» также применяются к вычислению выражений (§15.6). Единственная причина, по которой выражение может завершиться прерывисто, — это сгенерированное исключение из-за либо оператора throw с заданным значением (§14.18), либо исключения времени выполнения или ошибки (§11 (Исключения), §15.6).
Если оператор вычисляет выражение, прерывистое завершение выражения всегда приводит к немедленному прерывистому завершению оператора с той же причиной. Все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное в этой главе, прерывистое завершение подоператора приводит к немедленному прерывистому завершению самого оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное, оператор завершается нормально, если все вычисляемые им выражения и все выполняемые им подоператоры завершаются нормально.
Блок — это последовательность операторов, объявлений локальных классов и объявлений локальных переменных, заключённых в фигурные скобки.
Блок выполняется путем последовательного выполнения операторов объявления локальных переменных и других операторов в порядке следования (слева направо). Если все эти операторы блока завершаются нормально, то и блок завершается нормально. Если любой из этих операторов блока завершается неожиданно по какой-либо причине, то и весь блок завершается неожиданно по той же причине.
Локальный класс — это вложенный класс (§8 (Классы)), который не является членом какого-либо класса и имеет имя (§6.2, §6.7).
Все локальные классы являются внутренними классами (§8.1.3).
Каждое объявление локального класса непосредственно содержится в блоке (§14.2). Объявления локальных классов могут свободно перемешиваться с другими видами операторов в блоке.
Если объявление локального класса содержит какие-либо модификаторы доступа public, protected или private (§6.6), или модификатор static (§8.1.1), то это является ошибкой компиляции.
Область действия и затенение объявления локального класса определяются в §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.
Объявление локальной переменной объявляет одно или несколько имён локальных переменных.
См. §8.3 для ТипБезАннотаций. Ниже для удобства показаны следующие производства из §4.3, §8.3 и §8.4.1:
Каждое объявление локальной переменной находится непосредственно в блоке. Объявления локальных переменных могут свободно перемешиваться с другими типами операторов в блоке.
Помимо объявлений локальных переменных, локальная переменная может быть объявлена в заголовке оператора for (§14.14.1), оператора for (§14.14.2) или оператора try-with-resources (§14.20.3).
Правила аннотационных модификаторов для объявления локальной переменной указаны в §9.7.4 и §9.7.5.
Ошибка компиляции, если 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 появляется в начале объявления, переменная, которую объявляют, является постоянной переменной (§4.12.4).
Тип объявленной локальной переменной определяется следующим образом:
-
Если ТипЛокальнойПеременной является ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторОбъявленияПеременной нет пар скобок, то ТипБезАннотаций обозначает тип локальной переменной.
-
Если ТипЛокальнойПеременной является ТипБезАннотаций, и в ТипБезАннотаций или ИдентификаторОбъявленияПеременной есть пары скобок, то тип локальной переменной указан в §10.2.
-
Если ТипЛокальнойПеременной является
var, то пусть T — тип выражения-инициализатора, рассматриваемого так, как будто оно не присутствует в контексте присваивания и, следовательно, является отдельным выражением (§15.2). Тип локальной переменной — это проекция T вверх относительно всех синтетических типов переменных, упомянутых в T (§4.10.5).Ошибка компиляции, если T — тип null.
Поскольку инициализатор рассматривается так, как будто он не появляется в контексте присваивания, ошибка возникает, если это лямбда-выражение (§15.27) или выражение ссылки на метод (§15.13).
Пример 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, не могут быть объявлены с явным типом, потому что тип переменной не может быть обозначен.
Проекция вверх применяется к типу инициализатора при определении типа переменной. Если тип инициализатора содержит переменные захвата, эта проекция отображает тип инициализатора на супертип, не содержащий переменных захвата.
Хотя было бы возможно разрешить тип переменной, упоминающий переменные захвата, путём их проекции, мы устанавливаем привлекательное свойство, что область действия переменной захвата никогда не превышает оператор, содержащий выражение, тип которого захвачен. Неформально, переменные захвата не могут «просачиваться» в последующие операторы.
Локальная переменная типа float всегда содержит значение, являющееся элементом набора значений с плавающей запятой (§4.2.3); аналогично, локальная переменная типа double всегда содержит значение, являющееся элементом набора значений с двойной точностью. Не допускается, чтобы локальная переменная типа float содержала элемент набора значений с расширенной экспонентой с плавающей запятой, который также не является элементом набора значений с плавающей запятой, ни для локальной переменной типа double, чтобы содержать элемент набора значений с расширенной экспонентой с двойной точностью, который также не является элементом набора значений с двойной точностью.
Область действия и затенение объявления локальной переменной указаны в §6.3 и §6.4.
Оператор объявления локальной переменной является оператором выполнения. Каждый раз при его выполнении деклараторы обрабатываются в порядке слева направо. Если декларатор имеет инициализатор, инициализатор вычисляется, а его значение присваивается переменной.
Если у декларатора нет инициализатора, то каждое обращение к переменной должно предшествовать выполнению присваивания переменной, в противном случае происходит ошибка компиляции по правилам раздела §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 — это assert оператор, содержащий булево выражение. Утверждение может быть активным или выключенным. Если утверждение активно, его выполнение вызывает вычисление булева выражения, и возникает ошибка, если выражение вычисляется как false. Если утверждение выключено, его выполнение никак не влияет на программу.
Для упрощения изложения, первое выражение в обоих формах оператора assert называется выражение1. Во второй форме оператора assert второе выражение называется выражение2.
Если выражение1 не имеет тип boolean или Boolean, возникает ошибка компиляции.
Если во второй форме оператора assert выражение2 является 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 ничего не делает. В частности, ни выражение1, ни выражение2 (если оно присутствует) не вычисляются. Выполнение выключенного утверждения assert всегда завершается нормально.
Активное утверждение assert выполняется путём сначала вычисления выражение1. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если вычисление выражение1 или последующее преобразование распаковки (если таковое имеется) завершается аномально по какой-либо причине, утверждение assert завершается аномально по той же причине.
В противном случае выполнение продолжается, принимая решение на основе значения выражение1:
-
Если значение является
true, дальнейших действий не предпринимается, и утверждениеassertзавершается нормально. -
Если значение является
false, поведение выполнения зависит от того, присутствует ли выражение2:-
Если выражение2 присутствует, оно вычисляется. Затем:
-
Если вычисление завершается аномально по какой-либо причине, утверждение
assertзавершается аномально по той же причине. -
Если вычисление завершается нормально, создаётся экземпляр
AssertionError, в котором «сообщение об ошибке» — это результирующее значение выражение2. Затем:-
Если создание экземпляра завершается аномально по какой-либо причине, утверждение
assertзавершается аномально по той же причине. -
Если создание экземпляра завершается нормально, утверждение
assertзавершается аномально, сбрасывая исключение нового типаAssertionError.
-
-
-
Если выражение2 отсутствует, создаётся экземпляр
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. Любой оператор, непосредственно содержащийся в блоке switch, может быть помечен одной или несколькими метками switch, которые являются case или default метками. У каждой case метки есть case константа, которая является либо константным выражением, либо именем константы перечисления. Метки switch и их case константы называются ассоциированными с оператором switch.
Для оператора switch должны выполняться следующие условия, в противном случае произойдёт ошибка компиляции:
-
Каждая
caseконстанта, связанная с операторомswitch, должна быть совместима с типом Выражения оператораswitch(§5.2). -
Если тип Выражения оператора
switch— тип перечисления, то каждаяcaseконстанта, связанная с операторомswitch, должна быть константой перечисления этого типа. -
Никакие две
caseконстанты, связанные с операторомswitch, не имеют одинакового значения. -
Ни одна
caseконстанта, связанная с операторомswitch, не являетсяnull. -
Максимум одна
defaultметка ассоциирована с операторомswitch.
Запрет использования null в качестве case константы предотвращает создание кода, который никогда не будет выполняться. Если Выражение оператора switch относится к ссылочному типу, то есть String, упакованному примитивному типу или типу перечисления, то будет брошено исключение, если Выражение при выполнении приведёт к значению null. По мнению разработчиков языка Java, это лучше, чем пропустить весь оператор switch или выбрать выполнение операторов (если есть) после метки default (если есть).
Компилятор Java рекомендуется (но не обязан) выдавать предупреждение, если в операторе switch с выражением типа перечисления отсутствует метка default и отсутствуют case метки для одной или нескольких констант перечисления. Такой оператор ничего не сделает, если выражение приведёт к одному из пропущенных значений.
В 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 выполняется путём сначала вычисления Выражения. Если Выражение вычислится к null, будет брошено NullPointerException, и весь оператор switch завершится преждевременно по этой причине. В противном случае, если результат имеет тип Character, Byte, Short или Integer, он подвергается распаковке (§5.1.8).
Если вычисление Выражения или последующая распаковка (если применимо) завершаются преждевременно по какой-либо причине, оператор switch завершается преждевременно по той же причине.
В противном случае выполнение продолжается путём сравнения значения Выражения с каждой case константой, и есть выбор:
-
Если одна из
caseконстант равна значению выражения, то мы говорим, чтоcaseметка соответствует. Равенство определяется оператором==(§15.21), если значение выражения не являетсяString, в этом случае равенство определяется методомequalsклассаString.Все операторы после соответствующей
caseметки в блокеswitch, если таковые имеются, выполняются последовательно.Если все эти операторы завершаются нормально, или если операторов после соответствующей
caseметки нет, то весь операторswitchзавершается нормально. -
Если ни одна
caseметка не соответствует, но существуетdefaultметка, то все операторы после меткиdefaultв блокеswitch, если таковые имеются, выполняются последовательно.Если все эти операторы завершаются нормально, или если операторов после метки
defaultнет, то весь операторswitchзавершается нормально. -
Если ни одна
caseметка не соответствует и нетdefaultметки, то никаких дальнейших действий не производится, и операторswitchзавершается нормально.
Если любой оператор, непосредственно содержащийся в теле блока Block оператора switch, завершается преждевременно, то это обрабатывается следующим образом:
-
Если выполнение оператора Statement завершается преждевременно из-за
breakбез метки, то никаких дальнейших действий не производится, и операторswitchзавершается нормально. -
Если выполнение оператора Statement завершается преждевременно по какой-либо другой причине, то оператор
switchзавершается преждевременно по той же причине.Случай преждевременного завершения из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Пример 14.11-1. Пропускание в операторе switch
Как и в C и C++, выполнение операторов в блоке switch "проваливается" через метки.
Например, программа:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
содержит блок switch, в котором код для каждой case проваливается в код для следующей case. В результате программа выводит:
many too many one too many
Если код не должен проваливаться case в case таким образом, то следует использовать операторы break, как в этом примере:
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
}
}
Эта программа выводит:
one two many
Оператор цикла while выполняет Выражение и Оператор многократно, пока значение Выражения не будет false.
Выражение должно иметь тип boolean или Boolean, в противном случае произойдёт ошибка компиляции.
Оператор цикла while выполняется следующим образом: сначала вычисляется Выражение. Если результат имеет тип Boolean, он подвергается приведению к типу значения (§5.1.8).
Если вычисление Выражения или последующее приведение к типу значения (если таковое имеется) завершается прерыванием по какой-либо причине, оператор while также завершается прерыванием по той же причине.
В противном случае выполнение продолжается, принимая решение на основе полученного значения:
-
Если значение равно
true, то выполняется содержащийся Оператор. Затем происходит выбор:-
Если выполнение Оператора завершается нормально, то весь оператор цикла while выполняется снова, начиная с повторного вычисления Выражения.
-
Если выполнение Оператора завершается прерыванием, см. §14.12.1.
-
-
Если (возможно, приведённое к типу значения) значение Выражения равно
false, никаких дальнейших действий не предпринимается, и оператор while завершается нормально.Если (возможно, приведённое к типу значения) значение Выражения равно
falseпри первом вычислении, то Оператор не выполняется.
Прерывание содержащегося Оператора обрабатывается следующим образом:
-
Если выполнение Оператора завершается прерыванием из-за
breakбез метки, никаких дальнейших действий не предпринимается, и оператор while завершается нормально. -
Если выполнение Оператора завершается прерыванием из-за
continueбез метки, то весь оператор цикла while выполняется снова. -
Если выполнение Оператора завершается прерыванием из-за
continueс меткойL, то происходит выбор:-
Если оператор while имеет метку
L, то весь оператор while выполняется снова. -
Если оператор while не имеет метки
L, оператор while завершается прерыванием из-заcontinueс меткойL.
-
-
Если выполнение Оператора завершается прерыванием по любой другой причине, оператор while завершается прерыванием по той же причине.
Случай прерывания из-за
breakс меткой обрабатывается общим правилом для меченых операторов (§14.7).
Оператор do выполняет Оператор и Выражение повторно до тех пор, пока значение Выражения не будет false.
Выражение должно иметь тип boolean или Boolean, иначе произойдёт ошибка компиляции.
Оператор do выполняется путём сначала выполнения Оператора. Затем следует выбор:
-
Если выполнение Оператора завершается нормально, то вычисляется Выражение. Если результат имеет тип
Boolean, он подвергается распаковке (§5.1.8).Если вычисление Выражения или последующая распаковка (если она есть) завершается аномально по какой-либо причине, оператор
doзавершается аномально по той же причине.В противном случае, выбор зависит от полученного значения:
-
Если значение равно
true, то весь операторdoвыполняется снова. -
Если значение равно
false, никаких дальнейших действий не предпринимается, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершается аномально, см. §14.13.1.
Выполнение оператора do всегда выполняет содержащийся Оператор хотя бы один раз.
Аномальное завершение содержащегося Оператора обрабатывается следующим образом:
-
Если выполнение Оператора завершается аномально из-за
breakбез метки, то никаких дальнейших действий не предпринимается, и операторdoзавершается нормально. -
Если выполнение Оператора завершается аномально из-за
continueбез метки, то вычисляется Выражение. Затем следует выбор, основанный на полученном значении:-
Если значение равно
true, то весь операторdoвыполняется снова. -
Если значение равно
false, никаких дальнейших действий не предпринимается, и операторdoзавершается нормально.
-
-
Если выполнение Оператора завершается аномально из-за
continueс меткойL, то следует выбор:-
Если оператор
doимеет меткуL, то вычисляется Выражение. Затем следует выбор:-
Если значение Выражения равно
true, то весь операторdoвыполняется снова. -
Если значение Выражения равно
false, никаких дальнейших действий не предпринимается, и операторdoзавершается нормально.
-
-
Если оператор
doне имеет меткиL, операторdoзавершается аномально из-заcontinueс меткойL.
-
-
Если выполнение Оператора завершается аномально по любой другой причине, оператор
doзавершается аномально по той же причине.Случай аномального завершения из-за
breakс меткой обрабатывается общим правилом для помеченных операторов (§14.7).
Пример 14.13-1. Оператор do
Следующий код — одно из возможных реализаций метода toHexString класса Integer:
public static String toHexString(int i) {
StringBuffer buf = new StringBuffer(8);
do {
buf.append(Character.forDigit(i & 0xF, 16));
i >>>= 4;
} while (i != 0);
return buf.reverse().toString();
}
Поскольку должен быть сгенерирован хотя бы один символ, оператор do является подходящей управляющей структурой.
Оператор for имеет два вида:
-
Основной оператор
for. -
Улучшенный оператор
for
Оператор основного цикла for выполняет некоторую начальную обработку, затем выполняет выражение, оператор и некоторую обновляющую обработку многократно, пока значение выражения не станет false.
Выражение Expression должно иметь тип boolean или Boolean; в противном случае произойдёт ошибка компиляции.
Область видимости и перекрытие локальной переменной, объявленной в части ForInit оператора основного цикла for, указаны в §6.3 и §6.4.
Оператор цикла for выполняется путём сначала выполнения кода ForInit:
-
Если код ForInit представляет собой список выражений операторов (§14.8), выражения вычисляются в последовательности слева направо; их значения, если таковые имеются, отбрасываются.
Если вычисление любого выражения завершается аномально по какой-либо причине, оператор цикла
forзавершается аномально по той же причине; любые выражения операторов ForInit справа от завершившегося аномально выражения не вычисляются. -
Если код ForInit представляет собой объявление локальной переменной (§14.4), он выполняется так, как если бы это был оператор объявления локальной переменной, появляющийся в блоке.
Если выполнение объявления локальной переменной завершается аномально по какой-либо причине, оператор цикла
forзавершается аномально по той же причине. -
Если часть ForInit отсутствует, никаких действий не предпринимается.
Далее выполняется шаг итерации for:
-
Если присутствует Выражение, оно вычисляется. Если результат имеет тип
Boolean, он преобразуется в значение примитивного типа (§5.1.8).Если вычисление Выражения или последующее преобразование в примитивный тип (если таковое имеется) завершается аномально, оператор цикла
forзавершается аномально по той же причине.В противном случае выбор зависит от наличия или отсутствия Выражения и полученного значения, если Выражение присутствует; см. следующий пункт.
-
Если Выражение отсутствует или оно присутствует, и значение, полученное в результате его вычисления (включая возможное преобразование в примитивный тип), равно
true, то содержимый Оператор выполняется. Затем производится выбор:-
Если выполнение Оператора завершается нормально, то выполняются следующие два действия последовательно:
-
Во-первых, если присутствует часть ForUpdate, выражения вычисляются в последовательности слева направо; их значения, если таковые имеются, отбрасываются. Если вычисление любого выражения завершается аномально по какой-либо причине, оператор цикла
forзавершается аномально по той же причине; любые выражения операторов ForUpdate справа от завершившегося аномально выражения не вычисляются.Если часть ForUpdate отсутствует, никаких действий не предпринимается.
-
Во-вторых, выполняется другой шаг итерации
for.
-
-
Если выполнение Оператора завершается аномально, см. §14.14.1.3.
-
-
Если Выражение присутствует и значение, полученное в результате его вычисления (включая возможное преобразование в примитивный тип), равно
false, никаких дальнейших действий не предпринимается, и оператор циклаforзавершается нормально.Если значение (возможно, преобразованное в примитивный тип) Выражения равно
falseв первый раз при его вычислении, то Оператор не выполняется.
Если Выражение отсутствует, то единственный способ, которым оператор цикла for может завершиться нормально, — это использование оператора break.
Прерывание выполнения содержащегося Оператор обрабатывается следующим образом:
-
Если выполнение Оператор завершается прерыванием из-за
breakбез метки, дальнейшие действия не выполняются, и операторforзавершается нормально. -
Если выполнение Оператор завершается прерыванием из-за
continueбез метки, последовательно выполняются два следующих шага:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются.
Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется другой шаг итерации
for.
-
-
Если выполнение Оператор завершается прерыванием из-за
continueс меткойL, существует выбор:-
Если оператор
forимеет меткуL, последовательно выполняются два следующих шага:-
Сначала, если присутствует часть ForUpdate, выражения вычисляются последовательно слева направо; их значения, если таковые имеются, игнорируются.
Если часть ForUpdate отсутствует, никаких действий не выполняется.
-
Во-вторых, выполняется другой шаг итерации
for.
-
-
Если оператор
forне имеет меткиL, операторforзавершается прерыванием из-заcontinueс меткойL.
-
-
Если выполнение Оператор завершается прерыванием по любой другой причине, оператор
forзавершается прерыванием по той же причине.Обратите внимание, что случай прерывания из-за
breakс меткой обрабатывается общим правилом для меченых операторов (§14.7).
Улучшенный цикл for имеет вид:
Следующие правила из §4.3, §8.3, §8.4.1 и §14.4 приведены здесь для удобства:
Заголовок улучшенного цикла for объявляет локальную переменную, имя которой — идентификатор, заданный VariableDeclaratorId.
Если ключевое слово final появляется в начале объявления, то объявляемая переменная — переменная final (§4.12.4).
Ошибка компиляции, если LocalVariableType — var, а VariableDeclaratorId имеет одну или несколько пар скобок.
Тип Expression должен быть подтипом исходного типа Iterable или массива (§10.1), в противном случае произойдёт ошибка компиляции.
Тип локальной переменной определяется следующим образом:
-
Если LocalVariableType — UnannType, и в UnannType или VariableDeclaratorId нет пар скобок, то UnannType задаёт тип локальной переменной.
-
Если LocalVariableType — UnannType, и в UnannType или VariableDeclaratorId есть пары скобок, то тип локальной переменной определяется в §10.2.
-
Если LocalVariableType —
var, то пусть T определяется из типа Expression следующим образом:-
Если Expression имеет тип массива, то T — компонентный тип массива.
-
В противном случае, если тип Expression является подтипом
Iterable<X>для некоторого типа X, то T — X. -
В противном случае, тип Expression является подтипом исходного типа
Iterable, и T —Object.
Тип локальной переменной — проекция T вверх относительно всех синтетических типов, упомянутых в T (§4.10.5).
-
Область видимости и перекрытие локальной переменной определены в §6.3 и §6.4.
При выполнении улучшенного цикла for локальная переменная инициализируется на каждой итерации цикла последовательными элементами массива или объекта, полученного по выражению. Точное значение улучшенного цикла for даётся переводом в оператор basic, как показано ниже:
-
Если тип Expression является подтипом
Iterable, то перевод выполняется следующим образом.Если тип Expression является подтипом
Iterable<X>для некоторого аргумента типа X, то пусть I — типjava.util.Iterator<X>; иначе, пусть I — исходный типjava.util.Iterator.Улучшенный цикл for эквивалентен оператору basic следующего вида:
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} TargetType Identifier = (TargetType) #i.next(); Statement }
#i— автоматически сгенерированный идентификатор, отличающийся от любых других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости (§6.3) в точке, где находится улучшенный цикл for.Если объявленный тип локальной переменной в заголовке улучшенного цикла for — ссылочный тип, то TargetType — этот объявленный тип; в противном случае, TargetType — верхняя граница преобразования захвата (§5.1.10) аргумента типа I или
Object, если I — исходный тип.Например, этот код:
List<? extends Integer> l = ... for (float i : l) ...будет переведён в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); ... -
В противном случае, Expression обязательно имеет тип массива T
[].Пусть
L1...Lm— (возможно, пустая) последовательность меток, непосредственно предшествующих улучшенному циклу for.Улучшенный цикл for эквивалентен оператору basic следующего вида:
T
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { {VariableModifier} TargetType Identifier = #a[#i]; Statement }#aи#i— автоматически сгенерированные идентификаторы, отличающиеся от любых других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости в точке, где находится улучшенный цикл for.TargetType — объявленный тип локальной переменной в заголовке улучшенного цикла for.
Пример 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 без метки пытается передать управление самому внутреннему вложенному оператору switch, while, do или for в непосредственно окружающем методе или инициализаторе; этот оператор, который называется целью оператора break, затем немедленно завершается нормально.
Точнее, оператор break без метки всегда завершается прерывисто, по причине оператора break без метки.
Если ни один оператор switch, while, do или for в непосредственно окружающем методе, конструкторе или инициализаторе не содержит оператор break, возникает ошибка компиляции.
Оператор break с меткой Identifier пытается передать управление вложенному оператору с меткой (§14.7), который имеет ту же метку Identifier; этот оператор, который называется целью оператора break, затем немедленно завершается нормально. В этом случае цель оператора break не обязательно должна быть оператором switch, while, do или for.
Точнее, оператор break с меткой Identifier всегда завершается прерывисто, по причине оператора break с меткой Identifier.
Оператор break должен ссылаться на метку внутри непосредственно окружающего метода, конструктора, инициализатора или блока лямбда-выражения. Не существует нелокальных переходов. Если ни один оператор с меткой Identifier в непосредственно окружающем методе, конструкторе, инициализаторе или блоке лямбда-выражения не содержит оператор break, возникает ошибка компиляции.
Из этого следует, что оператор break всегда завершается прерывисто.
В предыдущих описаниях используется фраза «пытается передать управление», а не просто «передает управление», потому что если внутри цели оператора break есть любые операторы try (§14.20), блоки try или разделы catch которых содержат оператор break, то любые разделы finally этих операторов try выполняются в порядке, от самого внутреннего к самому внешнему, прежде чем управление передается цели оператора break. Прерывистое завершение раздела finally может нарушить передачу управления, начатую оператором break.
Пример 14.15-1. Оператор break
В приведенном примере математический граф представлен массивом массивов. Граф состоит из набора узлов и набора ребер; каждое ребро представляет стрелку, указывающую из некоторого узла в другой узел или из узла в себя. В этом примере предполагается, что ребра не повторяются; то есть для любых двух узлов P и Q, где Q может быть таким же, как P, существует не более одного ребра от P к Q.
Узлы представлены целыми числами, и существует ребро от узла i к узлу edges[ для каждого i][j]i и j, для которых ссылка на массив edges[ не генерирует i][j]ArrayIndexOutOfBoundsException.
Задача метода loseEdges, учитывая целые числа i и j, состоит в построении нового графа путем копирования заданного графа, но исключая ребро от узла i к узлу j, если оно есть, и ребро от узла j к узлу i, если оно есть:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
for (int k = 0; k < n; ++k) {
edgelist:
{
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
break edgelist;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelist
}
return new Graph(newedges);
}
}
Обратите внимание на использование двух меток операторов, edgelist и search, и использование операторов break. Это позволяет коду, копирующему список, исключая одно ребро, использоваться в двух отдельных тестах: тесте для ребра от узла i к узлу j и тесте для ребра от узла j к узлу i.
Оператор continue может встречаться только в операторе while, do или for; операторы этих трёх типов называются операторными циклами. Управление передается точке продолжения цикла операторного цикла.
Оператор continue без метки пытается передать управление самому внутреннему вложенному оператору while, do или for в непосредственно окружающем методе, конструкторе или инициализаторе; этот оператор, который называется целью оператора continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, такой оператор continue всегда завершается прерывисто, по причине оператора continue без метки.
Если ни один оператор while, do или for в непосредственно окружающем методе, конструкторе или инициализаторе не содержит оператор continue, возникает ошибка компиляции.
Оператор continue с меткой Identifier пытается передать управление вложенному оператору с меткой (§14.7), который имеет ту же метку Identifier; этот оператор, который называется целью оператора continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, оператор continue с меткой Identifier всегда завершается прерывисто, по причине оператора continue с меткой Identifier.
Цель оператора continue должна быть оператором while, do или for, в противном случае возникает ошибка компиляции.
Оператор continue должен ссылаться на метку внутри непосредственно окружающего метода, конструктора, инициализатора или блока лямбда-выражения. Не существует нелокальных переходов. Если ни один оператор с меткой Identifier в непосредственно окружающем методе, конструкторе, инициализаторе или блоке лямбда-выражения не содержит оператор continue, возникает ошибка компиляции.
Из этого следует, что оператор continue всегда завершается прерывисто.
См. описания оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки прерывистого завершения из-за оператора continue.
В предыдущих описаниях используется фраза «пытается передать управление», а не просто «передает управление», потому что если внутри цели оператора continue есть любые операторы try (§14.20), блоки try или разделы catch которых содержат оператор continue, то любые разделы finally этих операторов try выполняются в порядке, от самого внутреннего к самому внешнему, прежде чем управление передается цели оператора continue. Прерывистое завершение раздела finally может нарушить передачу управления, начатую оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла for. Этот оператор break может быть заменён оператором continue, если сам цикл for имеет метку:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Выбор того или иного варианта в основном зависит от стиля программирования.
Оператор return возвращает управление вызывающей стороне метода (§8.4), конструктора (§8.8) или лямбда-выражения (§15.12, §15.9, §15.27).
Оператор return вложен внутри самого внутреннего конструктора, метода, инициализатора или лямбда-выражения, тело которого содержит этот оператор.
Если оператор return находится внутри инициализатора экземпляра или статического инициализатора (§8.6, §8.7), возникает ошибка компиляции.
Оператор return без Выражения должен находиться в одном из следующих контекстов, иначе возникает ошибка компиляции:
Оператор return без Выражения пытается передать управление вызывающей стороне метода, конструктора или лямбда-выражения, содержащего этот оператор. Точнее, оператор return без Выражения всегда завершается внезапно, по причине возврата без значения.
Оператор return с Выражением должен находиться в одном из следующих контекстов, иначе возникает ошибка компиляции:
-
Метод, объявленный как возвращающий значение
-
Лямбда-выражение
Выражение должно обозначать переменную или значение, иначе возникает ошибка компиляции.
Если оператор return с Выражением находится в методе, то Выражение должно быть присваиваемым (§5.2) типу возвращаемого значения этого метода, иначе возникает ошибка компиляции.
Оператор return с Выражением пытается передать управление вызывающей стороне метода или лямбда-выражения, содержащего оператор; значение Выражения становится значением результата вызова метода. Точнее, выполнение оператора return с Выражением сначала оценивает Выражение. Если оценка Выражения завершается внезапно по какой-либо причине, то оператор return также завершается внезапно по той же причине. Если оценка Выражения завершается нормально, произведя значение V, то оператор return завершается внезапно, по причине возврата со значением V.
Если выражение имеет тип float и не является FP-строгим (§15.4), то значение может быть элементом либо набора значений типа float, либо расширенного набора значений с плавающей точкой (§4.2.3). Если выражение имеет тип double и не является FP-строгим, то значение может быть элементом либо набора значений типа double, либо расширенного набора значений с плавающей точкой.
Таким образом, оператор return всегда завершается внезапно.
В описаниях используется фраза «пытается передать управление», а не просто «передает управление», потому что если есть операторы try (§14.20) внутри метода или конструктора, блоки try или catch которых содержат оператор return, то любые finally блоки этих операторов будут выполнены в порядке от внутреннего к внешнему, прежде чем управление будет передано вызывающей стороне метода или конструктора. Внезапное завершение блока finally может нарушить передачу управления, инициированную оператором return.
Оператор throw вызывает исключение (§11 (Исключения)) для выбрасывания. Результатом является непосредственная передача управления (§11.3), которая может выйти из нескольких операторов и нескольких инициализаций конструкторов, инициализаций экземпляров, статических инициализаций и инициализаций полей, а также вызовов методов до тех пор, пока не будет найден оператор try (§14.20), который перехватывает значение, сгенерированное оператором `throw`. Если такой оператор try не найден, то выполнение потока (§17 (Потоки и блокировки)), который выполнил оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.
Выражение Выражение в операторе throw должно либо обозначать переменную или значение ссылочного типа, которое можно присвоить (§5.2) типу Throwable, или обозначать нулевую ссылку, в противном случае возникает ошибка компиляции.
Ссылочный тип Выражение всегда будет типом класса (так как типы интерфейсов не могут быть присвоены типу Throwable), который не является параметризованным (поскольку подкласс Throwable не может быть обобщённым (§8.1.2)).
Должно выполняться хотя бы одно из трёх следующих условий, в противном случае возникает ошибка компиляции:
-
Тип Выражения является классом необрабатываемых исключений (§11.1.1) или нулевым типом (§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. -
В противном случае, пусть ненулевое значение Выражение будет
V. Выполняющая нить блокирует монитор, связанный сV. Затем выполняется Блок, а затем выбирается один из вариантов:-
Если выполнение Блок завершится нормально, то монитор разблокируется, и оператор synchronized завершится нормально.
-
Если выполнение Блок завершится внезапно по какой-либо причине, то монитор разблокируется, и оператор synchronized завершится внезапно по той же причине.
-
Блокировки, приобретаемые операторами synchronized, такие же, как блокировки, которые неявно приобретаются синхронизированными методами (§8.4.3.6). Одна нить может приобретать блокировку более одного раза.
Приобретение блокировки, связанной с объектом, само по себе не препятствует другим нитям обращаться к полям объекта или вызывать не-синхронизированные методы объекта. Другие нити также могут использовать синхронизированные методы или оператор synchronized стандартным способом для достижения взаимного исключения.
Пример 14.19-1. Оператор synchronized
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
Эта программа выводит:
made it!
Обратите внимание, что эта программа зациклится, если одной нити не разрешено блокировать монитор более одного раза.
Оператор try выполняет блок кода. Если возникает исключение, и оператор try имеет одну или несколько операндов catch, способных его перехватить, то управление передаётся первому такому операнду catch. Если оператор try имеет операнд finally, то выполняется другой блок кода, независимо от того, завершился ли блок try нормально или абортивно, и независимо от того, получил ли операнд catch управление первым.
См. §8.3 для UnannClassType. Следующие правила из §4.3, §8.3 и §8.4.1 показаны здесь для удобства:
Блок Блок непосредственно после ключевого слова try называется блоком try оператора try.
Блок Блок непосредственно после ключевого слова finally называется блоком finally оператора try.
Оператор try может иметь catch операнды, также называемые обработчиками исключений.
Обработчик catch объявляет ровно один параметр, который называется параметром исключения.
Если final встречается более одного раза в качестве модификатора объявления параметра исключения, это является ошибкой компиляции.
Область видимости и перекрытие параметров исключения определены в §6.3 и §6.4.
Параметр исключения может обозначать свой тип как один тип класса или объединение двух или более типов классов (называемые альтернативами). Альтернативы объединения разделяются |.
Обработчик 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 или быть эффективно final (§4.12.4).
Неявно объявленный final параметр исключения является final по умолчанию, а эффективно final — final в силу своего использования. Параметр исключения многотипного catch блока неявно объявляется final, поэтому он никогда не будет левым операндом оператора присваивания, но не считается эффективно final.
Если параметр исключения эффективно final (в однотипном catch блоке) или неявно final (в многотипном catch блоке), то добавление явного final модификатора к его объявлению не приведёт к ошибкам компиляции. С другой стороны, если параметр исключения однотипного catch блока явно объявлен final, то удаление модификатора final может привести к ошибкам компиляции, так как параметр исключения, теперь считающийся эффективно final, больше не может использоваться объявленными анонимными и локальными классами в теле catch блока. Если ошибок компиляции нет, то можно изменить программу так, чтобы параметр исключения был повторно присвоен в теле catch блока, и, таким образом, он больше не будет считаться эффективно final.
Типы исключений, которые может сгенерировать оператор try, описаны в §11.2.2.
Взаимосвязь исключений, сгенерированных try блоком оператора try и перехваченных catch блоками (если таковые имеются) оператора try описана в §11.2.3.
Обработчики исключений рассматриваются слева направо: самый ранний catch блок принимает исключение, получая в качестве аргумента объект исключения, как указано в §11.3.
Многоисключительный блок можно рассматривать как последовательность одноисключительных блоков. То есть, блок с типом параметра исключения, обозначаемого как объединение D1|D2|...|Dn, эквивалентен последовательности из n одноисключительных блоков, где типы параметров исключения соответственно равны D1, D2, ..., Dn. В блоке Block каждого из n одноисключительных блоков объявленный тип параметра исключения — 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 ...
}
где многоисключительный блок с двумя вариантами был преобразован в два одноисключительных блока, по одному для каждого варианта. Компилятор Java не обязан и не рекомендуется компилировать многоисключительный блок, дублируя код таким образом, поскольку можно представить многоисключительный блок в файле без дублирования.
Блок `catch` гарантирует, что блок `catch` выполняется после блока `try` и любого блока `finally`, независимо от того, каким образом управление покидает блок `try` или блок `finally`. Обработка блока `finally` достаточно сложна, поэтому два случая оператора `try` со и без блока `finally` описаны отдельно (§14.20.1, §14.20.2).
Оператор `try` может опускать блоки `catch` и `finally`, если это оператор `try-with-resources` (§14.20.3).
Оператор `try` без блока `finally` выполняется путём сначала выполнения блока `try`. Затем возникает выбор:
-
Если выполнение блока `try` завершается нормально, то никаких дальнейших действий не выполняется, и оператор `try` завершается нормально.
-
Если выполнение блока `try` завершается аномально из-за исключения с значением
V, то возникает выбор:-
Если тип времени выполнения
Vсовместим по присваиванию (§5.2) с классом исключения любого блока `catch` оператора `try`, то выбирается первый (самый левый) такой блок `catch`. ЗначениеVприсваивается параметру выбранного блока `catch`, и выполняется блок Block этого блока `catch`, а затем возникает выбор:-
Если этот блок завершается нормально, то оператор `try` завершается нормально.
-
Если этот блок завершается аномально по любой причине, то оператор `try` завершается аномально по той же причине.
-
-
Если тип времени выполнения
Vне совместим по присваиванию с классом исключения любого блока `catch` оператора `try`, то оператор `try` завершается аномально из-за исключения со значением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 завершается прерывисто из-за исключения значения
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, то выполняется блок else. Затем следует выбор:-
Если блок else завершается нормально, то оператор try завершается прерывисто из-за исключения значения
V. -
Если блок else завершается прерывисто по причине
S, то оператор try завершается прерывисто по причинеS(и исключение значения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. Это приводит к выполнению блока catch, после чего поток, выполняющий main, единственный в тестовой программе, завершается из-за неперехваченного исключения, что обычно приводит к выводу имени исключения и краткого стека вызовов. Однако этот документ не требует краткого стека вызовов.
Проблема с требованием краткого стека вызовов заключается в том, что исключение может быть создано в одной точке программы и брошено в другой. Сохранение стека вызовов в исключении неэффективно, если оно не выбрасывается (в этом случае стек может быть сгенерирован при разворачивании стека). Поэтому мы не требуем краткий стек вызовов для каждого исключения.
Оператор try-with-resources параметризован локальными переменными (известными как ресурсы), которые инициализируются перед выполнением блока try и автоматически закрываются, в обратном порядке их инициализации, после выполнения блока try. Блоки catch и finally часто не нужны, когда ресурсы закрываются автоматически.
Приведены следующие правила из §8.4.1 и §14.4 для удобства:
В спецификации ресурсов используются переменные для обозначения ресурсов для оператора try-with-resources, либо путём объявления локальных переменных с инициализирующими выражениями, либо путём ссылки на существующие переменные. Существующая переменная ссылается либо с помощью имени выражения (§6.5.6), либо с помощью выражения доступа к полю (§15.11).
Ошибка компиляции, если в спецификации ресурсов объявлены две переменные с одинаковым именем.
Ошибка компиляции, если модификатор final появляется более одного раза для каждой переменной, объявленной в спецификации ресурсов.
Переменная, объявленная в спецификации ресурсов, неявно объявляется final, если она не объявлена явно final (§4.12.4).
Ресурс, обозначенный именем выражения или выражением доступа к полю, должен быть final или эффективно final переменной, которая определённо присваивается перед оператором try-with-resources (§16 (Определённое присваивание)), иначе возникает ошибка компиляции.
Ошибка компиляции, если LocalVariableType переменной, объявленной в спецификации ресурсов, является var, а выражение инициализации содержит ссылку на переменную.
Тип переменной, объявленной в спецификации ресурсов, определяется следующим образом:
-
Если LocalVariableType является UnannType, то UnannType обозначает тип локальной переменной.
-
Если LocalVariableType является
var, то пусть T будет типом выражения инициализации, когда оно рассматривается как самостоятельное выражение, не появляющееся в контексте присваивания (§15.2). Тип локальной переменной — это проекция вверх T по всем синтетическим типам переменных, упоминаемых T (§4.10.5).Ошибка компиляции, если T является типом null.
Тип переменной, объявленной или используемой в качестве ресурса в спецификации ресурсов, должен быть подтипом AutoCloseable, иначе произойдёт ошибка компиляции.
Область действия и перекрытие переменной, объявленной в спецификации ресурсов, указаны в §6.3 и §6.4.
Ресурсы инициализируются слева направо. Если инициализация ресурса завершается ошибкой (то есть выражение инициализации выбрасывает исключение), то все ресурсы, инициализированные до этого оператором try-with-resources, закрываются. Если все ресурсы инициализируются успешно, блок try выполняется как обычно, а затем закрываются все непустые ресурсы оператора try-with-resources.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он был инициализирован непустым значением. Исключение, выброшенное при закрытии одного ресурса, не препятствует закрытию других ресурсов. Такое исключение подавляется, если ранее было выброшено исключение из инициализатора, блока try или при закрытии ресурса.
Оператор try-with-resources, в котором спецификация ресурсов указывает на несколько ресурсов, рассматривается как несколько операторов try-with-resources, каждый из которых имеет спецификацию ресурсов, указывающую на один ресурс. При переводе оператора try-with-resources с n ресурсами (n > 1), результатом является оператор try-with-resources с n-1 ресурсами. После n таких переводов получаются вложенные операторы try-with-resources, и перевод завершается.
Оператор try-with-resources без блоков catch или finally называется базовым оператором try-with-resources.
Если базовый оператор try-with-resource имеет вид:
try (VariableAccess ...)
Block
то ресурс сначала преобразуется в объявление локальной переменной следующим образом:
try (T #r = VariableAccess ...) {
Block
}
T — тип переменной, обозначаемой VariableAccess, а #r — автоматически сгенерированное имя, отличное от всех других имен (автоматически сгенерированных или других), видимых в области действия оператора try-with-resources. Оператор try-with-resources затем преобразуется в соответствии с остальной частью этого раздела.
Значение базового оператора try-with-resources вида:
try ({VariableModifier} R Identifier = Expression ...) Block
задаётся следующим переводом в объявление локальной переменной и оператор try-catch-finally:
{
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} определяется как {VariableModifier} без final, если оно присутствует.
#t, #primaryExc и #suppressedExc — автоматически сгенерированные имена, отличные от всех других имен (автоматически сгенерированных или других), видимых в области действия оператора try-with-resources.
Если спецификация ресурсов указывает один ресурс, то ResourceSpecification_tail пусто (и оператор try-catch-finally сам по себе не является оператором try-with-resources).
Если спецификация ресурсов указывает более одного ресурса, то ResourceSpecification_tail содержит второй, третий и последующие ресурсы из спецификации в том же порядке (и оператор try-catch-finally сам по себе является оператором try-with-resources).
Правила достижимости и определённости присвоения для базового оператора try-with-resources неявно заданы вышеуказанным переводом.
В базовом операторе try-with-resources, управляющем одним ресурсом:
-
Если инициализация ресурса завершается аномально из-за исключения значения
V, то оператор try-with-resources завершается аномально из-за исключения значенияV. -
Если инициализация ресурса завершается нормально, а блок try завершается аномально из-за исключения значения
V, то:-
Если автоматическое закрытие ресурса завершается нормально, то оператор try-with-resources завершается аномально из-за исключения значения
V. -
Если автоматическое закрытие ресурса завершается аномально из-за исключения значения
V2, то оператор try-with-resources завершается аномально из-за исключения значенияVс добавлениемV2в список подавленных исключений дляV.
-
-
Если инициализация ресурса завершается нормально, блок try завершается нормально, а автоматическое закрытие ресурса завершается аномально из-за исключения значения
V, то оператор try-with-resources завершается аномально из-за исключения значенияV.
В базовом операторе try-with-resources, управляющем несколькими ресурсами:
-
Если инициализация ресурса завершается аномально из-за исключения значения
V, то:-
Если автоматические закрытия всех успешно проинициализированных ресурсов (возможно, нулевого количества) завершаются нормально, то оператор try-with-resources завершается аномально из-за исключения значения
V. -
Если автоматические закрытия всех успешно проинициализированных ресурсов (возможно, нулевого количества) завершаются аномально из-за исключений значений
V1...Vn, то оператор try-with-resources завершается аномально из-за исключения значенияVс добавлением всех оставшихся значенийV1...Vnв список подавленных исключений дляV.
-
-
Если инициализация всех ресурсов завершается нормально, а блок try завершается аномально из-за исключения значения
V, то:-
Если автоматические закрытия всех инициализированных ресурсов завершаются нормально, то оператор try-with-resources завершается аномально из-за исключения значения
V. -
Если автоматические закрытия одного или более инициализированных ресурсов завершаются аномально из-за исключений значений
V1...Vn, то оператор try-with-resources завершается аномально из-за исключения значенияVс добавлением всех оставшихся значенийV1...Vnв список подавленных исключений дляV.
-
-
Если инициализация всех ресурсов завершается нормально, а блок try завершается нормально, то:
-
Если одно автоматическое закрытие инициализированного ресурса завершается аномально из-за исключения значения
V, а все другие автоматические закрытия инициализированных ресурсов завершаются нормально, то оператор try-with-resources завершается аномально из-за исключения значенияV. -
Если более одного автоматического закрытия инициализированного ресурса завершаются аномально из-за исключений значений
V1...Vn, то оператор try-with-resources завершается аномально из-за исключения значенияV1с добавлением всех оставшихся значенийV2...Vnв список подавленных исключений дляV1(гдеV1— исключение от завершения работы правого ресурса, аVn— исключение от завершения работы левого ресурса).
-
Оператор try-with-resources с хотя бы одним блоком catch и/или блоком finally называется расширенным оператором try-with-resources.
Значение расширенного оператора try-with-resources:
try ResourceSpecification Block [Catches] [Finally]
задаётся следующим переводом в базовый оператор try-with-resources, вложенный в оператор try-catch-finally или оператор try-catch или оператор try-finally-catch:
try {
try ResourceSpecification
Block
}
[Catches]
[Finally]
Эффект перевода заключается в том, что спецификация ресурсов оказывается "внутри" оператора try. Это позволяет блоку catch расширенного оператора try-with-resources перехватывать исключения, возникающие при автоматической инициализации или закрытии ресурсов.
Кроме того, все ресурсы будут закрыты (или попытка закрытия будет произведена) к моменту выполнения блока try, что соответствует назначению ключевого слова try.
Если оператор не может быть выполнен, так как он недоступен, это ошибка времени компиляции.
Этот раздел посвящен точному объяснению слова «доступен». Идея заключается в том, что должен быть какой-либо возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, до самого оператора. Анализ учитывает структуру операторов. За исключением специального обращения с операторами while, do и for, условное выражение которых имеет постоянное значение true, значения выражений не учитываются при анализе потока.
Например, компилятор Java примет код:
{
int n = 5;
while (n > 7) k = 2;
}
даже если значение n известно во время компиляции и, в принципе, известно, что присвоение k никогда не будет выполнено.
Правила в этом разделе определяют два технических термина:
-
является ли оператор доступным
-
может ли оператор нормально завершиться
Определение здесь позволяет оператору нормально завершиться только если он доступен.
Для краткости описания правил используется общепринятое сокращение «iff», означающее «если и только если».
Доступный break оператор выходит из оператора, если внутри целевой метки break нет операторов try, блоки try которых содержат оператор break, или есть операторы try, блоки try которых содержат оператор break, и все finally предложения этих операторов try могут завершиться нормально.
Это определение основано на логике вокруг «попыток передачи управления» в §14.15.
Оператор continue продолжает оператор do, если внутри оператора do нет операторов try, блоки try которых содержат оператор continue, или есть операторы try, блоки try которых содержат оператор continue, и все finally предложения этих операторов try могут завершиться нормально.
Правила следующие:
-
Тело конструктора, метода, инициализатора экземпляра или статического инициализатора достижимо.
-
Пустой блок, не являющийся блоком 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 не содержит метку
default. -
Существует достижимый оператор
break, который выходит из оператораswitch.
-
-
Блок switch достижим тогда и только тогда, когда достижим его оператор
switch. -
Оператор в блоке switch достижим тогда и только тогда, когда достижим его оператор
switchи верно хотя бы одно из следующих условий:-
Он имеет метку
caseилиdefault. -
Перед ним в блоке switch находится оператор, и этот предыдущий оператор может завершиться нормально.
-
-
Оператор
whileможет завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
while-оператор достижим, и выражение условия не является константным выражением (§15.28) со значениемtrue. -
Существует достижимый оператор
break, который выходит из оператораwhile.
Содержащийся оператор достижим тогда и только тогда, когда
while-оператор достижим, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Оператор
doможет завершиться нормально тогда и только тогда, когда верно хотя бы одно из следующих условий:-
Содержащийся оператор может завершиться нормально, и выражение условия не является константным выражением (§15.28) со значением
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.28) со значениемtrue. -
Существует достижимый оператор
break, который выходит из оператораfor.
Содержащийся оператор достижим тогда и только тогда, когда
for-оператор достижим, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Расширенный оператор
forможет завершиться нормально тогда и только тогда, когда он достижим. -
Оператор
break,continue,returnилиthrowне может завершиться нормально. -
Оператор
synchronizedможет завершиться нормально тогда и только тогда, когда содержащийся оператор может завершиться нормально.Содержащийся оператор достижим тогда и только тогда, когда достижим
synchronized-оператор. -
Оператор
tryможет завершиться нормально тогда и только тогда, когда оба следующих условия истинны:-
Блок
tryможет завершиться нормально, или любой блокcatchможет завершиться нормально. -
Если у оператора
tryесть блокfinally, то блокfinallyможет завершиться нормально.
-
-
Блок
tryдостижим тогда и только тогда, когда достижим операторtry. -
Блок
catchCдостижим тогда и только тогда, когда оба следующих условия истинны:-
Либо тип параметра
Cявляется типом непроверенного исключения, либоExceptionили суперклассException, либо некоторое выражение или операторthrowв блокеtryдостижимы и могут выбросить проверенное исключение, тип которого присваивается типу параметраC. (Выражение достижимо тогда и только тогда, когда достижим самый внутренний оператор, содержащий его.)См. §15.6 для нормального и внезапного завершения выражений.
-
Нет более раннего блока
catchAв оператореtry, такого, что тип параметраCтакой же, как или является подклассом типа параметраA.
-
-
Блок Block блока
catchдостижим тогда и только тогда, когда достижим блокcatch. -
Если присутствует блок
finally, он достижим тогда и только тогда, когда достижим операторtry.
Можно ожидать, что оператор if будет обработан следующим образом:
-
Оператор
if-thenможет завершиться нормально, если истинно хотя бы одно из следующих условий:-
Оператор
if-thenдоступен, и выражение условия не является константным выражением, значение которого равноtrue. -
Оператор
thenможет завершиться нормально.
Оператор
thenдоступен, если операторif-thenдоступен, и выражение условия не является константным выражением, значение которого равноfalse. -
-
Оператор
if-then-elseможет завершиться нормально, если операторthenможет завершиться нормально или операторelseможет завершиться нормально.Оператор
thenдоступен, если операторif-then-elseдоступен, и выражение условия не является константным выражением, значение которого равноfalse.Оператор
elseдоступен, если операторif-then-elseдоступен, и выражение условия не является константным выражением, значение которого равноtrue.
Этот подход был бы согласован с обработкой других управляющих структур. Однако, чтобы разрешить использование оператора if для целей "условной компиляции", фактические правила отличаются.
Например, следующий оператор приводит к ошибке компиляции:
while (false) { x=3; }
потому что оператор x=3; недоступен; но в похожем случае:
if (false) { x=3; }
ошибка компиляции не возникает. Оптимизирующая компилятор может понять, что оператор x=3; никогда не будет выполнен и может выбрать опустить код этого оператора из сгенерированного файла class, но оператор x=3; не считается "недоступным" в техническом смысле, указанном здесь.
Обоснование такого различия в обработке заключается в том, чтобы позволить программистам определять переменные "флагов", такие как:
static final boolean DEBUG = false;
и затем писать код, такой как:
if (DEBUG) { x=3; }
Идея в том, что можно изменить значение DEBUG с false на true или с true на false, а затем скомпилировать код правильно без других изменений в тексте программы.
Условной компиляции сопутствует оговорка. Если набор классов, использующих переменную "флаг" — или, точнее, любую static константную переменную (§4.12.4) — скомпилированы, и условный код пропущен, то позднее недостаточно просто распространить новую версию класса или интерфейса, содержащего определение флага. Классы, использующие флаг, не увидят его нового значения, поэтому их поведение может быть неожиданным. По сути, изменение значения флага совместимо с бинарными предварительно существующими двоичными файлами (нет LinkageError возникает), но не совместимо с поведением.
Еще одна причина "встраивания" значений static константных переменных заключается в switch операторах. Это единственный тип оператора, который полагается на константные выражения, а именно, что каждый case метка оператора switch должна быть константным выражением, значение которого отличается от каждого другого case метки. case метки часто являются ссылками на static константные переменные, поэтому сразу не очевидно, что все метки имеют разные значения. Если на этапе компиляции доказано, что нет дублирующихся меток, то встраивание значений в файл class гарантирует отсутствие дублирующихся меток и во время выполнения — очень желательное свойство.
Пример 14.21-1. Условная компиляция
Если пример:
class Flags { static final boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
скомпилирован и выполнен, он выводит:
DEBUG is true
Предположим, что создана новая версия класса Flags:
class Flags { static final boolean DEBUG = false; }
Если Flags перекомпилирован, но не Test, то запуск нового двоичного файла с существующим двоичным файлом Test выводит:
DEBUG is true
потому что DEBUG является static константной переменной, поэтому ее значение могло быть использовано при компиляции Test без ссылки на класс Flags.
Это поведение также будет иметь место, если Flags был интерфейсом, как в измененном примере:
interface Flags { boolean DEBUG = true; }
class Test {
public static void main(String[] args) {
if (Flags.DEBUG)
System.out.println("DEBUG is true");
}
}
Фактически, поскольку поля интерфейсов всегда static и final, рекомендуется, чтобы полям интерфейсов назначались только константные выражения. Заметим, но не рекомендуем, что если поле примитивного типа интерфейса может измениться, его значение может быть выражено идиоматично, как в:
interface Flags {
boolean debug = Boolean.valueOf(true).booleanValue();
}
убеждаясь, что это значение не является константным выражением. Подобные идиомы существуют для других примитивных типов.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.