Блоки и Операторы


Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Второй Выпуск


ГЛАВА 14

Блоки и Операторы


Последовательностью выполнения программы управляют операторы, которые выполняются для их эффекта и не имеют значений.

Некоторые операторы содержат другие операторы как часть их структуры; такие другие операторы являются подоператорами оператора. Мы говорим, что оператор S сразу содержит оператор U, если нет никакого оператора T, отличающегося от S, и U так, что S содержит T, и T содержит U. Тем же самым способом некоторые операторы содержат выражения (§15) как часть их структуры.

Первый раздел этой главы обсуждает различие между нормальным и резким завершением операторов (§14.1). Большинство остающихся разделов объясняет различные виды операторов, описывая подробно и их нормальное поведение и любой специальный режим резкого завершения.

Блоки объясняются сначала (§14.2), сопровождаются объявлениями локального класса (§14.3) и операторы объявления локальной переменной (§14.4).

Затем грамматический маневр, который обходит знакомое "свисание else"проблема (§14.5) объясняется.

Операторы, которые будут знакомы C и программистам на C++, являются пустым (§14.6), маркировал (§14.7), выражение (§14.8), if (§14.9), switch (§14.10), while (§14.11), do (§14.12), for (§14.13), break (§14.14), continue (§14.15), и return (§14.16) операторы.

В отличие от C и C++, язык программирования Java имеет нет goto оператор. Однако, break и continue операторам позволяют упомянуть метки оператора.

Операторы языка программирования Java, которые не находятся на языке C, throw (§14.17), synchronized (§14.18), и try (§14.19) операторы.

Последний раздел (§14.20) этой главы адресует требование что каждый оператор быть достижимым в определенном техническом смысле.

14.1 Нормальное и Резкое Завершение Операторов

У каждого оператора есть нормальный режим выполнения, в котором выполняются определенные вычислительные шаги. Следующие разделы описывают нормальный режим выполнения для каждого вида оператора.

Если все шаги выполняются как описано без индикации относительно резкого завершения, оператор, как говорят, обычно завершается. Однако, определенные события могут препятствовать тому, чтобы оператор обычно завершался:

Если такое событие имеет место, то выполнение одного или более операторов может быть завершено прежде, чем все шаги их нормального режима выполнения завершились; такие операторы, как говорят, завершаются резко.

У резкого завершения всегда есть связанная причина, которая является одним из следующего:

Сроки, "полные обычно" и "полный резко" также, применяются к оценке выражений (§15.6). Единственная причина, которую выражение может завершить резко, состоит в том, что исключение выдается из-за любого a throw с данным значением (§14.17) или исключение на этапе выполнения или ошибка (§11, §15.6).

Если оператор оценивает выражение, резкое завершение выражения всегда вызывает непосредственное резкое завершение оператора с той же самой причиной. Все последующие шаги в нормальном режиме выполнения не выполняются.

Если иначе не определено в этой главе, резкое завершение подоператора вызывает непосредственное резкое завершение оператора непосредственно с той же самой причиной, и все последующие шаги в нормальном режиме выполнения оператора не выполняются.

Если иначе не определено, оператор обычно завершается, если все выражения, которые он оценивает и все подоператоры, которые он выполняет полный обычно.

14.2 Блоки

Блок является последовательностью операторов, объявлений локального класса и операторов объявления локальной переменной в пределах фигурных скобок.

Блок выполняется, выполняя каждый из операторов объявления локальной переменной и других операторов в порядке от сначала, чтобы продлиться (слева направо). Если все эти операторы блока, полные обычно, то блок обычно завершается. Если любой из этих операторов блока, полных резко по любой причине, то блок завершается резко по той же самой причине.

14.3 Объявления Локального класса

Локальный класс является вложенным классом (§8), который не является элементом любого класса, и у этого есть имя. Все локальные классы являются внутренними классами (§8.1.2). Каждый оператор объявления локального класса сразу содержался блоком. Операторы объявления локального класса могут быть смешаны свободно с другими видами операторов в блоке.

Контекст локального класса, объявленного в блоке, является остальной частью сразу блока включения, включая его собственное объявление класса.

Имя локального класса C не может быть повторно объявлено как локальный класс непосредственно метода включения, конструктора, или блока инициализатора в рамках C, или ошибка времени компиляции происходит. Однако, объявление локального класса может быть затенено (§6.3.1) где угодно в объявлении класса, вложенном в пределах контекста объявления локального класса. У локального класса нет канонического имени, и при этом у него нет полностью определенного имени.

Это - ошибка времени компиляции, если объявление локального класса содержит кого-либо из следующих модификаторов доступа: public, protected, private, или static.

Вот пример, который иллюстрирует несколько аспектов правил, данных выше:

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.

14.4 Операторы объявления Локальной переменной

Оператор объявления локальной переменной объявляет одно или более имен локальной переменной.

Следующее повторяется от §8.3, чтобы сделать представление здесь более четким:

Каждый оператор объявления локальной переменной сразу содержался блоком. Операторы объявления локальной переменной могут быть смешаны свободно с другими видами операторов в блоке.

Объявление локальной переменной может также появиться в заголовке a for оператор (§14.13). В этом случае это выполняется тем же самым способом, как будто это была часть оператора объявления локальной переменной.

14.4.1 Операторы объявления Локальной переменной и Типы

Каждый оператор объявления в объявлении локальной переменной объявляет одну локальную переменную, именем которой является Идентификатор, который появляется в операторе объявления.

Если дополнительный финал ключевого слова появляется в начале оператора объявления, объявляемая переменная является заключительной переменной (§4.5.4).

Тип переменной обозначается Типом, который появляется в объявлении локальной переменной, сопровождаемом любыми парами скобки, которые следуют за Идентификатором в операторе объявления.

Таким образом, объявление локальной переменной:

int a, b[], c[][];
эквивалентно серии объявлений:

int a;
int[] b;
int[][] c;
Скобки позволяются в операторах объявления как намек на традицию C и C++. Общее правило, однако, также означает что объявление локальной переменной:

float[][] f[][], g[][][], h[];													// Yechh!
эквивалентно серии объявлений:

float[][][][] f;
float[][][][][] g;
float[][][] h;
Мы не рекомендуем такую "смешанную нотацию" для объявлений массива.

Локальная переменная типа float всегда содержит значение, которое является элементом набора значений плавающего (§4.2.3); точно так же локальная переменная типа double всегда содержит значение, которое является элементом двойного набора значений. Это не разрешается для локальной переменной типа float чтобы содержать элемент набора значений "пускают в ход расширенную экспоненту", которая не является также элементом набора значений плавающего, ни для локальной переменной типа double чтобы содержать элемент набора значений "удваивают расширенную экспоненту", которая не является также элементом двойного набора значений.

14.4.2 Контекст Объявлений Локальной переменной

Контекст объявления локальной переменной в блоке (§14.4.2) является остальной частью блока, в котором объявление появляется, запускаясь с его собственного инициализатора (§14.4) и включая дальнейшие операторы объявления направо в операторе объявления локальной переменной.

Имя локальной переменной v не может быть повторно объявлено как локальная переменная непосредственно метода включения, конструктора или блока инициализатора в рамках v, или ошибка времени компиляции происходит. Имя локальной переменной v не может быть повторно объявлено как параметр исключения пункта выгоды в операторе попытки непосредственно метода включения, конструктора или блока инициализатора в рамках v, или ошибка времени компиляции происходит. Однако, локальная переменная метода или блока инициализатора может быть затенена (§6.3.1) где угодно в объявлении класса, вложенном в рамках локальной переменной.

Локальная переменная не может быть отнесена в использование полностью определенного имени (§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

Here is another example:

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 объявленный ранее в блоке.

Контекст локальной переменной объявляется в a for оператор является остальной частью for оператор, включая его собственный инициализатор.

Если объявление идентификатора как локальная переменная того же самого метода, конструктора, или блока инициализатора появляется в рамках параметра или локальной переменной того же самого имени, ошибка времени компиляции происходит.

Таким образом следующий пример не компилирует:

class Test {
	public static void main(String[] args) {
		int i;
		for (int i = 0; i < 10; i++)
			System.out.println(i);
	}
}
Это ограничение помогает обнаружить некоторых иначе очень неясные ошибки. Подобное ограничение на затенение элементов локальными переменными было оценено непрактичное, потому что добавление элемента в суперклассе могло заставить подклассы должными быть переименовывать локальные переменные. Связанные соображения делают ограничения на затенение локальных переменных элементами вложенных классов, или на затенении локальных переменных локальными переменными объявленными в пределах вложенных классов непривлекательный также. Следовательно, следующий пример компилирует без ошибки:

class Test {
	public static void main(String[] args) {
		int i;
		class Local {
			{
				for (int i = 0; i < 10; i++)
				System.out.println(i);
			}
		}
		new Local();
	}
}
С другой стороны локальные переменные с тем же самым именем могут быть объявлены в двух отдельных блоках или 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

14.4.3 Затенение Имен Локальными переменными

Если имя, объявленное как локальная переменная, уже объявляется как имя поля, то то внешнее описание затенено (§6.3.1) всюду по контексту локальной переменной. Точно так же, если имя уже объявляется как имя переменной или название параметра, то то внешнее описание затенено всюду по контексту локальной переменной (при условии, что затенение не вызывает ошибку времени компиляции по правилам §14.4.2). К затененному имени можно иногда получать доступ, используя соответственно полностью определенное имя.

Например, ключевое слово this может использоваться, чтобы получить доступ к затененному полю x, использование формы this.x. Действительно, эта идиома обычно появляется в конструкторах (§8.8):

class Pair {
	Object first, second;
	public Pair(Object first, Object second) {
		this.first = first;
		this.second = second;
	}
}
В этом примере конструктор берет параметры, имеющие те же самые имена как поля, которые будут инициализированы. Это более просто чем необходимость изобрести различные имена для параметров и не также сбивает с толку в этом стилизованном контексте. Вообще, однако, это считают плохим стилем, чтобы иметь локальные переменные с теми же самыми именами как поля.

14.4.4 Выполнение Объявлений Локальной переменной

Оператор объявления локальной переменной является исполнимым оператором. Каждый раз, когда это выполняется, операторы объявления обрабатываются в порядке слева направо. Если у оператора объявления есть выражение инициализации, выражение оценивается, и его значение присваивается переменной. Если у оператора объявления нет выражения инициализации, то компилятор Java должен доказать, используя точно алгоритм, данный в §16, что каждой ссылке на переменную обязательно предшествует выполнение присвоения на переменную. Если дело обстоит не так, то ошибка времени компиляции происходит.

Каждая инициализация (кроме первого) выполняется, только если оценка предыдущего выражения инициализации обычно завершается. Выполнение объявления локальной переменной обычно завершается, только если оценка последнего выражения инициализации обычно завершается; если объявление локальной переменной не содержит выражений инициализации, то выполнение его всегда обычно завершается.

14.5 Операторы

Есть много видов операторов в языке программирования Java. Большинство соответствует операторам в C и языках C++, но некоторые уникальны.

Как в C и C++, if оператор языка программирования Java страдает от так называемого "свисания else проблема," иллюстрированная этим обманчиво отформатированным примером:


if (door.isOpen())
	if (resident.isVisible())
		resident.greet("Hello!");
else door.bell.ring();	// A "dangling else"
Проблема - это оба внешнее if оператор и внутреннее if оператор мог бы очевидно собственный else пункт. В этом примере можно было бы предположить что программист, предназначенный else пункт, чтобы принадлежать внешнему if оператор. Язык программирования Java, как C и C++ и много языков программирования перед ними, произвольно устанавливает декретом что else пункт принадлежит самому внутреннему if которому это могло бы возможно принадлежать. Это правило получается следующей грамматикой:

Следующее повторяется от §14.9, чтобы сделать представление здесь более четким:

Операторы таким образом грамматически делятся на две категории: те, которые могли бы закончиться в if оператор, который имеет нет else пункт ("короткий if оператор") и те, которые определенно не делают. Только операторы, которые определенно не заканчиваются в коротком if оператор может появиться как непосредственный подоператор перед ключевым словом else в if оператор, который действительно имеет else пункт.

Это простое правило предотвращает "свисание else"проблема. Поведение при выполнении оператора с "нет короткий if"ограничение идентично поведению при выполнении того же самого вида оператора без "нет короткий if"ограничение; различие оттягивается просто, чтобы разрешить синтаксическую трудность.

14.6 Пустой Оператор

Пустой оператор ничего не делает.

Выполнение пустого оператора всегда обычно завершается.

14.7 Помеченные операторы

У операторов могут быть префиксы метки.

Идентификатор, как объявляют, является меткой сразу содержавшего Оператора.

В отличие от C и C++, язык программирования Java имеет нет goto оператор; метки оператора идентификатора используются с break (§14.14) или continue (§14.15) операторы, появляющиеся где угодно в пределах помеченного оператора.

Контекст метки, объявленной помеченным оператором, является оператором, сразу включенным помеченным оператором.

Позвольте l быть меткой, и позволять м. быть сразу методом включения, конструктором, инициализатором экземпляра или статическим инициализатором. Это - ошибка времени компиляции если l тени (§6.3.1) объявление другой метки, сразу включенной в м.

Нет никакого ограничения против использования того же самого идентификатора как метка и как имя пакета, класса, интерфейса, метода, поля, параметра, или локальной переменной. Использование идентификатора, чтобы маркировать оператор не затеняет (§6.3.2) пакет, класс, интерфейс, метод, поле, параметр, или локальную переменную с тем же самым именем. Использование идентификатора как класс, интерфейс, метод, поле, локальная переменная или как параметр обработчика исключений (§14.19) не затеняет метку оператора с тем же самым именем.

Помеченный оператор выполняется, выполняя сразу содержавший Оператор. Если оператор маркируется Идентификатором, и содержавший Оператор завершается резко из-за a break с тем же самым Идентификатором тогда помеченный оператор обычно завершается. Во всех других случаях резкого завершения Оператора помеченный оператор завершается резко по той же самой причине.

14.8 Операторы выражения

Определенные виды выражений могут использоваться в качестве операторов следующим их с точками с запятой:

Оператор выражения выполняется, оценивая выражение; если у выражения есть значение, значение отбрасывается. Выполнение оператора выражения обычно завершается, если и только если оценка выражения обычно завершается.

В отличие от C и C++, язык программирования Java позволяет только определенным формам выражений использоваться в качестве операторов выражения. Отметьте, что язык программирования Java не позволяет "бросок void"-void не тип так традиционный прием C записи оператора выражения, такого как:

(void) ... ;			// incorrect!
не работает. С другой стороны язык позволяет все самые полезные виды выражений в операторах выражений, и он не требует, чтобы вызов метода, используемый в качестве оператора выражения вызвал a void метод, таким образом, такой прием никогда не почти необходим. Если прием необходим, или оператор присваивания (§15.26) или оператор объявления локальной переменной (§14.4) может использоваться вместо этого.

14.9 if Оператор

if оператор позволяет условное выполнение оператора или условный выбор двух операторов, выполняясь один или другой, но не оба.

У Выражения должен быть тип boolean, или ошибка времени компиляции происходит.

14.9.1 if-then Оператор

if-then оператор выполняется первой оценкой Выражения. Если оценка Выражения завершается резко по некоторым причинам, if-then оператор завершается резко по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на получающемся значении:

14.9.2 if-then-else Оператор

if-then-else оператор выполняется первой оценкой Выражения. Если оценка Выражения завершается резко по некоторым причинам, то if-then-else оператор завершается резко по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на получающемся значении:

14.10 switch Оператор

switch оператор передает управление одному из нескольких операторов в зависимости от значения выражения.

Тип Выражения должен быть char, byte, short, или int, или ошибка времени компиляции происходит.

Тело a switch оператор известен как блок переключателя. Любой оператор, сразу содержавший блоком переключателя, может быть маркирован один или больше case или default метки. Эти метки, как говорят, связываются с switch оператор, как значения константных выражений (§15.28) в 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 оператор выполняется, сначала Выражение оценивается. Если оценка Выражения завершается резко по некоторым причинам, switch оператор завершается резко по той же самой причине. Иначе, выполнение продолжается, сравнивая значение Выражения с каждым case постоянный. Затем есть выбор:

Если любой оператор, сразу содержавший Телом блока switch оператор завершается резко, он обрабатывается следующим образом:

Как в C и C++, выполнение операторов в блоке переключателя "проваливается метки."

Например, программа:

class Toomany {
	static void howMany(int k) {
		switch (k) {
		case 1:			System.out.print("one ");
		case 2:			System.out.print("too ");
		case 3:			System.out.println("many");
		}
	}
	public static void main(String[] args) {
		howMany(3);
		howMany(2);
		howMany(1);
	}
}
содержит блок переключателя, в котором код для каждого случая проваливается в код для следующего случая. В результате печатные издания программы:

many
too many
one too many
Если код не должен провалиться случай, чтобы случиться этим способом, то 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

14.11, в то время как Оператор

while оператор неоднократно выполняет Выражение и Оператор, пока значение Выражения не false.

У Выражения должен быть тип boolean, или ошибка времени компиляции происходит.

A while оператор выполняется первой оценкой Выражения. Если оценка Выражения завершается резко по некоторым причинам, while оператор завершается резко по той же самой причине. Иначе, выполнение продолжается, делая выбор, основанный на получающемся значении:

Если значение Выражения false в первый раз, когда это оценивается, тогда Оператор не выполняется.

14.11.1 Резкое Завершение

Резкое завершение содержавшего Оператора обрабатывается следующим способом:

14.12 делают Оператор

do оператор неоднократно выполняет Оператор и Выражение, пока значение Выражения не false.

У Выражения должен быть тип boolean, или ошибка времени компиляции происходит.

A do оператор выполняется первым выполнением Оператора. Затем есть выбор:

Выполнение a do оператор всегда выполняет содержавший Оператор, по крайней мере, однажды.

14.12.1 Резкое Завершение

Резкое завершение содержавшего Оператора обрабатывается следующим способом:

14.12.2 Пример делает оператор

Следующий код является одной возможной реализацией 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 оператор является соответствующей управляющей структурой.

14.13 for Оператор

for оператор выполняет некоторый код инициализации, затем неоднократно выполняет Выражение, Оператор, и некоторый код обновления, пока значение Выражения не false.

У Выражения должен быть тип boolean, или ошибка времени компиляции происходит.

14.13.1 Инициализация for оператор

A for оператор выполняется первым выполнением кода ForInit:

Если код ForInit является объявлением локальной переменной, он выполняется, как будто это был оператор объявления локальной переменной (§14.4) появляющийся в блоке. Контекст локальной переменной объявляется в части ForInit a for оператор (§14.13) включает все следующее:

Если выполнение объявления локальной переменной завершается резко по какой-либо причине, for оператор завершается резко по той же самой причине.

14.13.2 Итерация для оператора

Затем, a for итеративный шаг выполняется, следующим образом:

Если значение Выражения false в первый раз, когда это оценивается, тогда Оператор не выполняется.

Если Выражение не присутствует, то единственный путь a for оператор может завершиться, обычно при помощи a break оператор.

14.13.3 Резкое Завершение for оператор

Резкое завершение содержавшего Оператора обрабатывается следующим способом:

14.14 break Оператор

Оператор завершения передает управление из оператора включения.

A break оператор без метки пытается передать управление самому внутреннему включению switch, while, do, или for оператор сразу метода включения или блока инициализатора; этот оператор, который вызывают целью повреждения, тогда сразу обычно завершается.

Быть точным, a break оператор без метки всегда завершается резко, причина, являющаяся a break без метки. Если нет switch, while, do, или for оператор включает break оператор, ошибка времени компиляции происходит.

A break оператор с меткой пытается передать управление помеченному оператору включения (§14.7), у которого есть тот же самый Идентификатор как его метка; этот оператор, который вызывают целью повреждения, тогда сразу обычно завершается. В этом случае, break предназначайтесь не должен быть a while, do, for, или switch оператор. Оператор завершения должен обратиться к метке в пределах сразу метода включения или блока инициализатора. Нет никаких нелокальных переходов.

Быть точным, a break оператор с меткой всегда завершается резко, причина, являющаяся a break с меткой. Если никакой помеченный оператор с Идентификатором как его метка не включает break оператор, ошибка времени компиляции происходит.

Это может быть замечено, тогда, это a break оператор всегда завершается резко.

Предыдущие описания говорят "попытки передать управление", а не только "управление передачами" потому что, если есть кто-либо try операторы (§14.19) в пределах повреждения предназначаются чей try блоки содержат break оператор, тогда любой finally пункты тех try операторы выполняются, в порядке, самом внутреннем к наиболее удаленному, прежде, чем управление будет передано цели повреждения. Резкое завершение a finally пункт может разрушить передачу управления, инициируемого a break оператор.

В следующем примере математический график представляется массивом массивов. График состоит из ряда узлов и ряда краев; каждый край является стрелкой, которая указывает от некоторого узла до некоторого другого узла, или от узла до себя. В этом примере предполагается, что нет никаких избыточных краев; то есть, для любых двух узлов P и Q, где Q может быть тем же самым как P, есть самое большее один край от P до К. Ноудса, представляются целыми числами, и есть край от узла i к узлу edges[я][j] для того, каждого я и j, для который ссылка массива edges[я][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;
					}
					// 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.

14.15 continue Оператор

A continue оператор может произойти только в a while, do, или for оператор; операторы этих трех видов вызывают операторами цикла. Управление передает к точке продолжения цикла оператора цикла.

A continue оператор без метки пытается передать управление самому внутреннему включению while, do, или for оператор сразу метода включения или блока инициализатора; этот оператор, который вызывают продолжать целью, тогда сразу заканчивает текущую итерацию и начинает новый.

Быть точным, такой continue оператор всегда завершается резко, причина, являющаяся a continue без метки. Если нет while, do, или for оператор сразу метода включения или блока инициализатора включает continue оператор, ошибка времени компиляции происходит.

A continue оператор с меткой пытается передать управление помеченному оператору включения (§14.7), у которого есть тот же самый Идентификатор как его метка; тот оператор, который вызывают продолжать целью, тогда сразу заканчивает текущую итерацию и начинает новый. Продолжать цель должна быть a while, do, или for оператор или ошибка времени компиляции происходят. Продолжать оператор должен обратиться к метке в пределах сразу метода включения или блока инициализатора. Нет никаких нелокальных переходов.

Более точно, a continue оператор с меткой всегда завершается резко, причина, являющаяся a continue с меткой. Если никакой помеченный оператор с Идентификатором как его метка не содержит continue оператор, ошибка времени компиляции происходит.

Это может быть замечено, тогда, это a continue оператор всегда завершается резко.

См. описания while оператор (§14.11), do оператор (§14.12), и for оператор (§14.13) для обсуждения обработки резкого завершения из-за continue.

Предыдущие описания говорят "попытки передать управление", а не только "управление передачами" потому что, если есть кто-либо try операторы (§14.19) в пределах продолжать цели, чей try блоки содержат continue оператор, тогда любой finally пункты тех try операторы выполняются, в порядке, самом внутреннем к наиболее удаленному, прежде, чем управление будет передано продолжать цели. Резкое завершение a finally пункт может разрушить передачу управления, инициируемого a continue оператор.

В Graph пример в предыдущем разделе, одном из break операторы используются, чтобы закончить выполнение всего тела наиболее удаленного for цикл. Это break может быть заменен a 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);
	}
}
Чтобы использовать, если также, в значительной степени вопрос стиля программирования.

14.16 return Оператор

A return оператор возвращает управление invoker метода (§8.4, §15.12) или конструктор (§8.8, §15.9).

A return оператор без Выражения должен содержаться в теле метода, который объявляется, используя ключевое слово void, не возвратить любое значение (§8.4), или в теле конструктора (§8.8). Ошибка времени компиляции происходит если a return оператор появляется в пределах инициализатора экземпляра или статического инициализатора (§8.7). A return оператор без Выражения пытается передать управление invoker метода или конструктора, который содержит это.

Быть точным, a return оператор без Выражения всегда завершается резко, причина, являющаяся a return без значения.

A return оператор с Выражением должен содержаться в объявлении метода, которое, как объявляют, возвращает значение (§8.4), или ошибка времени компиляции происходит. Выражение должно обозначить переменную или значение некоторого типа T, или ошибка времени компиляции происходит. Тип T должен быть присваиваемым (§5.2) объявленному типу результата метода, или ошибка времени компиляции происходит.

A return оператор с Выражением пытается передать управление invoker метода, который содержит это; значение Выражения становится значением вызова метода. Более точно, выполнение такого return оператор сначала оценивает Выражение. Если оценка Выражения завершается резко по некоторым причинам, то return оператор завершается резко по этой причине. Если оценка Выражения обычно завершается, производя значение V, то return оператор завершается резко, причина, являющаяся a return со значением V. Если выражение имеет тип float и не строго FP (§15.4), тогда значение может быть элементом или набора значений плавающего или набора значений "расширенная экспонента плавающая" (§4.2.3). Если выражение имеет тип double и не строго FP, тогда значение может быть элементом или двойного набора значений или набора значений "двойная расширенная экспонента".

Это может быть замечено, тогда, это a return оператор всегда завершается резко.

Предыдущие описания говорят "попытки передать управление", а не только "управление передачами" потому что, если есть кто-либо try операторы (§14.19) в пределах метода или конструктора, чей try блоки содержат return оператор, тогда любой finally пункты тех try операторы будут выполняться, в порядке, самом внутреннем к наиболее удаленному, прежде, чем управление будет передано invoker метода или конструктора. Резкое завершение a finally пункт может разрушить передачу управления, инициируемого a return оператор.

14.17 throw Оператор

A throw оператор заставляет исключение (§11) быть брошенным. Результатом является непосредственная передача управления (§11.3), который может выйти из многократных операторов и многократного конструктора, инициализатора экземпляра, статического инициализатора и полевых оценок инициализатора, и вызовов метода до a try оператор (§14.19) находится, который ловит брошенное значение. Если не такой try оператор находится, тогда выполнение потока (§17), который выполнился throw завершается (§11.3) после вызова uncaughtException метод для группы потока, которой принадлежит поток.

Выражение в операторе броска должно обозначить переменную или значение ссылочного типа, который присваиваем (§5.2) типу Throwable, или ошибка времени компиляции происходит. Кроме того по крайней мере одно из следующих трех условий должно быть истиной, или ошибка времени компиляции происходит:

A throw оператор сначала оценивает Выражение. Если оценка Выражения завершается резко по некоторым причинам, то throw завершается резко по этой причине. Если оценка Выражения обычно завершается, производя не -null оцените V, тогда throw оператор завершается резко, причина, являющаяся a throw со значением V. Если оценка Выражения обычно завершается, производя a null значение, затем экземпляр V' класса NullPointerException создается и бросается вместо null. throw оператор тогда завершается резко, причина, являющаяся a throw со значением V'.

Это может быть замечено, тогда, это a throw оператор всегда завершается резко.

Если есть какое-либо включение try операторы (§14.19), чей try блоки содержат throw оператор, тогда любой finally пункты тех try операторы выполняются, поскольку управление передается исходящее, пока брошенное значение не поймано. Отметьте что резкое завершение a finally пункт может разрушить передачу управления, инициируемого a throw оператор.

Если a throw оператор содержится в объявлении метода, но его значение не поймано некоторыми try оператор, который содержит это, тогда вызов метода, завершается резко из-за throw.

Если a throw оператор содержится в объявлении конструктора, но его значение не поймано некоторыми try оператор, который содержит это, тогда выражение создания экземпляра класса, которое вызвало конструктора, завершится резко из-за throw.

Если a throw оператор содержится в статическом инициализаторе (§8.7), затем проверка времени компиляции гарантирует, что или ее значение всегда является исключением непроверенным или ее значением, всегда пойман некоторыми try оператор, который содержит это. Если во время выполнения, несмотря на эту проверку, значение не поймано некоторыми try оператор, который содержит throw оператор, тогда значение повторно бросается, если это - экземпляр класса Error или один из его подклассов; иначе, это обертывается в ExceptionInInitializerError объект, который тогда бросается (§12.4.2).

Если a throw оператор содержится в инициализаторе экземпляра (§8.6), затем проверка времени компиляции гарантирует, что или ее значение всегда является исключением непроверенным или ее значением, всегда пойман некоторым оператором попытки, который содержит это, или тип выданного исключения (или один из его суперклассов) происходит в пункте бросков каждого конструктора класса.

Условно, объявленный пользователем типами throwable, как должны обычно объявлять, подклассы класса Exception, который является подклассом класса Throwable (§11.5).

14.18 synchronized Оператор

A synchronized оператор получает блокировку взаимного исключения (§17.13) от имени выполняющегося потока, выполняет блок, затем выпускает блокировку. В то время как выполняющемуся потоку принадлежит блокировка, никакой другой поток не может получить блокировку.

Тип Выражения должен быть ссылочным типом, или ошибка времени компиляции происходит.

A synchronized оператор выполняется первой оценкой Выражения.

Если оценка Выражения завершается резко по некоторым причинам, то synchronized оператор завершается резко по той же самой причине.

Иначе, если значение Выражения null, a NullPointerException бросается.

Иначе, позвольте не -null значение Выражения быть V. Выполняющийся поток блокирует блокировку, связанную с V. Затем Блок выполняется. Если выполнение Блока обычно завершается, то блокировка разблокирована и synchronized оператор обычно завершается. Если выполнение Блока завершается резко по какой-либо причине, то блокировка разблокирована и synchronized оператор тогда завершается резко по той же самой причине.

Получение блокировки, связанной с объектом, не делает себя, препятствуют тому, чтобы другие потоки получили доступ к полям объекта или вызвали несинхронизируемые методы на объект. Другие потоки могут также использовать synchronized методы или synchronized оператор стандартным способом, чтобы достигнуть взаимного исключения.

Блокировки, полученные synchronized операторы являются тем же самым как блокировками, которые получаются неявно synchronized методы; см. §8.4.3.6. Единственный поток может содержать блокировку не раз.

Пример:

class Test {
	public static void main(String[] args) {
		Test t = new Test();
		synchronized(t) {
			synchronized(t) {
				System.out.println("made it!");
			}
		}
	}
}
печатные издания:

made it!
Этот пример был бы мертвая блокировка, если единственному потоку не разрешили заблокировать блокировку не раз.

14.19 try оператор

A try оператор выполняет блок. Если значение бросается и try оператор имеет один или больше catch пункты, которые могут поймать это, затем управляют, будет передан первому такой catch пункт. Если try у оператора есть a finally пункт, тогда другой блок кода выполняется, независимо от того ли try блок завершается обычно или резко, и независимо от того ли a catch пункт является первым данным контролем.

Следующее повторяется от §8.4.1, чтобы сделать представление здесь более четким:

Следующее повторяется от §8.3, чтобы сделать представление здесь более четким:

Блок сразу после ключевого слова try вызывается try блок try оператор. Блок сразу после ключевого слова finally вызывается finally блок try оператор.

A try оператор может иметь catch пункты (также названный обработчиками исключений). A catch у пункта должен быть точно один параметр (который вызывают параметром исключения); объявленный тип параметра исключения должен быть классом Throwable или подкласс Throwable, или ошибка времени компиляции происходит. Контекстом переменной параметра является Блок catch пункт.

У параметра исключения пункта выгоды не должно быть того же самого имени как локальная переменная или параметр метода или блока инициализатора сразу включение пункта выгоды, или ошибка времени компиляции происходит.

Контекст параметра обработчика исключений, который объявляется в a catch пункт a try оператор (§14.19) является всем блоком, связанным с catch.

В пределах Блока catch пункт, имя параметра не может быть повторно объявлено как локальная переменная непосредственно метода включения или блока инициализатора, и при этом это не может быть повторно объявлено как параметр исключения пункта выгоды в операторе попытки непосредственно метода включения или блока инициализатора, или ошибка времени компиляции происходит. Однако, параметр исключения может быть затенен (§6.3.1) где угодно в объявлении класса, вложенном в пределах Блока пункта выгоды.

Это - ошибка времени компиляции, если параметр исключения, который объявляется финалом, присваивается в пределах тела пункта выгоды.

Параметры исключения не могут быть отнесены в использование полностью определенных имен (§6.6), только простыми именами.

Обработчики исключений рассматривают в слева направо порядке: самое раннее catch пункт принимает исключение, получая как его фактический параметр брошенный объект исключения.

A finally пункт гарантирует что finally блок выполняется после try блок и любой catch блок, который мог бы быть выполнен, независимо от того как листы управления try блок или catch блок.

Обработка finally блок довольно сложен, таким образом, два случая a try оператор с и без a finally блок описывается отдельно.

14.19.1 Выполнение выгоды попытки

A try оператор без a 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

14.19.2 Выполнение "попытки ловит наконец"

A try оператор с a 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, потому что a NullPointerException не присваиваемо переменной типа BlewIt. Это вызывает finally пункт тот, чтобы выполнить, после который выполнение потока main, то, который является единственным потоком тестовой программы, завершается из-за непойманного исключения, которое обычно приводит к печати имени исключения и простого следа.

14.20 Недостижимых Операторов

Это - ошибка времени компиляции, если оператор не может быть выполнен, потому что это недостижимо. Каждый компилятор Java должен выполнить консервативный анализ потоков, определенный здесь, чтобы удостовериться, что все операторы достижимы.

Этот раздел посвящается точному объяснению "достижимого" слова. Идея состоит в том, что должен быть некоторый возможный путь выполнения с начала конструктора, метода, инициализатора экземпляра или статического инициализатора, который содержит оператор к оператору непосредственно. Анализ принимает во внимание структуру операторов. За исключением специального режима while, do, и for операторы, у выражения условия которых есть постоянная величина true, значения выражений не принимаются во внимание в анализе потоков.

Например, компилятор Java примет код:

{
	int n = 5;
	while (n > 7) k = 2;
}
даже при том, что значение n известен во время компиляции, и в принципе можно быть известно во время компиляции что присвоение на k никогда не может выполняться.

Компилятор Java должен работать согласно правилам, размеченным в этом разделе.

Правила в этом разделе определяют два технических термина:

Определения здесь позволяют оператору обычно завершаться, только если это достижимо.

Чтобы сократить описание правил, общепринятое сокращение "эквивалентность" используется, чтобы означать "если и только если."

Правила следующие:

then- оператор является достижимой эквивалентностью if-then оператор достижим, и выражение условия не является константным выражением, значение которого false.

Фактические правила для, если оператор следующие:

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.8). (Есть другие причины такой несовместимости также, такие как использование констант в case метки в switch операторы; см. §13.4.8.)


Содержание | Предыдущий | Следующий | Индекс Спецификация языка Java
Второй Выпуск
Авторское право © Sun Microsystems, Inc 2000 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к jls@java.sun.com



Spec-Zone.ru - all specs in one place