Выбрать главу

Перехват исключений

Чтобы увидеть, как перехватываются ошибки, сначала следует усвоить понятие защищенной секции — той части программы, в которой могут произойти исклю¬чения и за которой следует специальный блок, отвечающий за обработку этих исключений.

Блок try

Если вы «находитесь» внутри метода и инициируете исключение (или это дела¬ет другой вызванный метод), этот метод завершит работу при возникновении исключения. Но если вы не хотите, чтобы оператор throw завершил работу ме¬тода, разместите в методе специальный блок для перехвата исключения — так называемый блок try. Этот блок представляет собой простую область действия, которой предшествует ключевое слово try:

try {

// Фрагмент, способный возбуждать исключения

}

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

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

Конечно, возбужденное исключение в конечном итоге должно быть где-то обра¬ботано. Этим местом является обработчик исключений, который создается для каждого исключения, которое вы хотите перехватить. Обработчики исключе¬ний размещаются прямо за блоком try и обозначаются ключевым словом catch:

try {

// Часть программы, способная возбуждать исключения } catch(Typel idl) {

// Обработка исключения Typel } catch(Туре2 id2) {

// Обработка исключения Туре2 } catch(ТуреЗ id3) {

// Обработка исключения ТуреЗ

}

//ИТ д.

Каждое предложение catch (обработчик исключения) напоминает малень¬кий метод, принимающий один и только один аргумент определенного типа. Идентификатор (idl, id2 и т. д.) может использоваться внутри обработчика точ¬но так же, как и метод распоряжается своими аргументами. Иногда этот иден¬тификатор остается невостребованным, так как тип исключения дает достаточ¬но информации для его обработки, но тем не менее присутствует он всегда.

Обработчики всегда следуют прямо за блоком try. При возникновении ис¬ключения механизм обработки исключений ищет первый из обработчиков ис¬ключений, аргумент которого соответствует текущему типу исключения. После этого он передает управление в блок catch, и таким образом исключение счита¬ется обработанным. После выполнения предложения catch поиск обработчиков исключения прекращается. Выполняется только одна секция catch, соответст¬вующая типу исключения; в этом отношении обработка исключений отличает¬ся от команды switch, где нужно дописывать break после каждого case, чтобы предотвратить исполнение всех прочих case.

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

Прерывание в сравнении с возобновлением

В теории обработки исключений имеется две основные модели. Модель преры¬вания (которое используется в Java и С++) предполагает, что ошибка настолько серьезна, что при возникновении исключения продолжить исполнение невоз¬можно. Кто бы ни возбудил исключение, сам факт его выдачи означает, что ис¬править ситуацию «на месте» невозможно и возвращать управление обратно не нужно.

Альтернативная модель называется возобновлением. Она подразумевает, что обработчик ошибок сделает что-то для исправления ситуации, после чего пред¬принимается попытка повторить неудавшуюся операцию в надежде на успеш¬ный исход. В таком случае исключение больше напоминает вызов метода — чтобы применить модель возобновления в Java, вам придется пойти именно по этому пути (то есть не возбуждать исключение, а вызвать метод, способный решить проблему). Также можно создать блок try внутри цикла while, который станет снова и снова обращаться к этому блоку, пока не будет достигнут нуж¬ный результат.

Исторически сложилось, что программисты, использующие операционные системы с поддержкой возобновления, со временем переходили к модели пре¬рывания, забывая другую модель. Хотя идея возобновления выглядит привле¬кательно, она не настолько полезна на практике. Основная причина кроется в обратной связи: обработчик ошибки часто должен знать, где произошло ис¬ключение и содержать специальный код для каждого отдельного места ошибки. А это усложняет написание и поддержку программ, особенно для больших сис¬тем, где исключения могут быть сгенерированы во многих различных местах.

Создание собственных исключений

Ваш выбор не ограничивается использованием уже существующих в Java ис¬ключений. Иерархия исключений JDK не может предусмотреть все возможные ошибки, поэтому вы вправе создавать собственные типы исключений для обо¬значения специфических ошибок вашей программы.

Для создания собственного класса исключения вам придется определить его производным от уже существующего типа — желательно наиболее близкого к вашей ситуации (хоть это и не всегда возможно). В простейшем случае созда¬ется класс с конструктором по умолчанию:

//• exceptions/InheritingExceptions java

// Создание собственного исключения

class SimpleException extends Exception {}

public class InheritingExceptions {

public void f() throws SimpleException {

System.out.printin("Возбуждаем SimpleException из f()"). throw new SimpleException();

}

public static void main(String[] args) {

Inherit! ngExcepti ons sed = new InheritingExceptionsO;

try {

sed.fO; } catch(SimpleException e) {

System.out.println( Перехвачено!").

}

}

} /* Output

Возбуждаем SimpleException из f() Перехвачено! */// ~

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

В примере результаты работы выводятся на консоль. Впрочем, их также можно направить в стандартный поток ошибок, что достигается использовани¬ем класса System.err. Обычно это правильнее, чем выводить в поток System.out, который может быть перенаправлен. При выводе результатов с помощью System, err пользователь заметит их скорее, чем при выводе в System.out.

Также можно создать класс исключения с конструктором, получающим ар¬гумент String:

//: exceptions/Full Constructors java

class MyException extends Exception { public MyException() {}

public MyException(String msg) { super(msg); }

}

public class Full Constructors {

public static void f() throws MyException {

System.out рппШГВозбуждаем MyException из fO"). throw new MyException();

}

public static void g() throws MyException {

System, out. pri ntl n( "Возбуждаем MyException из g(D; throw new MyException("Создано в g()");

}

public static void main(String[] args) { try {

f();

} catch(MyException e) {

e.printStackTrace(System.err);

}

try {

g();

} catch(MyException e) {

e.pri ntStackTrace(System.err):

}

}

} /* Output:

Возбуждаем MyException из f() продолжение &

MyException

at Ful1 Constructors.f(Ful1 Constructors.java:11) at Full Constructors main(FullConstructors.java-19) Возбуждаем MyException из g() MyException Создано в g()