Глава 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).
Если оператор вычисляет выражение, прерывистое завершение выражения всегда вызывает немедленное прерывистое завершение оператора с той же причиной. Все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное в этой главе, прерывистое завершение подоператора вызывает немедленное прерывистое завершение самого оператора с той же причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.
Если не указано иное, оператор завершается нормально, если все вычисляемые им выражения и все выполняемые подоператоры завершаются нормально.
Блок — это последовательность операторов, локальных объявлений классов и объявлений локальных переменных внутри фигурных скобок.
Блок:
{ BlockStatementsopt }
BlockStatements:
BlockStatement
BlockStatements BlockStatement
BlockStatement:
LocalVariableDeclarationStatement
ClassDeclaration
Statement
Блок выполняется путём последовательного выполнения объявлений локальных переменных и других операторов от первого до последнего (слева направо). Если все операторы блока завершаются нормально, то и сам блок завершается нормально. Если какой-либо из этих операторов завершается абортивно по любой причине, то и весь блок завершается абортивно по той же причине.
Локальный класс — это вложенный класс (§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.
Объявление локальной переменной — это оператор, объявляющий одно или несколько имён локальных переменных.
LocalVariableDeclarationStatement:
LocalVariableDeclaration ;
LocalVariableDeclaration:
VariableModifiersopt Type VariableDeclarators
Следующее повторяется из §8.4.1 и §8.3, чтобы сделать изложение здесь более ясным:
VariableModifiers:
VariableModifier
VariableModifiers VariableModifier
VariableModifier: один из
Annotation final
VariableDeclarators:
VariableDeclarator
VariableDeclarators , VariableDeclarator
VariableDeclarator:
VariableDeclaratorId
VariableDeclaratorId = VariableInitializer
VariableDeclaratorId:
Identifier
VariableDeclaratorId []
VariableInitializer:
Expression
ArrayInitializer
Каждое объявление локальной переменной находится непосредственно внутри блока. Объявления локальных переменных могут быть свободно перемешаны с другими операторами в блоке.
Объявление локальной переменной также может появиться в заголовке оператора for (§14.14). В этом случае оно выполняется так же, как если бы оно было частью оператора объявления локальной переменной.
Каждый декларатор в объявлении локальной переменной объявляет одну локальную переменную, имя которой — идентификатор, который появляется в деклараторе.
Если ключевое слово final появляется в начале декларатора, то объявляемая переменная — это конечная переменная (§4.12.4).
Если аннотация a (§9.7) на объявлении локальной переменной соответствует типу аннотации T, и T имеет (мета-)аннотацию m, которая соответствует java.lang.annotation.Target, то m должен иметь элемент со значением java.lang.annotation.ElementType.LOCAL_VARIABLE; в противном случае возникает ошибка компиляции.
Тип объявленной локальной переменной обозначается типом Type, который появляется в объявлении локальной переменной, за которым следуют любые скобки, следующие за Identifier в деклараторе.
Локальная переменная типа float всегда содержит значение, являющееся элементом множества значений типа float (§4.2.3); аналогично, локальная переменная типа double всегда содержит значение, являющееся элементом множества значений типа double. Не допускается, чтобы локальная переменная типа float содержала элемент множества значений с расширенным экспоненциальным форматом типа float, который не является также элементом множества значений типа float, и для локальной переменной типа double не допускается содержать элемент множества значений с расширенным экспоненциальным форматом типа double, который не является также элементом множества значений типа 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, к которой она, возможно, относится. Это правило отражено в следующей грамматике:
Управляющая инструкция:
Управляющая инструкция без последующей подинструкции
Инструкция с меткой
Инструкция if с ветвью then
Инструкция if с ветвями then и else
Инструкция while
Инструкция for
Управляющая инструкция без последующей подинструкции:
Блок
Пустая инструкция
Инструкция выражения
Инструкция assert
Инструкция switch
Инструкция do
Инструкция break
Инструкция continue
Инструкция return
Инструкция synchronized
Инструкция throw
Инструкция try
Инструкция без краткой формы if:
Управляющая инструкция без последующей подинструкции
Инструкция с меткой без краткой формы if
Инструкция if с ветвями then и else без краткой формы if
Инструкция while без краткой формы if
Инструкция for без краткой формы if
Следующее повторяется из §14.9, чтобы сделать изложение здесь более ясным:
Инструкция if с ветвью then:
if ( Выражение ) Управляющая инструкция
Инструкция if с ветвями then и else:
if ( Выражение ) Инструкция без краткой формы if else Управляющая инструкция
Инструкция if с ветвями then и else без краткой формы if:
if ( Выражение ) Инструкция без краткой формы if else Инструкция без краткой формы if
Таким образом, управляющие инструкции грамматически делятся на две категории: те, которые могут заканчиваться инструкцией без клаузы else ("короткая инструкция if"), и те, которые определенно не могут.
Только управляющие инструкции, которые определенно не заканчиваются короткой инструкцией if, могут появляться как непосредственная подинструкция перед ключевым словом else в инструкции if, которая имеет клаузу else.
Это простое правило предотвращает проблему "висящей else". Поведение инструкции с ограничением "без короткой формы if" идентично поведению такой же инструкции без ограничения "без короткой формы if"; различие вводится исключительно для решения синтаксической проблемы.
Пустая инструкция ничего не делает.
Пустая инструкция:
;
Выполнение пустой инструкции всегда завершается нормально.
Инструкции могут иметь префиксы метки.
Инструкция с меткой:
Идентификатор : Управляющая инструкция
Инструкция с меткой без краткой формы if:
Идентификатор : Инструкция без краткой формы if
Идентификатор объявляется меткой непосредственно содержащейся Управляющей инструкции.
В отличие от C и C++, язык программирования Java не имеет инструкции goto; метки идентификаторов используются с инструкциями break (§14.15) или continue (§14.16), появляющимися где угодно внутри инструкции с меткой.
Область действия метки инструкции с меткой — непосредственно содержащаяся Управляющая инструкция.
Ошибка компиляции, если имя метки инструкции с меткой (§14.7) используется в области действия метки в качестве метки другой инструкции с меткой.
Нет ограничений на использование одного и того же идентификатора в качестве метки и в качестве имени пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора для метки инструкции не скрывает (§6.4.2) пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же именем. Использование идентификатора как класса, интерфейса, метода, поля, локальной переменной или в качестве параметра обработчика исключений (§14.20) не скрывает метку инструкции с тем же именем.
Инструкция с меткой выполняется путём выполнения непосредственно содержащейся Управляющей инструкции.
Если инструкция имеет метку с Идентификатором и содержащая Управляющая инструкция завершается прерывисто из-за инструкции break с тем же Идентификатором, то инструкция с меткой завершается нормально. Во всех других случаях прерывистого завершения Управляющей инструкции, инструкция с меткой завершается прерывисто по той же причине.
Определенные виды выражений могут использоваться как инструкции, если за ними следуют точки с запятой.
Инструкция выражения:
Выражение инструкции ;
Выражение инструкции:
Присваивание
Выражение прединкремента
Выражение преддекремента
Выражение постинкремента
Выражение постдекремента
Вызов метода
Выражение создания экземпляра класса
Инструкция выражения выполняется путём оценки выражения; если выражение имеет значение, значение отбрасывается.
Выполнение инструкции выражения завершается нормально тогда и только тогда, когда оценка выражения завершается нормально.
В отличие от C и C++, язык программирования Java позволяет использовать только определенные формы выражений в качестве инструкций выражения. Обратите внимание, что язык программирования Java не допускает "приведения к void" - void не является типом - поэтому традиционный трюк C с написанием инструкции выражения, например:
(void)... ; // incorrect!
не работает. С другой стороны, язык программирования Java допускает все самые полезные виды выражений в инструкциях выражений и не требует, чтобы вызов метода, используемого как инструкция выражения, вызывал метод void, поэтому такой трюк почти никогда не нужен. Если нужен трюк, вместо него можно использовать инструкцию присваивания (§15.26) или инструкцию объявления локальной переменной (§14.4).
Оператор if позволяет условно выполнить оператор или выбрать один из двух операторов, выполнив один из них, но не оба.
IfThenStatement:
if ( Выражение ) Оператор
IfThenElseStatement:
if ( Выражение ) ОператорNoShortIf else Оператор
IfThenElseStatementNoShortIf:
if ( Выражение ) ОператорNoShortIf else ОператорNoShortIf
У Выражения должен быть тип boolean или Boolean, в противном случае произойдёт ошибка компиляции.
Оператор if-then выполняется путём предварительной оценки Выражения. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если оценка Выражения или последующее преобразование распаковки (если таковое имеется) завершается внезапно по какой-либо причине, оператор if-then завершается внезапно по той же причине.
В противном случае выполнение продолжается путём выбора в зависимости от полученного значения:
-
Если значение равно
true, то содержащийся Оператор выполняется; операторif-thenзавершается нормально только в том случае, если выполнение Оператора завершается нормально. -
Если значение равно
false, никаких дальнейших действий не предпринимается, и операторif-thenзавершается нормально.
Оператор if-then-else выполняется путём предварительной оценки Выражения. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если оценка Выражения или последующее преобразование распаковки (если таковое имеется) завершается внезапно по какой-либо причине, оператор if-then-else завершается внезапно по той же причине.
В противном случае выполнение продолжается путём выбора в зависимости от полученного значения:
-
Если значение равно
true, то первый содержащийся Оператор (тот, что перед ключевым словомelse) выполняется; операторif-then-elseзавершается нормально только в том случае, если выполнение этого оператора завершается нормально. -
Если значение равно
false, то второй содержащийся Оператор (тот, что после ключевого словаelse) выполняется; операторif-then-elseзавершается нормально только в том случае, если выполнение этого оператора завершается нормально.
Оператор assert — это оператор, содержащий булево выражение. Оператор может быть активирован или деактивирован. Если оператор активирован, выполнение оператора приводит к оценке булева выражения, и если выражение оценивается как false, то генерируется ошибка. Если оператор деактивирован, его выполнение не оказывает никакого влияния.
AssertStatement:
assert Expression1 ;
assert Expression1 : Expression2 ;
Ошибка компиляции возникает, если Expression1 не имеет тип boolean или Boolean.
Во второй форме оператора assert, ошибка компиляции возникает, если Expression2 имеет тип void (§15.1).
Оператор assert, выполняемый после завершения инициализации своего класса, активирован тогда и только тогда, когда система определила, что класс верхнего уровня, в котором лексически содержится оператор assert, активирует операторы assert.
Вопрос о том, активирует ли класс верхнего уровня операторы assert, решается не позднее, чем в момент инициализации класса верхнего уровня или инициализации любого класса, вложенного в класс верхнего уровня, и после того, как решение принято, оно не может быть изменено.
Оператор assert, выполняемый до завершения инициализации своего класса, активирован.
Это правило мотивировано случаем, требующим особого подхода. Вспомните, что статус 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).
Из-за правила выше, если программа выше выполняется без активации операторов assert, она должна вывести:
Asserts enabled Asserts disabled
Деактивированный оператор assert не выполняет никаких действий. В частности, ни Expression1, ни Expression2 (если оно присутствует) не оцениваются. Выполнение деактивированного оператора assert всегда завершается нормально.
Активированный оператор assert выполняется путём сначала оценки Expression1. Если результат имеет тип Boolean, он преобразуется к своему примитивному представлению (§5.1.8).
Если оценка Expression1 или последующее преобразование (если таковое имеется) завершается аварийно по какой-либо причине, оператор assert завершается аварийно по той же причине.
В противном случае выполнение продолжается, основываясь на значении Expression1:
-
Если значение равно
true, дальнейшие действия не выполняются, и операторassertзавершается нормально. -
Если значение равно
false, поведение выполнения зависит от наличия Expression2:-
Если Expression2 присутствует, оно оценивается.
-
Если оценка завершается аварийно по какой-либо причине, оператор
assertзавершается аварийно по той же причине. -
Если оценка завершается нормально, создаётся экземпляр
AssertionError, у которого "сообщение об ошибке" — результат оценки Expression2.-
Если создание экземпляра завершается аварийно по какой-либо причине, оператор
assertзавершается аварийно по той же причине. -
Если создание экземпляра завершается нормально, оператор
assertзавершается аварийно путём выбрасывания созданного объектаAssertionError.
-
-
-
Если Expression2 отсутствует, создаётся экземпляр
AssertionErrorбез "сообщения об ошибке".-
Если создание экземпляра завершается аварийно по какой-либо причине, оператор assert завершается аварийно по той же причине.
-
Если создание экземпляра завершается нормально, оператор assert завершается аварийно путём выбрасывания созданного объекта
AssertionError.
-
-
Например, после извлечения всех аргументов из буфера данных программист может утверждать, что количество оставшихся байтов данных в буфере равно нулю. Проверяя, что булево выражение действительно true, система подтверждает знания программиста о программе и повышает уверенность в том, что программа свободна от ошибок.
Обычно проверка операторов assert активируется во время разработки и тестирования и отключается при развертывании для повышения производительности.
Так как операторы assert могут быть отключены, программы не должны предполагать, что выражения, содержащиеся в операторах assert, будут оценены. Таким образом, эти булевы выражения, как правило, должны быть свободны от побочных эффектов.
Вычисление такого булева выражения не должно влиять на какой-либо состояние, видимый после завершения вычисления. Не является ошибкой, если булево выражение, содержащееся в операторе assert, имеет побочный эффект, но это, как правило, не рекомендуется, поскольку это может привести к изменению поведения программы в зависимости от того, были ли операторы assert включены или отключены.
Аналогично, операторы assert не следует использовать для проверки аргументов в методах public. Проверка аргументов обычно является частью контракта метода, и этот контракт должен быть соблюдён независимо от того, включены ли операторы assert или отключены.
Ещё одна проблема использования операторов assert для проверки аргументов заключается в том, что некорректные аргументы должны вызывать соответствующее исключение во время выполнения (например, IllegalArgumentException, ArrayIndexOutOfBoundsException или NullPointerException). Ошибка assert не выбросит соответствующее исключение. Опять же, использование операторов assert для проверки аргументов в методах public не является ошибкой, но, как правило, не рекомендуется. Предполагается, что исключение AssertionError никогда не будет перехвачено, но это возможно, поэтому правила для операторов try должны рассматривать операторы assert, появляющиеся в блоке try, аналогично текущему обращению с операторами throw.
Оператор switch передает управление одному из нескольких операторов в зависимости от значения выражения.
ОператорSwitch:
switch ( Выражение ) БлокSwitch
БлокSwitch:
{ ГруппыОператоровSwitchнеобяз МеткиSwitchнеобяз }
ГруппыОператоровSwitch:
ГруппаОператоровSwitch
ГруппыОператоровSwitch ГруппаОператоровSwitch
ГруппаОператоровSwitch:
МеткиSwitch БлокОператоров
МеткиSwitch:
МеткаSwitch
МеткиSwitch МеткаSwitch
МеткаSwitch:
case КонстантаВыражение :
case ИмяПостояннойПеречисления :
default :
ИмяПостояннойПеречисления:
Идентификатор
Тип Выражения должен быть char, byte, short, int, Character, Byte, Short, Integer, String или типом перечисления (§8.9), в противном случае произойдет ошибка компиляции.
Тело оператора switch называется блоком switch.
Любой оператор, непосредственно содержащийся в блоке switch, может быть помечен одной или несколькими метками switch, которые являются case или default метками.
Эти метки считаются связанными с оператором switch, а также со значениями константных выражений (§15.28) или констант перечислений (§8.9.1) в метках case.
Все перечисленные ниже условия должны выполняться, в противном случае произойдет ошибка компиляции:
-
Каждое
caseконстантное выражение, связанное с операторомswitch, должно быть присваиваемо (§5.2) типуswitchВыражения. -
Никакие два
caseконстантных выражения, связанные с операторомswitch, не могут иметь одинаковое значение. -
Ни одна метка switch не является
null. -
Не более одной
defaultметки может быть связана с одним и тем же операторомswitch.
Запрет на использование null в качестве метки switch предотвращает написание кода, который никогда не будет выполнен. Если 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 завершается прерыванием по этой причине. В противном случае, если результат является ссылочного типа, он подвергается преобразованию распаковки (§5.1.8).
Если вычисление Выражения или последующее преобразование распаковки (если таковое имеется) завершается прерыванием по какой-либо причине, оператор switch завершается прерыванием по той же причине.
В противном случае выполнение продолжается путем сравнения значения Выражения с каждой case константой, и существует выбор:
-
Если одна из
caseконстант равна значению выражения, то мы говорим, что операторcaseсоответствует, и все операторы после соответствующейcaseметки вswitchблоке, если таковые имеются, выполняются последовательно.Если все эти операторы завершаются нормально, или если операторов после соответствующей
caseметки нет, то весь операторswitchзавершается нормально. -
Если ни одна
caseконстанта не соответствует, но естьdefaultметка, то все операторы после соответствующейdefaultметки вswitchблоке, если таковые имеются, выполняются последовательно.Если все эти операторы завершаются нормально, или операторов после
defaultметки нет, то весь операторswitchзавершается нормально. -
Если ни одна
caseконстанта не соответствует и нетdefaultметки, то никаких дальнейших действий не предпринимается, и операторswitchзавершается нормально.
Если любой оператор, непосредственно вложенный в Блок тела оператора switch, завершается прерыванием, это обрабатывается следующим образом:
-
Если выполнение оператора Оператор завершается прерыванием из-за
breakбез метки, никаких дальнейших действий не предпринимается, и операторswitchзавершается нормально. -
Если выполнение оператора Оператор завершается прерыванием по любой другой причине, оператор 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.
WhileStatement:
while ( Выражение ) Оператор
WhileStatementNoShortIf:
while ( Выражение ) ОператорБезКороткогоIf
Выражение должно иметь тип boolean или Boolean, в противном случае произойдёт ошибка компиляции.
Оператор while выполняется путём сначала вычисления Выражения. Если результат имеет тип Boolean, он подвергается преобразованию распаковки (§5.1.8).
Если вычисление Выражения или последующее преобразование распаковки (если таковое имеется) завершается аномально по какой-либо причине, оператор while завершается аномально по той же причине.
В противном случае выполнение продолжается путём принятия решения на основе полученного значения:
-
Если значение является
true, то содержимое Оператора выполняется. Затем следует выбор:-
Если выполнение Оператора завершается нормально, то весь оператор while выполняется снова, начиная с повторного вычисления Выражения.
-
Если выполнение Оператора завершается аномально, см. §14.12.1.
-
-
Если (возможно, распакованное) значение Выражения равно
false, никаких дальнейших действий не выполняется, и оператор while завершается нормально.Если (возможно, распакованное) значение Выражения равно
falseв первый раз при его вычислении, то Оператор не выполняется.
Оператор do выполняет Оператор и Выражение повторно до тех пор, пока значение Выражения не станет false.
DoStatement:
do Оператор while ( Выражение ) ;
У Выражения должен быть тип 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
ForStatement:
BasicForStatement
EnhancedForStatement
Основной оператор for выполняет код инициализации, затем выполняет Выражение, Оператор и код обновления многократно, пока значение Выражения не станет false.
BasicForStatement:
for ( ForInitopt ; Expressionopt ; ForUpdateopt ) Statement
ForStatementNoShortIf:
for ( ForInitopt ; Expressionopt ; ForUpdateopt ) StatementNoShortIf
ForInit:
StatementExpressionList
LocalVariableDeclaration
ForUpdate:
StatementExpressionList
StatementExpressionList:
StatementExpression
StatementExpressionList , StatementExpression
У Выражения должен быть тип 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 имеет вид:
РасширенныйОператорFor:
for ( Параметр : Выражение ) Оператор
Следующее повторяется из §8.4.1 и §8.3, чтобы сделать изложение здесь более ясным:
Параметр:
МодификаторыПеременныхнеобязательно Тип ИдентификаторПеременной
ИдентификаторПеременной:
Идентификатор
ИдентификаторПеременной []
Тип Выражения должен быть Iterable или типом массива (§10.1), в противном случае произойдет ошибка компиляции.
Область видимости и перекрытие локальной переменной, объявленной в части Параметр расширенного оператора for, указаны в §6.3 и §6.4.
Значение расширенного оператора for задается переводом в базовый оператор for следующим образом:
-
Если тип Выражения является подтипом
Iterable, то перевод осуществляется следующим образом.Если тип Выражения является подтипом
Iterable<X> для некоторого аргумента типа X, то пусть I будет типомjava.util.Iterator<X>; в противном случае, пусть I будет исходным типомjava.util.Iterator.Расширенный оператор for эквивалентен базовому оператору for следующего вида:
for (I #i = Expression.iterator(); #i.hasNext(); ) { VariableModifiersopt TargetType Identifier = (TargetType) #i.next(); Statement }
#i— это автоматически сгенерированный идентификатор, отличающийся от любого другого идентификатора (автоматически сгенерированного или иного), находящегося в области видимости (§6.3) в точке, где находится расширенный оператор for.Если Тип (в производстве Параметр) — это ссылочный тип, то ЦелевойТип — это Тип; в противном случае, ЦелевойТип — это верхняя граница преобразования захвата аргумента типа I, или
Object, если I — это исходный тип.List<? extends Integer> l = ... for (float i : l) ...будет переведен в:
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); ... -
В противном случае, Выражение обязательно имеет тип массива T
[].Пусть
L1...Lm— это (возможно, пустая) последовательность меток, непосредственно предшествующих расширенному оператору for.Расширенный оператор for эквивалентен базовому оператору for следующего вида:
T
[]#a = Expression;L1:L2: ...Lm: for (int #i = 0; #i < #a.length; #i++) { VariableModifiersopt TargetType Identifier = #a[#i]; Statement }#aи#i— это автоматически сгенерированные идентификаторы, отличающиеся от любого другого идентификатора (автоматически сгенерированного или иного), находящегося в области видимости в момент возникновения расширенного оператора for.ЦелевойТип — это тип переменной цикла, как обозначено Тип, который появляется в Параметр, за которым следуют любые скобки, следующие за Идентификатор в Параметр (§10.2).
Пример 14.14-1. Расширенный оператор for и массивы
Следующая программа, вычисляющая сумму целочисленного массива, демонстрирует, как работает расширенный оператор for для массивов:
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
Пример 14.14-2. Расширенный оператор for и приведение к примитивному типу
Следующая программа сочетает расширенный оператор for с автоматическим приведением к примитивному типу, чтобы преобразовать гистограмму в таблицу частот:
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
Оператор break передает управление за пределы окружающего оператора.
ОператорBreak:
break Идентификаторнеобязательно ;
Оператор break без метки пытается передать управление самому внутреннему окружающему оператору цикла for, while, do-while или оператору switch в немедленно окружающем методе или инициализаторе; этот оператор, который называется целью break, затем завершается нормально.
Точнее, оператор break без метки всегда завершается неожиданно, причиной чего является оператор break без метки.
Если ни оператор for, ни while, ни do-while, ни оператор switch в немедленно окружающем методе, конструкторе или инициализаторе не содержит оператор break, возникает ошибка компиляции.
Оператор break с меткой Идентификатор пытается передать управление окружающему оператору с меткой (§14.7), который имеет ту же самую метку, что и оператор break; этот оператор, который называется целью break, затем завершается нормально. В этом случае целевой оператор break необязательно должен быть оператором for, while, do-while или оператором switch.
Точнее, оператор break с меткой Идентификатор всегда завершается неожиданно, причиной чего является оператор break с меткой Идентификатор.
Оператор break должен ссылаться на метку внутри немедленно окружающего метода, конструктора или инициализатора. Нет переходов за пределы области видимости. Если ни один оператор с меткой Идентификатор в немедленно окружающем методе, конструкторе или инициализаторе не содержит оператор break, возникает ошибка компиляции.
Можно видеть, что оператор break всегда завершается неожиданно.
Предыдущие описания говорят "пытается передать управление", а не просто "передает управление", потому что если в цели break есть операторы try (§14.20) блоки try-catch или блоки catch, содержащие оператор break, то любые блоки catch таких операторов try выполняются в порядке, от самого внутреннего к самому внешнему, прежде чем управление передается цели break. Неожиданное завершение блока catch может нарушить передачу управления, инициированную оператором 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; операторы этих трех типов называются итерационными операторами. Управление передаётся к точке продолжения цикла итерационного оператора.
ContinueStatement:
continue Идентификаторопт ;
Оператор continue без метки пытается передать управление к ближайшему внутреннему циклу, оператору while, оператору do или оператору for в том же методе, конструкторе или инициализаторе; этот оператор, который называется целью оператора continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, такой оператор continue всегда завершается прерыванием, причиной которого является оператор continue без метки.
Если ни один из циклов, операторов while, операторов do или операторов for в ближайшем методе, конструкторе или инициализаторе не содержит оператор continue, возникает ошибка компиляции.
Оператор continue с меткой Идентификатор пытается передать управление к оператору с этой же меткой Идентификатор (см. §14.7), который находится в охватывающей области; этот оператор, называемый целью оператора continue, затем немедленно завершает текущую итерацию и начинает новую.
Точнее, оператор continue с меткой Идентификатор всегда завершается прерыванием, причиной которого является оператор continue с меткой Идентификатор.
Целью оператора continue должен быть оператор цикла, оператор while, оператор do или оператор for; в противном случае возникает ошибка компиляции.
Оператор continue должен ссылаться на метку в ближайшем методе, конструкторе или инициализаторе. Нет переходов на другие области. Если оператор continue не находится в цикле, операторе while, операторе do или операторе for с меткой Идентификатор в ближайшем методе, конструкторе или инициализаторе, возникает ошибка компиляции.
Таким образом, оператор continue всегда завершается прерыванием.
См. описание оператора while (§14.12), оператора do (§14.13) и оператора for (§14.14) для обсуждения обработки прерывания из-за оператора continue.
В приведенных описаниях используется фраза "пытается передать управление", а не просто "передаёт управление", потому что если в целевом операторе continue есть операторы try (§14.20), блоки try или catch которых содержат оператор continue, то все блоки catch этих операторов try выполняются в порядке, от внутреннего к внешнему, перед передачей управления целевому оператору continue. Прерывание выполнения блока catch может нарушить передачу управления, инициированную оператором continue.
Пример 14.16-1. Оператор continue
В классе Graph в §14.15 один из операторов break используется для завершения выполнения всего тела внешнего цикла. Этот break можно заменить оператором continue, если сам цикл помечен меткой:
class Graph {
int edges[][];
public Graph(int[][] edges) { this.edges = edges; }
public Graph loseEdges(int i, int j) {
int n = edges.length;
int[][] newedges = new int[n][];
edgelists:
for (int k = 0; k < n; ++k) {
int z;
search:
{
if (k == i) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == j) break search;
}
} else if (k == j) {
for (z = 0; z < edges[k].length; ++z) {
if (edges[k][z] == i) break search;
}
}
// No edge to be deleted; share this list.
newedges[k] = edges[k];
continue edgelists;
} //search
// Copy the list, omitting the edge at position z.
int m = edges[k].length - 1;
int ne[] = new int[m];
System.arraycopy(edges[k], 0, ne, 0, z);
System.arraycopy(edges[k], z+1, ne, z, m-z);
newedges[k] = ne;
} //edgelists
return new Graph(newedges);
}
}
Выбор того, какой из операторов использовать, в основном зависит от стиля программирования.
Оператор return возвращает управление вызывающей стороне метода (§8.4, §15.12) или конструктора (§8.8, §15.9).
ReturnStatement:
return Выражениеопт ;
Оператор return содержится во внутреннем конструкторе, методе или инициализаторе, тело которого содержит оператор return.
Если оператор return содержится в инициализаторе экземпляра или статическом инициализаторе (§8.6, §8.7), возникает ошибка компиляции.
Оператор return без Выражения должен находиться в методе, объявленном с помощью ключевого слова void, не возвращающим значения (§8.4), или в конструкторе; в противном случае возникает ошибка компиляции.
Оператор return без Выражения пытается передать управление вызывающей стороне метода или конструктора, в котором он находится. Точнее, оператор return без Выражения всегда завершается прерыванием, причиной которого является возврат без значения.
Оператор return с Выражением должен находиться в объявлении метода, объявленном для возврата значения (§8.4), в противном случае возникает ошибка компиляции.
Выражение должно обозначать переменную или значение некоторого типа T; в противном случае возникает ошибка компиляции.
Тип T должен быть приводим (§5.2) к объявленному типу результата метода, в противном случае возникает ошибка компиляции.
Оператор return с Выражением пытается передать управление вызывающей стороне метода, содержащего его; значение Выражения становится значением вызова метода. Точнее, выполнение оператора return с Выражением сначала оценивает Выражение. Если вычисление Выражения завершается прерыванием по какой-либо причине, оператор return также завершается прерыванием по той же причине. Если вычисление Выражения завершается успешно, возвращая значение V, то оператор return завершается прерыванием, причиной которого является возврат со значением V.
Если выражение имеет тип float и не является FP-строгим (§15.4), то значение может быть элементом либо множества значений float, либо множества значений float с расширенным порядком (§4.2.3). Если выражение имеет тип double и не является FP-строгим, то значение может быть элементом либо множества значений double, либо множества значений double с расширенным порядком.
Таким образом, оператор return всегда завершается прерыванием.
В приведенных описаниях используется фраза "пытается передать управление", а не просто "передаёт управление", потому что если в методе или конструкторе, содержащем оператор return, есть операторы try (§14.20), блоки try или catch которых содержат оператор return, то все блоки catch этих операторов try выполняются в порядке, от внутреннего к внешнему, перед передачей управления вызывающей стороне метода или конструктора. Прерывание выполнения блока catch может нарушить передачу управления, инициированную оператором return.
Оператор throw вызывает исключение (§11). Результатом является немедленная передача управления (§11.3), которая может выйти из нескольких операторов и нескольких инициализаций конструкторов, экземпляров, статических инициализаций и инициализаций полей, а также вызовов методов, пока не будет найден оператор try (§14.20), который перехватывает брошенное значение. Если такой оператор try не найден, выполнение потока (§17), который выполнил оператор throw, завершается (§11.3) после вызова метода uncaughtException для группы потоков, к которой принадлежит поток.
ThrowStatement:
throw Expression ;
Выражение Expression в операторе throw должно обозначать либо 1) переменную или значение ссылочного типа, которое присваиваемо (§5.2) типу Throwable, либо 2) нулевую ссылку; в противном случае произойдет ошибка компиляции.
Ссылочный тип Expression всегда будет типом класса (так как типы интерфейсов не присваиваются типу Throwable), который не параметризован (так как подкласс Throwable не может быть обобщенным (§8.1.2)).
Должно выполняться хотя бы одно из следующих трех условий; в противном случае произойдет ошибка компиляции:
-
Тип Expression — это класс исключения без проверки (§11.1.1) или нулевой тип (§4.1).
-
Оператор
throwнаходится в блокеtryоператораtry(§14.20), и при этом операторtryне может сгенерировать исключение типа Expression. (В этом случае мы говорим, что брошенное значение перехвачено операторомtry.) -
Оператор
throwнаходится в объявлении метода или конструктора, и тип Expression присваивается (§5.2) хотя бы одному типу, перечисленному в спискеthrowsв предложении (объявления §8.4.6, §8.8.5).
Типы исключений, которые может сгенерировать оператор throw, указаны в §11.2.2.
Оператор throw сначала вычисляет Expression. Затем:
-
Если вычисление Expression завершается внезапно по какой-либо причине, то оператор
throwзавершается внезапно по этой причине. -
Если вычисление Expression завершается нормально, создавая не-
nullзначениеV, то операторthrowзавершается внезапно по причинеthrowсо значениемV. -
Если вычисление Expression завершается нормально, создавая
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) от имени выполняющей потока, выполняет блок, затем освобождает замок. Пока выполняющий поток владеет замком, никакой другой поток не может его приобрести.
SynchronizedStatement:
synchronized ( Выражение ) Блок
Тип Выражения должен быть ссылочным типом; в противном случае возникает ошибка компиляции.
Оператор synchronized выполняется путем сначала вычисления Выражения. Затем:
-
Если вычисление Выражения завершается аномально по какой-либо причине, тогда оператор synchronized завершается аномально по той же причине.
-
В противном случае, если значение Выражения равно
null, выбрасывается исключениеNullPointerException. -
В противном случае, пусть не-
nullзначение Выражения будетV. Выполняющий поток блокирует монитор, связанный сV. Затем выполняется Блок, а затем происходит выбор:-
Если выполнение Блока завершается нормально, то монитор разблокируется, и оператор synchronized завершается нормально.
-
Если выполнение Блока завершается аномально по любой причине, то монитор разблокируется, и оператор synchronized завершается аномально по той же причине.
-
Замки, приобретаемые операторами synchronized, такие же, как замки, которые неявно приобретаются синхронизированными методами (§8.4.3.6). Один поток может приобрести замок более одного раза.
Приобретение замка, связанного с объектом, само по себе не препятствует другим потокам обращаться к полям объекта или вызывать не-synchronized методы объекта. Другие потоки также могут использовать synchronized методы или оператор synchronized обычным способом для достижения взаимной исключительности.
Пример 14.19-1. Оператор synchronized
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("made it!");
}
}
}
}
Эта программа выводит:
made it!
Обратите внимание, что эта программа будет зависать, если одному потоку не разрешено блокировать монитор более одного раза.
Оператор try выполняет блок кода. Если возникает исключение, и оператор try содержит один или более блоков catch, способных обработать это исключение, управление передаётся первому такому блоку catch. Если оператор try содержит блок finally, то другой блок кода выполняется независимо от того, как завершился блок try (нормально или аварийно), и независимо от того, был ли блок catch вызван первым.
TryStatement:
try Block Catches
try Block Catchesopt Finally
TryWithResourcesStatement
Catches:
CatchClause
Catches CatchClause
CatchClause:
catch ( CatchFormalParameter ) Block
CatchFormalParameter:
VariableModifiersopt CatchType VariableDeclaratorId
CatchType:
ClassType
ClassType | CatchType
Finally:
finally Block
Блок Block сразу после ключевого слова try называется блоком try оператора try.
Блок Block сразу после ключевого слова finally называется блоком finally оператора try.
Оператор try может содержать блоки catch, также называемые обработчиками исключений.
Блок catch имеет ровно один параметр, который называется параметром исключения.
Область действия и затенение параметра исключения определены в §6.3 и §6.4.
Параметр исключения может указывать свой тип как один класс или объединение двух или более типов классов (называемые вариантами). Варианты в объединении разделяются символом |.
Блок catch, где параметр исключения обозначен как один тип класса, называется блоком uni-catch.
Блок catch, где параметр исключения обозначен как объединение типов, называется блоком multi-catch.
Каждый тип класса, используемый в обозначении типа параметра исключения, должен быть классом Throwable или подклассом класса Throwable; в противном случае произойдёт ошибка компиляции.
Использование параметра типа в обозначении типа параметра исключения является ошибкой компиляции.
Если объединение типов содержит два варианта Di и Dj (i ≠ j), где Di является подтипом Dj (§4.10.2), то это ошибка компиляции.
Объявленный тип параметра исключения, который обозначает тип одним типом класса, — это тип этого класса.
Объявленный тип параметра исключения, который обозначает тип как объединение вариантов D1 | D2 | ... | Dn, это lub(D1, D2, ..., Dn) (§15.12.2.7).
Параметр исключения блока multi-catch неявно объявлен final, если он не объявлен явно final.
Ошибка компиляции возникает, если параметру исключения, неявно или явно объявленному final, присваивается значение внутри тела блока catch.
В блоке uni-catch параметр исключения, который не объявлен (неявно или явно) final, считается эффективно final, если он никогда не используется в качестве левого операнда оператора присваивания (§15.26).
Неявно final параметр исключения является таким по умолчанию, в то время как эффективно final параметр исключения таким становится из-за способа использования. Параметр исключения в блоке multi-catch неявно final, поэтому никогда не может выступать в качестве левого операнда оператора присваивания, но он не считается эффективно final.
Если параметр исключения эффективно final (в блоке uni-catch) или неявно final (в блоке multi-catch), то добавление явного модификатора final к его объявлению не вызовет ошибок компиляции. Однако, если параметр исключения блока uni-catch объявлен явно final, то удаление модификатора final может привести к ошибкам компиляции. Это связано с тем, что параметр исключения, хотя и эффективно final, больше не может быть использован, например, локальными классами. С другой стороны, если ошибок компиляции нет, можно изменить программу таким образом, чтобы параметр исключения был повторно присвоен, и больше не будет эффективно final.
Типы исключений, которые может генерировать оператор try, описаны в §11.2.2.
Взаимосвязь исключений, генерируемых блоком try оператора try и обрабатываемых блоками catch оператора try, описана в §11.2.3.
Обработчики исключений рассматриваются слева направо: самый ранний возможный catch блок принимает исключение, получая в качестве аргумента объект исключения, как указано в §11.3.
Блок multi-catch можно представить как последовательность блоков uni-catch. То есть, блок catch, тип параметра исключения которого обозначен как объединение D1|D2|...|Dn, эквивалентен последовательности из n блоков catch, где параметры исключения имеют типы D1, D2, ..., Dn, соответственно. Например, следующий код:
try {
... throws ReflectiveOperationException ...
}
catch (ClassNotFoundException | IllegalAccessException ex) {
... body ...
}
Семантически эквивалентен следующему коду:
try {
... throws ReflectiveOperationException ...
} catch (final ClassNotFoundException ex1) {
... body ...
} catch (final IllegalAccessException ex2) {
... body ...
}
где блок multi-catch с двумя вариантами был преобразован в два отдельных блока catch, по одному для каждого варианта. Компилятор Java не обязан и не рекомендуется компилировать блок multi-catch, дублируя код таким образом, так как возможно представить блок multi-catch в файле class без дублирования.
Блок finally гарантирует, что блок finally выполняется после блока try и любого блока catch, который может быть выполнен, независимо от того, как управление покидает блок try или блок catch. Обработка блока finally довольно сложна, поэтому два случая оператора try с блоком finally и без него описываются отдельно (§14.20.1, §14.20.2).
Оператор try может опустить блоки catch и блок finally, если он является оператором try-с-ресурсами (§14.20.3).
Оператор try без блока finally выполняется путём сначала выполнения блока try. Затем следует выбор:
-
Если выполнение блока
tryзавершается нормально, то никаких дальнейших действий не выполняется, и операторtryзавершается нормально. -
Если выполнение блока
tryзавершается неожиданно из-заthrowзначенияV, то следует выбор:-
Если тип времени выполнения
Vсовместим с присваиванием (§5.2) классу исключения, которое можно перехватить, любого блокаcatchпредложенияtry, то выбирается первое (самое левое) такое предложениеcatch. ЗначениеVприсваивается параметру выбранного предложенияcatch, и блок этого предложенияcatchвыполняется, после чего следует выбор:-
Если этот блок завершается нормально, то оператор
tryзавершается нормально. -
Если этот блок завершается неожиданно по любой причине, то оператор
tryзавершается неожиданно по той же причине.
-
-
Если тип времени выполнения
Vне совместим с присваиванием с классом исключения, которое можно перехватить, любого предложенияcatchпредложенияtry, то операторtryзавершается неожиданно из-заthrowзначенияV.
-
-
Если выполнение блока
tryзавершается неожиданно по любой другой причине, то операторtryзавершается неожиданно по той же причине.
Пример 14.20.1-1. Перехват исключения
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt { throw new BlewIt(); }
public static void main(String[] args) {
try {
blowUp();
} catch (RuntimeException r) {
System.out.println("Caught RuntimeException");
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
}
}
}
Здесь исключение BlewIt выбрасывается методом blowUp. Оператор try-catch в теле main имеет два блока catch. Тип времени выполнения исключения — это BlewIt, который не может быть присвоен переменной типа RuntimeException, но может быть присвоен переменной типа BlewIt, поэтому выходные данные примера — это:
Caught BlewIt
Оператор try с блоком finally выполняется путём сначала выполнения блока try. Затем следует выбор:
-
Если выполнение блока
tryзавершается нормально, то блокfinallyвыполняется, после чего следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается неожиданно по причинеS, то операторtryзавершается неожиданно по причинеS.
-
-
Если выполнение блока
tryзавершается неожиданно из-заthrowзначенияV, то следует выбор:-
Если тип времени выполнения
Vсовместим с присваиванием с классом исключения, которое можно перехватить, любого предложенияcatchпредложенияtry, то выбирается первое (самое левое) такое предложениеcatch. ЗначениеVприсваивается параметру выбранного предложенияcatch, и блок этого предложенияcatchвыполняется. Затем следует выбор:-
Если блок
catchзавершается нормально, то блокfinallyвыполняется. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается нормально. -
Если блок
finallyзавершается неожиданно по любой причине, то операторtryзавершается неожиданно по той же причине.
-
-
Если блок
catchзавершается неожиданно по причинеR, то выполняется блокfinally. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается неожиданно по причинеR. -
Если блок
finallyзавершается неожиданно по причинеS, то операторtryзавершается неожиданно по причинеS(и причинаRотбрасывается).
-
-
-
Если тип времени выполнения
Vне совместим с присваиванием с классом исключения, которое можно перехватить, любого предложенияcatchпредложенияtry, то выполняется блокfinally. Затем следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается неожиданно из-заthrowзначенияV. -
Если блок
finallyзавершается неожиданно по причинеS, то операторtryзавершается неожиданно по причинеS(иthrowзначенияVотбрасывается и забывается).
-
-
-
Если выполнение блока
tryзавершается неожиданно по любой другой причинеR, то выполняется блокfinally, после чего следует выбор:-
Если блок
finallyзавершается нормально, то операторtryзавершается неожиданно по причинеR. -
Если блок
finallyзавершается неожиданно по причинеS, то операторtryзавершается неожиданно по причинеS(и причинаRотбрасывается).
-
Пример 14.20.2-1. Обработка неперехваченного исключения с finally
class BlewIt extends Exception {
BlewIt() { }
BlewIt(String s) { super(s); }
}
class Test {
static void blowUp() throws BlewIt {
throw new NullPointerException();
}
public static void main(String[] args) {
try {
blowUp();
} catch (BlewIt b) {
System.out.println("Caught BlewIt");
} finally {
System.out.println("Uncaught Exception");
}
}
}
Эта программа выводит:
Uncaught Exception
Exception in thread "main" java.lang.NullPointerException
at Test.blowUp(Test.java:7)
at Test.main(Test.java:11)
Исключение NullPointerException (которое является видом RuntimeException), которое выбрасывается методом blowUp, не перехватывается оператором try в main, потому что NullPointerException не может быть присвоен переменной типа BlewIt. Это приводит к выполнению блока finally, после чего поток, выполняющий main, который является единственным потоком тестовой программы, завершается из-за неперехваченного исключения, что обычно приводит к выводу имени исключения и простого трассировки стека. Однако эта спецификация не требует трассировки стека.
Проблема с требованием трассировки стека заключается в том, что исключение может быть создано в одной точке программы и выброшено в другой. Сохранение трассировки стека в исключении является чрезвычайно затратным процессом, если оно не выброшено (в этом случае трассировка может быть сгенерирована при размотке стека). Поэтому мы не требуем трассировки стека при каждом исключении.
Оператор try-with-resources параметризуется переменными (известными как ресурсы), которые инициализируются перед выполнением блока try и автоматически закрываются в обратном порядке их инициализации после выполнения блока try. Обычно блоки catch и finally не нужны, когда ресурсы закрываются автоматически.
TryWithResourcesStatement:
try ResourceSpecification Block Catchesopt Finallyopt
ResourceSpecification:
( Resources ;opt )
Resources:
Resource
Resource ; Resources
Resource:
VariableModifiersopt Type VariableDeclaratorId = Expression
Раздел ResourceSpecification объявляет одну или несколько локальных переменных с инициализирующими выражениями, которые действуют как ресурсы для оператора try.
Ресурс, объявленный в ResourceSpecification, неявно объявляется final (§4.12.4), если он не объявлен явно final.
Тип переменной, объявленной в ResourceSpecification, должен быть подтипом AutoCloseable, в противном случае произойдёт ошибка компиляции.
Область видимости и перекрытие переменной, объявленной в ResourceSpecification, определены в §6.3 и §6.4.
Ошибка компиляции возникает, если в ResourceSpecification объявлены две переменные с одинаковым именем.
Ресурсы инициализируются слева направо. Если ресурс не удаётся инициализировать (то есть выражение инициализатора генерирует исключение), то все ресурсы, инициализированные до этого оператором try-with-resources, закрываются. Если все ресурсы инициализируются успешно, блок try выполняется как обычно, а затем все непустые ресурсы оператора try-with-resources закрываются.
Ресурсы закрываются в обратном порядке их инициализации. Ресурс закрывается только если он был инициализирован ненулевым значением. Исключение, возникающее при закрытии одного ресурса, не предотвращает закрытие других ресурсов. Такое исключение считается подавленным, если ранее возникло исключение от инициализатора, блока try или закрытия ресурса.
Оператор try-with-resources с разделом ResourceSpecification, объявляющим несколько ресурсов, обрабатывается так, как если бы это были несколько операторов try-with-resources, каждый из которых имеет раздел ResourceSpecification, объявляющий один ресурс. При переводе оператора try-with-resources с n ресурсами (n > 1) результат представляет собой оператор try-with-resources с n-1 ресурсами. После n таких переводов существует n вложенных операторов try-with-resources, и весь перевод завершается.
Оператор try-with-resources без блоков catch или finally называется основным оператором try-with-resources.
Значение основного оператора try-with-resources:
try (VariableModifiersopt R Identifier = Expression ...) Block
определяется следующим переводом в объявление локальной переменной и оператор try-catch-finally:
{
final VariableModifiers_minus_final 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();
}
}
}
}
VariableModifiersopt_minus_final определяется как VariableModifiersopt без ключевого слова final, если оно присутствует.
#t, #primaryExc и #suppressedExc — автоматически сгенерированные идентификаторы, отличающиеся от других идентификаторов (автоматически сгенерированных или иных), находящихся в области видимости в точке, где встречается оператор try-with-resources.
Если ResourceSpecification объявляет один ресурс, то ResourceSpecification_tail пуст (и оператор try-catch-finally не является оператором try-with-resources).
Если ResourceSpecification объявляет более одного ресурса (n > 1), то ResourceSpecification_tail содержит второй, третий и последующие ресурсы, объявленные в ResourceSpecification, в том же порядке (и оператор 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с добавленным в список подавляемых исключений исключением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 Catchesopt Finallyopt
определяется следующим переводом в основной оператор try-with-resources (§14.20.3.1), вложенный в оператор try-catch-finally или оператор try-finally или оператор try-catch:
try {
try ResourceSpecification
Block
}
Catchesopt
Finallyopt
Результат перевода — размещение ResourceSpecification "внутри" оператора try. Это позволяет блоку catch расширенного оператора try-with-resources перехватывать исключение, возникшее при автоматической инициализации или закрытии ресурса.
Кроме того, все ресурсы будут закрыты (или попытка закрытия будет предпринята) к моменту выполнения блока finally, что соответствует цели использования ключевого слова try.
Если оператор не может быть выполнен, так как он недостижим, то это ошибка времени компиляции.
Данный раздел посвящен точному толкованию слова «достижимый». Идея заключается в том, что должен существовать возможный путь выполнения от начала конструктора, метода, инициализатора экземпляра или статического инициализатора, содержащего оператор, до самого оператора. Анализ учитывает структуру операторов. За исключением специального обращения с while, do и for операторами, условное выражение которых имеет постоянное значение true, значения выражений не учитываются в анализе потока выполнения.
Например, Java-компилятор примет код:
{
int n = 5;
while (n > 7) k = 2;
}
даже если значение n известно на этапе компиляции и, в принципе, на этапе компиляции известно, что присвоение переменной k никогда не выполнится.
Правила в этом разделе определяют два технических термина:
-
является ли оператор достижимым
-
может ли оператор нормально завершиться
Оператор может нормально завершиться только если он достижим.
Для краткости описания правил используется сокращение «iff», означающее «если и только если».
Достижимый break оператор выходит из оператора, если в точке выхода нет try операторов, блоки которых содержат оператор break, или есть try операторов, блоки которых содержат оператор break, и все finally-части этих try операторов могут нормально завершиться.
Это определение основано на логике «попыток передачи управления» в §14.15.
Оператор continue продолжает оператор do, если в операторе do нет try операторов, блоки которых содержат оператор continue, или есть 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является типом непроверяемого исключения, либоThrowable; либо какое-либо выражение или инструкцияthrowв блокеtryдостижимы и могут вызывать проверяемое исключение, тип которого можно присвоить параметру предложенияcatchC.Выражение достижимо тогда и только тогда, когда достижима самая внутренняя инструкция, содержащая его.
См. §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, а затем скомпилировать код правильно без каких-либо других изменений в тексте программы.
Эта возможность "условной компиляции" существенно влияет на и имеет отношение к двоичной совместимости (§13). Если набор классов, использующих такую "флаговую" переменную, скомпилирован, а условный код пропущен, то позже недостаточно распространить только новую версию класса или интерфейса, содержащую определение флага. Изменение значения флага, следовательно, не является двоично совместимым с существующими двоичными файлами (§13.4.9). (Существуют и другие причины такой несовместимости, такие как использование констант в case метках в операторах switch; см. §13.4.9.)
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.