Содержание | Предыдущая | Следующая


ГЛАВА 11

Исключения

Когда программа нарушает семантические ограничения языка Ява, виртуальная машина Ява дает сигнал об этой ошибке программе в виде исключения. Пример такого нарушения - попытка индексировать вне границ массива. Некоторые языки программирования и их реализации противодействуют таким ошибкам безоговорочным завершением программы; другие позволяют реагировать произвольным или непредсказуемым образом. Никакой из этих подходов не совместим с задачами языка Ява: обеспечивать мобильность и устойчивость к ошибкам. Взамен, Ява указывает на то, что исключение будет генерироваться тогда, когда будут нарушены семантические ограничения и будет вызывать нелокальную передачу управления от места, где происходит исключение до места, которое может определяться программистом. Исключение генерируется в том месте, где оно произошло, и перехватывается в том месте, которому передается контроль.

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

Каждое исключение представляется экземпляром класса Throwable или одним из его подклассов; такой объект может использоваться для того, чтобы нести информацию с места, в котором исключение происходит к обработчику, который это исключение перехватывает. Обработчики устанавливаются предложениями catch операторов try (§14.18). В процессе генерирования исключения, виртуальная машина языка Ява завершает преждевременно, один за другим, любые выражения, операторы, вызовы метода и конструктора, статические инициализаторы и поле инициализированных выражений, которые начали, но не закончили выполнение в текущем потоке. Этот процесс продолжается до тех пор, пока обработчик не найдет, что указывает на то, что это обрабатывается как частичное исключение присваиванием имен класса исключения или суперкласса класса исключения. Если такой обработчик не найден, тогда метод uncaughtException (§20.21.31) вызывается для ThreadGroup, который является породителем нитевого потока, таким образом весь объем работы производится для того, чтобы избежать ситуации необработанного исключения.

Механизм исключений Ява объединен с моделью синхронизации Ява (§17), так , что блокировки реализованы как synchronized-операторы (§14.17) и вызовы synchronized-методов, завершаемых преждевременно (§8.4.3.5, §15.11).

Эта глава описывает различные причины исключений (§11.1). Здесь подробно говорится о том как проверяются исключения во время компиляции(§11.2) и обрабатываются во время выполнения (§11.3). Разобранный пример (§11.4) сопровождается объяснением иерархии исключительных ситуаций и стандартных классов исключений (§11.5).

11.1 Причины исключений

Исключение генерируется по одной из трех причин:

 

 

Исключения представлены экземплярами класса Throwable и его подклассов. Все вместе эти классы- классы исключений

11.2 Проверка исключений времени компиляции

Во время компиляции язык Ява проверяет то, что Ява- программа содержит обработчики для проверяемых исключений, в результате анализа которых они могут заканчиваться выполнением метода или конструктора. Для каждого проверяемого исключения, которое является возможным результатом, предложения trows метода (§8.4.4) или конструктора (§8.6.4) должны упоминать класс этого исключения или один из суперклассов класса исключения. Эта проверка времени компиляции при наличии обработчиков исключения предназначена для уменьшения числа исключений, которые не обработаны должным образом.

Классы непроверяемых исключений - это класс RuntimeException и его подклассы, а также класс Error и его подклассы. Все оставшиеся классы исключений - классы проверяемых исключений. Стандартный интерфейс прикладного программирования (API) языка Ява определяет количество классов исключений, проверяемых и непроверяемых. Дополнительные классы исключений, проверяемые и непроверяемые, могут быть объявлены самими программистами. Для изучения исключительной ситуации класса иерархии и классов исключений, определенных стандартным интерфейсом прикладного программирования (API) и виртуальной машиной языка Ява cм. §11.5.

Проверяемые классы исключений, названные в throws-предложении - это часть соглашения между разработчиком и пользователем метода или конструктора. Throws- предложение метода замещения не может определить то, что этот метод произойдет в результате генерации любого проверяемого исключения, которое не допускает для генерации метод замещения в соответствии с его throws предложением. Когда интерфейсы включают в себя, более чем одно объявление метода может замещаться простым замещением объявления. В этом случае, замещение объявления должно иметь throws-предложение, которое совместимо со всеми замещенными объявлениями (§9.4).

Инициализаторы-переменные для полей (§8.3.2) и статические инициализаторы(§8.5) не должны заканчиваться проверяемым исключением; иначе происходит ошибка времени компиляции.

11.2.1 Почему ошибки не контролируются

Непроверяемые классы исключений, которые являются классами ошибок (Error и его подклассы), свободны от проверки времени компиляции, потому что они могут происходить во многих местах программы, и возвращение из них трудно или невозможно. Ява-программа, объявляющая такие исключения, была бы бессмысленно хаотична.

11.2.2 Почему не контролируются исключения времени выполнения

Классы исключений времени выполнения (RuntimeException и его подклассы) освобождаются от проверки времени компиляции, потому что, по мнению разработчиков Ява, возможность объявлять такие исключения не помогла бы в установлении правильности Ява-программы. Многие из операций и конструкций языка Ява могут завершаться во время выполнения исключением. Информация, доступная Ява - компилятору, и уровень анализа, который выполняет компилятор, обычно недостаточен для того, чтобы установить, что такие исключения во время выполнения не могут происходить, даже если это может быть очевидно для Ява-программиста. Требование объявлять такие классы исключений вызывало бы только недовольство у Ява-программистов.

Например, некоторый код мог бы реализовывать кольцевую структуру данных, которая по инструкции никогда не может вызывать null-ссылки; тогда программист может быть уверен в том, что NullPointerException не произойдет, но для компилятора было бы сложно доказать это. Теория, которая необходима для создания таких глобальных свойств структур данных, находится вне возможности спецификации языка Ява.

11.3 Обработка исключения

Когда генерируется исключение, управление передается от кода, который вызвал исключение для тесно связанного динамически-окруженного предложения catch оператора try (§14.18), который обрабатывает исключение.

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

Источник вызова оператора или выражения зависит от того, где это происходит:

Обрабатывает ли данное предложение catch данное исключение определяется сравнением класса объекта, который был сгенерирован, и объявленного типа параметра предложения catch. Предложение catch обрабатывает исключение, если тип его параметра - это класс исключения или суперкласс класса исключения. Иначе говоря, предложение catch будет захватывать любой объект исключения, который является instanceof (§15.19.2) объявленного типа параметра.

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

Если предложение catch, обрабатывающее исключение, не может быть найдено, тогда текущий поток (поток, который столкнулся с исключением) заканчивается, но только после выполнения всех finally-предложений и для ThreadGroup вызывается метод uncaughtException (§20.21.31), то есть для родителя текущего потока.

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

Точные правила для преждевременного завершения и для перехвата исключений подробно определяются со спецификацией каждого оператора в §14 и в §15 для выражений (главным образом в §15.5).

11.3.1 Исключения точны

Исключения в языке Ява точны: когда имеет место передача управления, все результаты выполненных операторов и вычисленных выражений, до момента с которого генерируется исключение, остаются в силе. Никакие выражения, операторы, или части того, что происходит после момента с которого исключение генерируется, не могут использоваться в вычислениях. Если оптимизированный код с опережением выполнил некоторые из выражений или операторов, которые следуют за моментом, в котором происходит исключение, то такой код должен быть подготовлен для того, чтобы скрыть это рискованное выполнение в видимом пользователем состоянии Ява-программы.

11.3.2 Обработка асинхронных исключений

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

Асинхронные исключения в языке Ява встречаются очень редко. Они происходят только в результате:

Stop методы могут быть вызваны одним потоком для того, чтобы воздействовать на другой поток или все потоки в указанной группе потоков. Они - асинхронные, потому что могут происходить в любой момент при выполнении другого потока или потоков. InternalError рассматривается асинхронно так, чтобы оно могло обрабатываться используя одинаковый механизм, который вызывает stop метод, как будет описано.

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

Простая реализация могла бы упорядочивать асинхронные исключения в месте каждой инструкции передачи управления. Так как Ява- программа имеет конечный объем, это накладывает пределы на итоговую задержку при обнаружении асинхронного исключения. Так как никакое асинхронное исключение не произойдет между передачами управления, генератор кода имеет некоторую гибкость, чтобы привести в порядок вычисление между передачами управления для лучшего выполнения.

Статья Polling Efficiently в Stock Hardware, автор Mark Feeley, Proc. 1993 Конференция по Функциональному программированию и архитектуре компьютера, Копенгаген, Дания, стр. 179-187, рекомендуется для дальнейшего чтения.

Подобно всем исключениям, асинхронные исключения - точны (§11.3.1).

11.4 Примеры исключений

Рассмотрим следующий пример:

 

class TestException extends Exception {

	TestException() { super(); }


	TestException(String s) { super(s); }

}

class Test {

	public static void main(String[] args) {
		for (int i = 0; i < args.length; i++) {

			try {
				thrower(args[i]);
				System.out.println("Test \"" + args[i] +
					"\" didn't throw an exception");
			} catch (Exception e) {
				System.out.println("Test \"" + args[i] +
					"\" threw a " + e.getClass() +
					"\n        with message: " + e.getMessage());
			}
		}
	}


	static int thrower(String s) throws TestException {
		try {
			if (s.equals("divide")) {
				int i = 0;
				return i/i;
			}
			if (s.equals("null")) {
				s = null;
				return s.length();
			}
			if (s.equals("test"))
				throw new TestException("Test message");
			return 0;
		} finally {
			System.out.println("[thrower(\"" + s +
					"\") done]");
		}
	}
}

Если мы выполняем тестирующую программу, передаются независимые переменные:

 

divide null not test

программа выводит следующее:

[thrower("divide") done]
Test "divide" threw a class java.lang.ArithmeticException
        with message: / by zero
[thrower("null") done]
Test "null" threw a class java.lang.NullPointerException
        with message: null
[thrower("not") done]
Test "not" didn't throw an exception
[thrower("test") done]
Test "test" threw a class TestException
        with message: Test message


Этот пример объявляет класс исключения TestException. Основной метод класса Test вызывает метод thrower четыре раза, генерируя исключения три из четырех раз. Оператор try в основном методе перехватывает каждое исключение для того, чтобы thrower генерировал. Будет ли вызов thrower заканчиваться обычно или преждевременно, печатается сообщение, описывая что произошло.

Объявление метода thrower должно иметь throws-предложение, потому что таким образом могут генерироваться экземпляры класса TestException, который является проверяемым классом исключения (§11.2).Ошибка времени компиляции произошла бы, если бы throws-предложения были опущены.

Заметьте, что finally -предложение выполняется при каждом вызове thrower, вне зависимости происходит или нет исключение, как видно из "[thrower (...) done]" вывод которого происходит при каждом вызове.

11.5 Иерархия исключений

Возможные исключения Ява-программ организованы в иерархии классов, содержащейся в классе Throwable (§ 11.5, § 20.22), непосредственном подклассе Object. Классы Exсeption и Error - непосредственные подклассы класса Throwable. Класс RuntimeException - непосредственный подкласс класса Exсeption.

Классы исключений, объявленные стандартными пакетами java.lang, java.util, java.io и java.net, называются стандартными классами исключений.

Ява-программы могут использовать ранее существующие классы исключений в операторах throw, или определять дополнительные классы исключений, как подклассы класса Throwable или любого из его подклассов. Чтобы воспользоваться преимуществами Ява-системы проверки времени компиляции для обработчиков исключений, обычно определяют большинство классов новых исключений как классы проверяемых исключений и как подклассы класса Exсeption,которые не являются подклассами RuntimeException.

11.5.1 Классы Exception и RuntimeException

Класс Exception - суперкласс всех исключений, обработки которых могут требовать обычные программы.

11.5.1.1 Стандартные исключения времени выполнения

Класс RuntimeException - подкласс класса Exception. Подклассы класса RuntimeException - классы непроверяемых исключений.

Пакет java.lang определяет следующие стандартные непроверяемые исключения времени выполнения, которые, подобно всем другим классам пакета java.lang, неявно импортированы, и поэтому на них можно ссылаться с помощью их простых имен:

 

 

Пакет java.util определяет следующие дополнительные стандартные непроверяемые исключения времени выполнения:

 

11.5.1.2 Стандартные проверяемые исключения

Все стандартные подклассы класса Exception, за исключением класса RuntimeException, являются классами проверяемых исключений.

Пакет java.lang определяет следующие стандартные исключения, которые, как все другие классы пакета java.lang, неявно импортированы, и поэтому на них можно ссылаться с помощью их простых имен:

Пакет java.io определяет следующие дополнительные стандартные исключения:

Стандартный пакет java.net определяет следующие дополнительные подклассы java.io.IOException: и java.net.MalformedURLException: Строка, которая предусматривалась как URL, или как часть URL, имела несоответствующий формат или определяла неизвестный протокол.

11.5.2 Класс Error

Класс Error и его стандартные подклассы - это исключения, при возникновении которых обычные программы, как ожидается, восстановятся. Класс Error является отдельным подклассом Throwable, отличным от класса Exception в иерархии классов, он позволяет программам использоватьследующую запись:

 

} catch (Exception e) {

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

Пакет java.lang определяет все классы ошибок, описанные здесь. Эти классы, как и все друге классы пакета java.lang, неявно импортированы и поэтому на них можно ссылаться с помощью их простых имен.

11.5.2.1 Ошибки загрузки и компоновки

Виртуальная машина языка Ява генерирует объект, который является экземпляром подкласса LinkageError, когда происходит ошибка загрузки, компоновки, подготовки, контроля или инициализации:

11.5.2.2 Ошибки виртуальной машины

Виртуальная машина языка Ява генерирует объект, который является экземпляром подкласса класса VirtualMachineError, когда внутренняя ошибка или ограничение ресурсов не допускает реализации семантики языка Ява. Эта спецификация языка и виртуальной машины языка Ява определяет следующие ошибки виртуальной машины:

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

Мы исследуем усовершенствования Ява-системы для упрощения обработки ситуаций нехватки памяти. Одна возможность состоит в том, что можно было бы автоматически приостановить поток, который столкнулся с ошибкой OutOfMemoryError и позволить обратиться к другому потоку для обработки ошибочной ситуации. Такая техника могла бы также позволить Ява-программе исправить ошибку StackOverflowError, если это переполнение не является результатом не завершения рекурсии. Приветствуются предложения для других подходов.


Содержание | Предыдущая | Следующая