Содержание | Предыдущая | Следующая
ГЛАВА 14
Последовательность выполнения программы в языке Ява управляется операторами, которые выполняются для разных эффектов и не имеют значений.
Некоторые операторы содержат в себе другие операторы, как часть своей структуры; эти операторы называются подоператорами. Будем говорить, что оператор S непосредственно содержит оператор
U, если не имеется такого оператора T, отличного от S и U, что S содержит T, и T содержит U. Таким же самым способом, некоторые операторы содержат выражения (§ 15), как часть своей структуры.В первом разделе этой главы обсуждается различие между нормальным и преждевременным завершением операторов
(§ 14.1). Большинство остающихся разделов объясняет различные виды операторов, описывая подробно как их нормальное поведение так и любую специальную обработку преждевременного завершения.Блоки описываются первыми
(§ 14.2), потому что они могут появляться в тех местах, где другие виды операторов не разрешаются, и потому что один вид оператора, оператор описания локальной переменной (§ 14.3), должен непосредственно содержаться в пределах блока.Следующий грамматический маневр объясняется необходимостью обойти знакомую проблему “обособленного
else” (§ 14.4).Операторы, которые будут знакомы программирующим на языке C и C++, это пустой
(§ 14.5), помеченный (§ 14.6), выражение (§ 14.7), if (§14.8), switch (§14.9), while (§14.10), do (§14.11), for (§14.12), break (§14.13), continue (§14.14) и return (§14.15).В отличие от Cи и Cи++, язык Ява не имеет оператора
goto. Однако, операторы break и continue расширены в языке Ява до того, чтобы им разрешено упоминать метки операторов.Операторы языка Ява, которых нет в языке
C, - throw (§14.16), synchronized (§14.17) и try (§14.18).Последний раздел
(§ 14.19) этой главы требует, чтобы каждый оператор был достижим в некотором техническом смысле.Каждый оператор имеет нормальный способ реализации, в которой выполняются определенные вычислительные шаги. Следующие разделы описывают нормальный способ выполнения для каждого вида оператора. Если все шаги выполнены, как описано, без преждевременного завершения оператора, оператор, как говорится
, завершен нормально. Однако, некоторые события могут прерывать нормальное завершение оператора:Если такое происходит, то выполнение одного или более операторов может закончится прежде, чем будут выполнены все шаги их нормального способа исполнения; такие операторы называются завершенными преждевременно. Преждевременное завершение всегда имеет причину, которая является одной из следующих:
Термины " завершен нормально "и" завершен преждевременно " также представляют собой оценку выражений
(§ 15.5). Единственная причина, когда выражение может быть завершенным преждевременно, - это сгенерированное исключение из-за throw с данным значением (§ 14.16) или исключение времени выполнения, или ошибка (§11, §15.5).Если оператор вычисляет выражение, то преждевременное завершение выражения всегда приводит к немедленному преждевременному завершению оператора по той же самой причине. Все следующие шаги нормального способа выполнения игнорируются.
Однако в этой главе не определено преждевременное завершение подоператора, приводящее к немедленному преждевременному завершению оператора по той же самой причине, и все следующие шаги нормального способа выполнения оператора игнорируются.
Если не указан иной способ действий, оператор является завершенным нормально, если все входящие в него выражения и все подоператоры являются нормально завершенными.
Блок - это последовательность операторов и операторов описания локальной переменной в пределах фигурных скобок.
Block:{
BlockStatementsopt}
BlockStatements:BlockStatement
BlockStatements
BlockStatement BlockStatement: LocalVariableDeclarationStatement Statement
Блок выполняется, если выполняются все операторы описания локальной переменной и другие операторы по порядку от первого до последнего (слева направо). Если все эти операторы нормально завершаются, то блок нормально завершается. Если любой из операторов блока преждевременно завершается по любой причине, то блок преждевременно завершается по той же самой причине.
Операторы описания локальной переменной, представляют собой одно или более имен локальных переменных.
LocalVariableDeclarationStatement: LocalVariableDeclaration;
LocalVariableDeclaration: TypeVariableDeclarators
Далее идет повторение из §8.3 для того, чтобы сделать представление более понятным:
VariableDeclarators: VariableDeclarator VariableDeclarators,
VariableDeclarator VariableDeclarator: VariableDeclaratorId VariableDeclaratorId=
VariableInitializer VariableDeclaratorId: Identifier VariableDeclaratorId[ ]
VariableInitializer: Expression ArrayInitializer
Каждый оператор описания локальной переменной, содержится непосредственно в блоке. Операторы описания локальной переменной могут быть свободно перемешаны с другими видами операторов в блоке.
Описание локальной переменной может также появляться в заголовке оператора for (§ 14.12). В этом случае он выполняется тем же самым способом, как будто бы это была часть оператора описания локальной переменной.
Каждый описатель локальной переменной объявляет одну локальную переменную, имя которой является Идентификатором, появляющееся в описателе.
Тип переменной обозначается как Тип, появляющийся в начале описания локальной переменной, сопровождается любым количеством скобок, которые следуют за Идентификатором в описателе Таким образом, описание локальной переменной:
int a, b [], c [] [];
является эквивалентным ряду описаний:
int a;
int [] b;
int [] [] c;
Скобки разрешаются в описателях как дань традиции языков Cи и Cи ++. Общее правило, однако, также означает, что описание локальной переменной:
float[][] f[][], g[][][], h[];
является эквивалентным ряду описаний:
float[][][][] f;
float[][][][][] g;
float[][][] h;
Мы не рекомендуем такую "смешанную запись" для описаний множеств.
Область действия локальной переменной, объявленной в блоке, - остальная часть блока, включающая инициализатор этой переменной. Имя параметра локальной переменной может повторно не объявляться как локальная переменная или параметр исключения в пределах своей области действия, или же во время компиляции произойдет ошибка; то есть сокрытие имени локальной переменной не разрешается.
Локальная переменная не может повторно упоминаться, используясь квалифицированным именем
(§ 6.6), только простым именем.Пример:
class Test { static int x; public static void main(String[] args) { int x = x; } }
приводит к ошибке времени компиляции, потому что инициализация x - в пределах области действия x происходит как с локальной переменной, а локальная x еще не имеет значения и не может использоваться.
Следующая программа компилируется:
class Test { static int x; public static void main(String[] args) { int x = (x=2)*2; System.out.println(x); } }
потому что локальная переменная x определена (§ 16) прежде, чем использована. Печатается:
4
Имеется другой пример:
class Test { public static void main(String[] args) { System.out.print("2+1="); int two = 2, three = two + 1; System.out.println(three); } }
который правильно компилируется и выводит:
2+1=3
Инициализатор для three может правильно обратиться к переменной two, объявленной ранее в описателе, и вызов метода на следующей линии может правильно обратиться к переменной three, объявленный ранее в блоке.
Область действия локальной переменной, объявленной в операторе for есть остальная часть оператора for, включающая собственный инициализатор.
Если описание идентификатора локальной переменной появляется в области действия параметра или локальной переменной с тем же самым именем, во время компиляции происходит ошибка. Таким образом, следующий пример не компилируется:
class Test { public static void main(String[] args) { int i; for (int i = 0; i < 10; i++) System.out.println(i); } }
Это ограничение помогает обнаруживать ошибки, поиск которых в другом случае был бы непрост. (Подобное ограничение на сокрытие членов класса локальными переменными оценено как непрактичное, потому что добавление члена в суперкласс могло бы привести к необходимости переименовывать локальные переменные подклассов.)
С другой стороны, локальные переменные с тем же самым именем могут быть объявлены в двух отдельных блоках или операторах for, ни один из которых не содержит другой. Таким образом, программа:
class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) System.out.print(i + " "); for (int i = 10; i > 0; i--) System.out.print(i + " "); System.out.println(); } }
компилируется без ошибки, выполняется и выводит:
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
Если имя, объявленное как локальная переменная уже объявлено как поле или имя типа, тогда внешнее объявление скрывается областью видимости локальной переменной. Поле или имя типа по-прежнему почти всегда
могут быть доступными (§6.8), употребляя нужное имя ограниченного использования. Например, ключевое слово this может использоваться для доступа к скрытому полю x, применяя форму this.x. На самом деле, эта форма записи типична для конструкторов (§8.6):class Pair { Object first, second; public Pair(Object first, Object second) { this.first = first; this.second = second; } }
В этом примере, конструктор использует параметры, имеющие те же самые имена что и поля для того, чтобы быть инициализированным. Так намного проще, чем изобретать различные имена для параметров, и не так запутано в контексте, изображенном в традиционном стиле. Однако, в общем это рассматривается как плохой стиль - иметь локальные переменные с теми же самыми именами что и поля.
Оператор объявления локальной переменной - выполнимый оператор. Он выполняется каждый раз, операторы объявления обрабатываются в порядке слева направо. Если объявление имеет выражение инициализации, то выражение вычисляется и его значение присваивается переменной. Если объявление не имеет выражения инициализации, тогда Ява-компилятор должен показать, используя точный алгоритм, данный в
§16, что каждой ссылке на переменную обязательно предшествует присваивание переменной. Если дело обстоит иначе, тогда происходит ошибка времени компиляции.Каждая инициализация (кроме первой) выполняется только в том случае, если вычисление предшествующего выражения инициализации завершается обычным образом. Выполнение объявления локальной переменной завершается обычным образом, если вычисление последнего выражения инициализации завершается обычным образом; если объявление локальной переменной не содержит выражений инициализации, то выполнение его завершается обычным образом.
В языке Ява существует множество видов операторов. Наблюдается наибольшее сходство с операторами языков Cи и Cи ++, но существуют некоторые операторы, известные только языку Ява.
Как в Cи и Cи ++, в языке Ява оператор
if испытывает так называемую "обособленную else проблему, " проиллюстрированную этим нерационально-форматированным примером:if (door.isOpen()) if (resident.isVisible()) resident.greet("Hello!"); else door.bell.ring(); // "обособленное else"
Проблема состоит в том, что и внешний оператор if и внутренний оператор if могли бы, очевидно, иметь свое предложение else. В этом примере, каждый мог бы предположить, что программист намеревался отнести предложение else ко внешнему оператору if. Язык Ява , подобно Cи и Cи++ и многим языкам, созданным прежде, условно устанавливает, что предложение else относится к самому внутреннему оператору if, которому оно могло бы принадлежать. Это правило зафиксировано в следующей грамматике:
Statement: StatementWithoutTrailingSubstatement LabeledStatement IfThenStatement IfThenElseStatement WhileStatement ForStatement StatementNoShortIf: StatementWithoutTrailingSubstatement LabeledStatementNoShortIf IfThenElseStatementNoShortIf WhileStatementNoShortIf ForStatementNoShortIf StatementWithoutTrailingSubstatement: Block EmptyStatement ExpressionStatement SwitchStatement DoStatement BreakStatement ContinueStatement ReturnStatement SynchronizedStatement ThrowStatement TryStatement
Следующее повторяется из §14.8 для того, чтобы написанное выше стало более понятным:
IfThenStatement:if (
Expression)
Statement IfThenElseStatement:if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:if (
Expression)
StatementNoShortIfelse
StatementNoShortIf
Таким образом, операторы грамматически делятся на две категории: те, которые могли бы заканчиваться оператором if, который не имеет предложение else (" короткий оператор if ") и те, у которых предложение else присутствует. Только те операторы, которые определенно не заканчиваются коротким оператором if, могут применяться как непосредственный подоператор перед ключевым словом else оператора if, который выполняет имеющееся предложение else. Это простое правило предотвращает "обособленную else" проблему. Характер выполнения оператора с " не коротким if" ограничением идентичен поведению выполнения того же самого вида оператора без "не короткого if " ограничения; различие показано для того, чтобы полностью разрешить синтаксическое затруднение.
Пустой оператор не делает ничего.
EmptyStatement:
;
Выполнение пустого оператора всегда завершается нормально.
Операторы могут иметь префикс label(метка)
.LabeledStatement: Identifier:
Statement LabeledStatementNoShortIf: Identifier:
StatementNoShortIf
Identifier объявляется для того, чтобы стать меткой содержащегося Statement.
В отличие от Cи и Cи++, язык Ява не имеет оператора
goto ; метки оператора используются оператором break (§14.13) или оператором continue (§14.14) , появляющимися в пределах помеченного оператора.Оператор, помеченный идентификатором не должен появляться в пределах другого оператора, помеченного тем же самым идентификатором, иначе произойдет ошибка времени компиляции. Два оператора могут быть помечены одним и тем же идентификатором, только если ни один из операторов не содержит другого.
Не существует никакого ограничения по поводу использования того же самого идентификатора как
метку и имя пакета, класса, интерфейса, метода, поля, параметра или локальной переменной. Использование идентификатора для того, чтобы пометить оператор, не скрывает пакет, класс, интерфейс, метод, поле, параметр или локальную переменную с тем же самым именем. Использованиеидентификатора в качестве локальной переменной или в качестве параметра
обработчика исключительной ситуации (§14.18) не скрывает метку оператора с тем же самым именем.Помеченный оператор исполняется, немедленно выполняя содержащийся
Statement. Если оператор помечен Identifier, и содержащийся Statement завершается преждевременно из-за оператора break с тем же самым Identifier, тогда помеченный оператор завершается обычным образом. Во всех других случаях преждевременного завершения оператора, помеченный оператор завершается преждевременно по той же самой причине.Некоторые виды выражений могут использоваться как операторы, сопровождаемые точкой с запятой:
ExpressionStatement:
StatementExpression ;
StatementExpression:
Assignment
PreIncrementExpression
PreDecrementExpression
PostIncrementExpression
PostDecrementExpression
MethodInvocation
ClassInstanceCreationExpression
Оператор-выражение начинается с вычисления выражения; если выражение имеет значение, то значение не сохраняется. Выполнение оператор-выражения завешается обычным образом тогда и только тогда, когда вычисление выражения завершается обычным образом.
В отличие от Cи и Cи++, язык Ява допускает только некоторые формы выражений, которые будут использованы как операторы-выражение. Заметим, что язык Ява не допускает "приведение к void"- void -это не тип языка Ява, эта традиционная особенность языка Cи написания оператор-выражения, такого как:
(void) ... ; // Это идиоматическое выражение применимо к Cи, но не к Ява!
не действует в языке Ява. С другой стороны, Ява позволяет все наиболее полезные виды выражений в операторах-выражениях и не требует вызова метода, используемого как оператор-выражение для того, чтобы вызвать void метод, так что такой прием практически никогда не нужен. Если же он необходим, то либо оператор присваивания (§15.25), либо оператор объявления локальной переменной (§14.3) могут быть использованы вместо него.
Оператор
if позволяет условное выполнение оператора или условный выбор двух операторов, выполняя один или другой, но не оба сразу.IfThenStatement:if (
Expression)
Statement IfThenElseStatement:if (
Expression)
StatementNoShortIfelse
Statement IfThenElseStatementNoShortIf:if (
Expression)
StatementNoShortIfelse
StatementNoShortIf
Выражение должно иметь тип boolean, иначе происходит ошибка времени компиляции.
Оператор
if-then начинается с вычисления Expression. Если вычисление Expression по некоторым причинам завершается преждевременно, то оператор if-then завершается преждевременно по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на значении Expression:Оператор
if-then-else начинается с вычисления Expression. Если вычисление Expression по некоторой причине завершается преждевременно, тогда оператор if-then-else завершается преждевременно по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на значении Expression:Оператор
switch передает управление одному из нескольких операторов в зависимости от значения выражения.SwitchStatement:switch (
Expression)
SwitchBlock SwitchBlock:{
SwitchBlockStatementGroupsoptSwitchLabelsopt
}
SwitchBlockStatementGroups:SwitchBlockStatementGroup
SwitchBlockStatementGroups
SwitchBlockStatementGroup SwitchBlockStatementGroup: SwitchLabels
BlockStatements SwitchLabels: SwitchLabel SwitchLabels
SwitchLabel SwitchLabel:
case
ConstantExpression: default :
Тип Expression должен быть char, byte, short или int, иначе происходит ошибка времени компиляции.
Телом оператора switch должен быть блок. Любой оператор, непосредственно содержащийся в блоке, может быть помечен одной или более метками case или default. Эти метки связаны с оператором switch значениями константных выражений (§15.27) в метках case.
Все нижесказанное должно быть истинным, иначе результатом будет ошибка времени компиляции:
В Cи и Cи++ телом оператора
switch может быть оператор или операторы с метками case, которые непосредственно не содержатся в операторе switch. Рассмотрим простой цикл:for (i = 0; i < n; ++i) foo();
где
n-положительное число. Приём, показанный ниже, может использоваться в Cи или Cи++ для того, чтобы развернуть цикл, но он не действует в языке Ява:int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Великая проделка Си, Том, case 7: foo(); // но не имеющая силы в Ява. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q >= 0); }
К счастью, этот прием, по-видимому, не широко известен и не часто используется. Кроме того, он менее необходим в настоящее время; этот вид преобразования кода должным образом применяется оптимизирующими компиляторами.
Когда выполняется оператор
switch, вычисляется первое Expression. Если вычисление Expression по некоторой причине завершается преждевременно, то оператор switch завершается преждевременно по той же самой причине. Иначе, выполнение продолжается сравнением значения Expression с каждой константой case. В этом случае существует выбор:Если какой-нибудь оператор непосредственно содержащийся в
теле Block оператора 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-блок, в котором код для каждого случая продолжается в коде для следующего случая. В результате, программа печатает:many too many one too many
В случае, когда “падение сквозь метки” не происходит, должен использоваться оператор
break, как показано в этом примере:class Twomany { static void howMany(int k) { switch (k) { case 1: System.out.println("one"); break; // выход из switch case 2: System.out.println("two"); break; // выход из switch case 3: System.out.println("many"); break; // ненужный, но хороший стиль } } public static void main(String[] args) { howMany(1); howMany(2); howMany(3); } }
Программа печатает следующее:
one two many
Оператор
while неоднократно выполняет Expression и Statement, пока значение Expression - не false.WhileStatement:Expression должно иметь тип boolean, иначе происходит ошибка времени компиляции.while (
Expression)
Statement WhileStatementNoShortIf:while (
Expression)
StatementNoShortIf
Оператор
while начинается с вычисления Expression. Если вычисление Expression по некоторой причине завершается преждевременно, оператор while завершается преждевременно по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на значении Expression:Если значение Expression - false, то в первый раз оно вычисляется, а затем Statement не выполняется.
Преждевременное завершение содержащегося
Statement обрабатывается следующим образом:
- Если оператор while имеет метку L, тогда весь оператор while выполняется снова.
- Если оператор while не имеет метки L, тогда оператор while завершается преждевременно из-за оператора continue с меткой L.
Оператор
do неоднократно выполняет Statement и Expression, пока значение Expression - не false. do
Statement while (
Expression ) ;
Expression должно иметь тип boolean,
иначе происходит ошибка времени компиляции.
Оператор
do начинается с вычисления Statement. Далее существует несколько вариантов:При выполнении оператора
do всегда, по крайней мере один раз, выполняется содержащийся Statement.Преждевременное завершение содержащегося
Statement обрабатывается следующим образом:Следующий код - первая возможная реализация метода
toHexString (§20.7.14) класса 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 выполняет некоторый код инициализации, затем неоднократно выполняет Expression, Statement и некоторый код обновления, пока значение Expression - не false.ForStatement:Expression должно иметь тип boolean, иначе происходит ошибка времени компиляции.for (
ForInitopt;
Expressionopt;
ForUpdateopt)
Statement ForStatementNoShortIf:for (
ForInitopt;
Expressionopt;
ForUpdateopt)
StatementNoShortIf ForInit: StatementExpressionList LocalVariableDeclaration ForUpdate: StatementExpressionList StatementExpressionList: StatementExpression StatementExpressionList,
StatementExpression
Оператор
for начинается с выполнения кода ForInit:Далее, итеративный шаг
for выполняется следующим образом:Если значение Expression - false, то сначала оно вычисляется, а далее Statement не выполняется.
Если Expression отсутствует, тогда оператор for может завершаться нормально единственным путем, т.е. при помощи оператора break.
Преждевременное завершение содержащегося
Statement обрабатывается следующим образом:Оператор
break передает управление наружу оператора, внутри которого он находится.BreakStatement:break
Identifieropt;
Оператор
break без метки пытается передать управление внутренним операторам switch, while, do или for, внутри которых он находится, этот оператор, который называется цель прерывания, затем немедленно нормально завершается . Чтобы быть точным, оператор break без метки всегда завершается преждевременно, причина в операторе break без метки. Если оператор break содержится не в операторе switch, while, do или for, то происходит ошибка времени компиляции.Оператор
break с меткой Identifier пытается передать управление помеченному оператору, внутри которого он находится (§14.6), который содержит тот же самый Identifier в качестве метки; этот оператор, который называется цель прерывания, затем немедленно завершается обычным образом. В этом случае, цель прерывания не должна быть while, do, for или switch оператором. Чтобы быть точным, оператор break с меткой Identifier всегда завершается преждевременно, причина в операторе break с меткой Identifier. Если оператор break содержится вне оператора с меткой Identifier, то происходит ошибка времени компиляции.Можно заметить, что оператор
break всегда завершается преждевременно.Предшествующие описания, говорящие "пытается передать управление" более точны, чем "передает управление" потому что, если имеются некоторые операторы
try (§14.18) в пределах цели прерывания, чьи try-блоки содержат оператор break, тогда любые предложения finally этих операторов try выполняются в порядке от самого внутреннего к внешнему прежде, чем управление передается цели прерывания. Преждевременное завершение предложения finally может прервать передачу управления, инициированную оператором break.В следующем примере, математический граф представлен массивом из массивов. Граф состоит из множества вершин и множества ребер; каждое ребро является стрелкой, которая направлена от некоторой вершины до другой вершины, или от некоторой вершины до этой же вершины. В этом примере предполагается, что нет никаких лишних ребер; то есть для любых двух вершин
P и Q, где Q может совпадать с P, существует максимум одно ребро от P до Q. Вершины представлены целыми числами, и есть ребро от вершины i до вершины edges[i][j] для каждого i и j, для которых ссылка на массив edges[i][j] не генерирует IndexOutOfBoundsException.Задача метода
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; }
//Никакое ребро не
будет удалено; разделите этот
// список.
newedges[k] = edges[k]; break edgelist; }//search
// Копируют список, опуская ребро в позиции z.
int m = edges[k].length - 1; int ne[] = new int[m]; System.arraycopy(edges[k], 0, ne, 0, z); System.arraycopy(edges[k], z+1, ne, z, m-z); newedges[k] = ne; }//edgelist } return new Graph(newedges); } }
Обратите внимание на использование двух операторов с метками
edgelist и search и на использование оператора break. Это допускает код, который копирует список, опуская одно ребро, для того, чтобы быть разделенным между двумя отдельными проверками, проверка для ребра от точки i до точки j и проверка для ребра от точки j до точки i. continueОператор
continue может встретиться только в операторах while, do или for; операторы этих трех видов называются итерационными операторами. Управление передается к месту продолжения цикла итерационного оператора.ContinueStatement:continue
Identifieropt;
Оператор
continue без метки пытается передать управление самому внутреннему оператору while, do или for, внутри которого он находится; этот оператор, который называется цель продолжения затем немедленно заканчивает текущее повторение и начинает новое. Чтобы быть точным, такой оператор continue всегда завершается преждевременно, причина в операторе continue без метки. Если оператор continue содержится не в операторе while, do или for, то происходит ошибка времени компиляции.Оператор
continue с меткой Identifier пытается передать управление помеченному оператору, внутри которого он находится (§14.6), который содержит тот же самый Identifier в качестве метки; этот оператор, который называется цель продолжения, затем немедленно заканчивает текущее повторение и начинает новое. Цель продолжения должна быть оператором while, do или for, или же происходит ошибка времени компиляции. Чтобы быть более точным, оператор continue с меткой Identifier всегда завершается преждевременно, причина в самом операторе continue с меткой Identifier. Если оператор continue содержится вне оператора с меткой Identifier, то происходит ошибка времени компиляции.Можно заметить, что оператор
continue всегда завершается преждевременно.Смотрите описания оператора
while (§14.10), оператора do (§14.11) и оператора for (§14.12) для обсуждения обработки преждевременного завершения из-за оператора continue.Предшествующие описания, говорящие " пытается передать управление" более точны, чем
"передает управление" потому что, если имеются некоторые операторы
try (§14.18) в пределах цели продолжения, чьи try-блоки содержат оператор continue, тогда любые предложения finally этих операторов try выполняются в порядке от самого внутреннего к внешнему, прежде чем управление передается цели продолжения. Преждевременное завершение предложения finally может прервать передачу управления, начатого оператором continue.В примере
Graph в предшествующем разделе, один из операторов break используется для того, чтобы закончить выполнение всего тела внешнего цикла for. Этот оператор break может быть заменен оператором continue, если сам цикл for помечен: class Graph { . . . 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) { . . . } else if (k == j) { . . . } newedges[k] = edges[k]; continue edgelists; }//search . . . }//edgelists return new Graph(newedges); } }Применение которых, если таковые имеются, в значительной степени является вопросом стиля программирования.
Оператор
return возвращает управление в то место, откуда был вызван метод (§8.4,§15.11) или конструктор (§8.6, §15.8).ReturnStatement:return
Expressionopt;
Оператор
return без Expression должен содержаться в теле метода, который объявляется при использовании ключевого слова void, не для возврата какого-нибудь значения (§8.4), или же он (оператор return) должен содержаться в теле конструктора (§8.6). Ошибка времени компиляции происходит в том случае, если оператор return появляется в пределах статического инициализатора (§8.5). Оператор return без Expression пытается передать управление в то место, откуда был вызван метод или конструктор, которые содержат его. Чтобы быть точным, оператор return без Expression всегда завершается преждевременно, причина в самом операторе return без значения.Оператор
return с Expression должен содержаться в объявлении метода, который объявляется для того, чтобы возвратить значение (§8.4), или же происходит ошибка времени компиляции. Expression должно означать переменную или значение некоторого типа T, или происходит ошибка времени компиляции. Тип T должен быть совместим по присваиванию с типом объявленного результата метода (§5.2), или происходит ошибка времени компиляции.Оператор
return c Expression пытается передать управление в то место, откуда был вызван метод, который его содержит; значение Expression становится значением вызова метода. Чтобы быть более точным, выполнение такого оператора return сначала вычисляет Expression. Если вычисление Expression по некоторой причине завершается преждевременно, тогда оператор return завершается преждевременно по той же причине. Если вычисление Expression завершается нормально, порождая значение V, тогда оператор return завершается преждевременно, причина в операторе return со значением V.Можно заметить, что оператор
return всегда завершается преждевременно.Предшествующие описания, говорящие "пытается передать управление" более точны, чем "передает управление" потому что, если имеются некоторые операторы try (§14.18) в пределах метода или конструктора, чьи try-блоки содержат оператор return, тогда любые предложения finally этих операторов try будут выполняться в порядке от самого внутреннего к внешнему прежде, чем управление передается в то место, откуда был вызван метод или конструктор. Преждевременное завершение предложения finally может прервать передачу управления, введенную оператором return.
Оператор
throw вызывает для генерации исключительную ситуацию (§11). Результат - немедленная передача управления (§11.3), которая может выводить составные операторы и составные конструкторы, вычисления статических инициализаторов и инициализаторов полей и вызовы метода до тех пор, пока не обнаружен оператор try, который захватывает сгенерированное значение (§14.18). Если такой оператор try не обнаружен, тогда выполнение потока (§17, §20.20), который исполнял оператор throw, завершается (§11.3) после вызова метода UncaughtException (§20.21.31) для группы потоков, к которой данный поток принадлежит.throw
Expression ;
Expression в операторе throw
должно означать переменную или значение
ссылочного типа, который совместим по
присваиванию с типом Throwable(§5.2), или происходит ошибка
времени компиляции. Кроме того, по крайней мере
одно из следующих трёх условий должно быть
истинным, иначе происходит ошибка времени
компиляции:
Тип Expression - класс Error или подкласс класса Error.
Оператор
throw сначала вычисляет Expression. Если вычисление Expression по некоторой причине завершается преждевременно, тогда оператор throw завершается преждевременно по той же причине. Если вычисление Expression завершается нормально с получением значения V, тогда оператор throw завершается преждевременно, причина в операторе throw со значением V.Можно заметить, что оператор
throw всегда завершается преждевременно.Если имеются некоторые
охватывающие операторы try (§14.18), чьи try-блоки содержат оператор throw, тогда любые предложения finally этих операторов try выполняются, поскольку управление передается наружу до тех пор, пока сгенерированное значение не захватывается. Заметим, что преждевременное завершение предложения finally может прервать передачу управления, инициированную оператором throw.Если оператор
throw содержится в объявлении метода, но его значение не захватывается некоторым оператором try, который его содержит, тогда вызов метода завершается преждевременно из-за оператора throw.Если оператор
throw содержится в объявлении конструктора, но его значение не захватывается некоторым оператором try, который его содержит, тогда выражение создания экземпляра класса (или вызов метода NewInstance класса Class) которое вызвало конструктор, завершится преждевременно из-за оператора throw.Если оператор
throw содержится в статическом инициализаторе (§8.5), тогда проверка времени компиляции гарантирует, что любое его значение - всегда неконтролируемая исключительная ситуация, или же его значение всегда обнаруживается некоторым оператором try, который его содержит. Если, несмотря на эту проверку, значение не захватывается некоторым оператором try, который содержит оператор trow, тогда значение генерируется повторно в том случае, если оно - экземпляр класса Error или одного из его подклассов; иначе, оно заключается в объект ExceptionInInitializerError, который затем генерируется (§12.4.2).В соответствии с соглашением, объявленные пользователем генерирующие типы обычно должны объявляться как подклассы класса
Exception, который в свою очередь является подклассом класса Throwable (§11.5, §20.22).Оператор
synchronized завладевает замком (§17.13) в интересах выполняемого потока, выполняет блок, затем освобождает замок. Пока выполняемый поток обладает замком, никакой другой поток не может завладеть замком.SynchronizedStatement:synchronized (
Expression)
Block
Тип
Expression должен быть ссылочным типом, иначе происходит ошибка времени компиляции.Оператор synchronized начинается с вычисления Expression.
Если вычисление
Expression по некоторой причине завершается преждевременно, тогда оператор synchronized завершается преждевременно по той же причине.Иначе, если значение
Expression - null, тогда генерируется NullPointerException.Иначе, пусть
V - не null значение Expression. Выполняемый поток захватывает замок, связанный с V. Затем выполняется Block. Если выполнение Block завершается нормально, тогда замок освобождается, и оператор synchronized завершается обычным образом. Если выполнение Block завершается преждевременно по какой-нибудь причине, тогда замок освобождается, и затем оператор synchronized завершается преждевременно по той же причине.Завладевание замком, связанным с объектом само не предохраняет другие потоки от обращения к полям объекта или от вызова несинхронизированных методов объекта. Другие потоки также могут использовать методы
synchronized или оператор synchronized обычным способом для того, чтобы достичь взаимного исключения.Замки, которыми завладевают операторы
synchronized - такие же как замки, какими неявно завладевают методы synchronized; см. §8.4.3.5. Одиночный поток может захватывать замок более одного раза. Пример: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
BlockCatches
try
BlockCatchesopt
Finally Catches: CatchClause Catches
CatchClause CatchClause:
catch (
FormalParameter)
Block Finally:finally
Block
Следующее повторяется из
§8.4.1 для того, чтобы написанное выше стало более понятным:
FormalParameter:
Type
VariableDeclaratorId
Следующее повторяется из
§8.3 для того, чтобы написанное выше стало более понятным:
VariableDeclaratorId:
Identifier
VariableDeclaratorId [ ]
Block, стоящий после ключевого слова try, назван try-блоком
оператора try. Block,
стоящий после ключевого слова finally,
назван finally-блоком
оператора try.
Оператор
try может иметь предложения catch (так называемые обработчики исключительной ситуации). Предложение catch должно иметь точно один параметр (который называется параметром исключительной ситуации); объявленный тип параметра исключительной ситуации должен быть классом Throwable или подклассом класса Throwable, или происходит ошибка времени компиляции. Область видимости параметра -переменной - Block предложения catch. Параметр исключительной ситуации не должен иметь то же самое имя что и локальная переменная или параметр, в чей области видимости он (параметр исключительной ситуации) объявляется, или происходит ошибка времени компиляции.Область видимости имени параметра исключительной ситуации -
Block предложения catch. Имя параметра не может быть объявлено повторно как локальная переменная или параметр исключительной ситуации в пределах Block предложения catch; то есть сокрытие имени параметра исключительной ситуации не допускается.Параметры исключительной ситуации при применении не могут быть обозначены квалифицированными именами
(§6.6), они обозначаются только простыми именами.Обработчики исключительной ситуации рассматриваются в порядке слева направо: самое раннее возможное предложение
catch принимает исключение, получая как фактический аргумент объект сгенерированной исключительной ситуации.Предложение
finally гарантирует, что finally-блок выполняется после try-блока и любого catch-блока, который мог бы быть выполненным, независимо от того, как управление покидает try-блок или catch-блок.Обработка
finally-блока достаточно сложна, поэтому оба случая оператора try с finally-блоком и без него описаны отдельно.Оператор
try без finally-блока начинается с выполнения try-блока. Далее существует выбор:В примере:
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("RuntimeException:" + r); } catch (BlewIt b) { System.out.println("BlewIt"); } } }
исключение
BlewIt генерируется методом blowUp. Оператор try-catch в теле main имеет два предложения catch. Тип исключительной ситуации во время выполнения - BlewIt, который не совместим по присваиванию с переменной типа RuntimeException, но совместим по присваиванию с переменной типа BlewIt, поэтому результат примера:BlewIt
Оператор
try с finally-блоком начинается с выполнением try-блока. Далее существует выбор:Пример:
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("BlewIt"); } finally { System.out.println("Uncaught Exception"); } } }
выводит на экран
:Uncaught Exception java.lang.NullPointerException at Test.blowUp(Test.java:7) at Test.main(Test.java:11)
NullPointerException
(который является видом RuntimeException), который генерируется методом blowUp, не захватывается оператором try в теле main, потому что NullPointerException не совместим по присваиванию с переменной типа BlewIt. Это заставляет предложение finally выполняться, после чего поток, выполняющий main, который является потоком только испытательной программы, заканчивается из-за не захваченной исключительной ситуации(§20.21.31), в результате чего печатается имя исключительной ситуации и небольшая трассировка.Если оператор не может быть выполнен, потому что он является недостижимым, то возникает ошибка времени компиляции. Каждый транслятор Ява должен выполнить тщательный потоковый анализ, чтобы удостовериться, что все операторы достижимы.
Этот раздел посвящен точному объяснению слова "достижимый". Идея состоит в том, что каждый оператор должен иметь некоторый возможный путь выполнения от начала конструктора, метода, или статического инициализатора, который содержит оператор к следующему оператору. Анализ операторов принимает во внимание его структуру. Кроме специальной обработки операторов
while, do, и for, у которых выражение условия имеет постоянное значение true, то значения этих выражений не принимаются во внимание в потоковом анализе. Например, транслятор Ява примет код:{ int n = 5; while (n > 7) n = 2; }
Даже если значение
n известно во времени компиляции, и в принципе это может быть известно во времени компиляции, что присваивание на k не может быть выполнено. Транслятор Ява должен функционировать согласно правилам, описанными в этом разделе.Правила в этом разделе определяют два технических условия:
Определенные здесь правила позволяют оператору завершаться нормально только, если оператор достижим.
Введем обозначение: " <=> " означает "тогда и только тогда". Это обозначение будем использовать для описания правил.
Правила следующие:
Содержащийся оператор достижим <=>, когда помеченный оператор достижим.
Содержащийся оператор - достижим <=>, когда оператор while достижим, и выражение условия не состоит из выражения со значением false.
Содержащийся оператор достижим <=>, когда оператор do достижим.
Содержащийся оператор достижим <=>,когда оператор for достижим, и выражение условия не содержит выражение со значением false.
Если finally блок присутствует, он достижим <=>, когда оператор try достижим.
Можно было ожидать, что оператор
if будет обработан следующим способом, но то, что Ява фактически использует его, вообще говоря неверно:Оператор
then достижим <=>, когда оператор if-then достижим, и выражение условия не состоит из выражения с значением false.Этот подход был бы непротиворечив с обработкой других структур управления в Ява. Однако, чтобы поставить оператор
if в наилучшие, для "трансляций условия", то практически правила следующие:Для примера, результат следующего оператора: ошибка времени компиляции:
while (false) { x=3; }
Потому что оператор
x=3; не достижим; но вообще подобный случай:if (false) { x=3; }
не приводит к ошибке времени компиляции. Оптимизирующий транслятор может понять что оператор x=3, никогда не будет выполнен и может опустить код, для этого оператора, из сгенерированного файла класса, но оператора
x=3, не расценен как "недостижимый" в техническом смысле, определенном здесь.Основная причина различной обработки этих операторов состоит в том, чтобы позволить программистам определять переменные типа "флаг":
static final boolean DEBUG = false;
И затем можно написать код типа:
if (DEBUG) { x=3; }
Идея состоит в том, что можно изменить значение
DEBUG с false на true или с true на false и затем компилировать код без других изменений в тексте программы.Эта способность к "условной компиляции" имеет значительное влияние на связь и двоичную совместимость
(§13). Если установка классов, которые используют переменную типа "флаг", откомпилируется, и условный код будет опущен, то код не будет удовлетворяет позже, чтобы распределить только новую версию класса или интерфейса, который содержит определение флагов. Изменение значения флага, следовательно приведет к двоичной несовместимости с предыдущим двоичным кодом (§13.4.8). (Имеются другие причины для такой несовместимости, например использования констант в case метках в swich операторах; смотри (§13.4.8).