Рсключительные ситуации (exceptions) РјРѕРіСѓС‚ возникнуть РІРѕ время выполнения (runtime) программы, прервав ее обычный С…РѕРґ. Рљ РЅРёРј относится деление РЅР° нуль, отсутствие загружаемого файла, отрицательный или вышедший Р·Р° верхний предел индекс массива, переполнение выделенной памяти Рё масса РґСЂСѓРіРёС… неприятностей, которые РјРѕРіСѓС‚ случиться РІ самый неподходящий момент.
Конечно, можно предусмотреть такие ситуации и застраховаться от них как-нибудь так:
if (something == wrong){
// Предпринимаем аварийные действия }else{
// Обымный ход действий
}
Но при этом много времени уходит на проверки, и программа превращается в набор этих проверок. Посмотрите любую штатную производственную программу, написанную на языке С или Pascal, и вы увидите, что она на 2/3 состоит из таких проверок.
РљСЂРѕРјРµ того, действия, направленные РЅР° выполнение задачи, смешиваются СЃ действиями РїРѕ обработке исключительных ситуаций. Рто затрудняет отладку программы Рё РїСЂРёРІРѕРґРёС‚ Рє скрытым ошибкам, которые трудно обнаружить Рё устранить.
Р’ объектно-ориентированных языках программирования РїСЂРёРЅСЏС‚ РґСЂСѓРіРѕР№ РїРѕРґС…РѕРґ. РџСЂРё возникновении исключительной ситуации исполняющая система создает объект определенного класса, соответствующего возникшей ситуации. Ртот объект содержит сведения Рѕ том, что, РіРґРµ Рё РєРѕРіРґР° произошло. РћРЅ передается РЅР° обработку программе, РІ которой возникло исключение. Если программа РЅРµ обрабатывает исключение, то объект возвращается обработчику исполняющей системы. Ртот обработчик поступает очень просто: выводит РЅР° консоль сообщение Рѕ произошедшем исключении Рё прекращает выполнение программы.
Приведем пример. В программе листинга 21.1 может возникнуть деление на нуль, если запустить ее с аргументом 0. В программе нет никаких средств обработки такой исключительной ситуации. Посмотрите на рис. 21.1, какие сообщения выводит исполняющая система Java.
Рис. 21.1. Сообщения об исключительных ситуациях |
Листинг 21.1. Программа без обработки исключений
class SimpleExt{
public static void main(String[] args){ int n = Integer.parseInt(args[0]);
System.out.println("10 / n = " + (10 / n));
System.out.println("After all actions");
}
}
Программа SimpleExt запущена три раза. Первый раз аргумент args[0] равен 5 и программа выводит результат: "10 / n = 2". После этого появляется второе сообщение:
"After all actions".
Второй раз аргумент равен 0, и вместо результата мы получаем сообщение о том, что в подпроцессе "main" произошло исключение класса ArithmeticException вследствие деления на нуль: "/ by zero". Далее уточняется, что исключение возникло при выполнении метода main класса SimpleExt, а в скобках указано, что действие, в результате которого возникла исключительная ситуация, записано в четвертой строке файла SimpleExtjava. Выполнение программы на этом прекращается, заключительное сообщение не появляется.
Третий раз программа запущена вообще без аргумента. В массиве args [ ] нет элементов, его длина равна нулю, а мы пытаемся обратиться к элементу args[0]. Возникает исключительная ситуация класса ArrayIndexOutOfBoundsException вследствие действия, записанного в третьей строке файла SimpleExtjava. Выполнение программы прекращается, обращение к методу println() не происходит.
Блоки перехвата исключения
РњС‹ можем перехватить Рё обработать исключение РІ программе. РџСЂРё описании обработки применяется бейсбольная терминология. Говорят, что исполняющая система или программа "выбрасывает" (throws) объект-исключение. Ртот объект "пролетает" через РІСЃСЋ программу, появившись сначала РІ том методе, РіРґРµ произошло исключение. Программа РІ РѕРґРЅРѕРј или нескольких местах пытается (try) его "перехватить" (catch) Рё обработать. Обработку можно сделать полностью РІ РѕРґРЅРѕРј месте, Р° можно частично обработать исключение РІ РѕРґРЅРѕРј месте, выбросить СЃРЅРѕРІР°, перехватить РІ РґСЂСѓРіРѕРј месте Рё обрабатывать дальше.
Мы уже много раз в этой книге сталкивались с необходимостью обрабатывать различные исключительные ситуации, но не делали этого, потому что не хотели отвлекаться от основных конструкций языка. Не вводите это в привычку! Хорошо написанные объектно-ориентированные программы обязательно должны обрабатывать все возникающие в них исключительные ситуации.
Для того чтобы попытаться (try) перехватить (catch) объект-исключение, надо весь код программы, в котором может возникнуть исключительная ситуация, охватить оператором try{} catch () {}. Каждый блок catch(){} перехватывает исключение одного или нескольких типов — они указываются в его параметре. Можно написать несколько блоков catch (){} для перехвата нескольких типов исключений.
Например, мы знаем, что в программе листинга 21.1 могут возникнуть исключения двух типов. Напишем блоки их обработки, как это сделано в листинге 21.2.
Листинг 21.2. Программа с блоками обработки исключений
class SimpleExt1{
public static void main(String[] args){ try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()");
System.out.println(" 10 / n = " + (10 / n));
System.out.println("After results output");
}catch(ArithmeticException ae){
System.out.println("From Arithm.Exc. catch: " + ae);
}catch(ArrayIndexOutOfBoundsException arre){
System.out.println("From Array.Exc. catch: " + arre);
}finally{
System.out.println("From finally");
}
System.out.println("After all actions");
}
}
В программу листинга 21.2 вставлен блок try{} и два блока перехвата catch(){} для каждого типа исключений. Обработка исключения здесь заключается просто в выводе сообщения и содержимого объекта-исключения, как оно представлено методом toString () соответствующего класса-исключения.
После блоков перехвата вставлен еще один, необязательный блок finally{}. Он предназначен для выполнения действий, которые надо выполнить обязательно, что бы ни случилось. Все, что написано в этом блоке, будет выполнено и при возникновении исключения, и при обычном ходе программы, и даже если выход из блока try{} или из блока catch (){} осуществляется оператором return. В последнем случае оператор return выполняется после блока finally{}.
Если в операторе обработки исключений есть блок finally{}, то блок catch() {} может отсутствовать, т. е. можно не перехватывать исключение, но при его возникновении все-таки проделать какие-то обязательные действия.
Кроме блоков перехвата в листинге 21.2 после каждого действия выполняется трассировочная печать, чтобы можно было проследить за порядком выполнения программы. Программа запущена три раза: с аргументом 5, с аргументом 0 и вообще без аргумента. Результат показан на рис. 21.2.
Рис. 21.2. Сообщения обработки исключений |
После первого запуска, при обычном ходе программы, выводятся все сообщения.
После второго запуска, приводящего к делению на нуль, управление сразу же передается в соответствующий блок catch(ArithmeticException ae) {}, потом выполняется то, что написано в блоке finally{}.
После третьего запуска управление после выполнения метода parseInt () передается в другой блок catch(ArrayIndexOutOfBoundsException arre) {}, затем в блок finally{}.
Обратите внимание, что РІРѕ всех случаях — Рё РїСЂРё обычном С…РѕРґРµ программы, Рё после этих обработок — выводится сообщение "After all actions". Рто свидетельствует Рѕ том, что выполнение программы РЅРµ прекращается РїСЂРё возникновении исключительной ситуации, как это было РІ программе листинга 21.1, Р° продолжается после обработки Рё выполнения блока finally{}.
При записи блоков обработки исключений надо совершенно четко представлять себе, как будет передаваться управление во всех случаях. Поэтому изучите внимательно рис. 21.2.
Рнтересно, что пустой блок catch() {}, РІ котором между фигурными скобками нет ничего, даже пробела, тоже считается обработкой исключения Рё РїСЂРёРІРѕРґРёС‚ Рє тому, что выполнение программы РЅРµ прекратится. Рменно так РјС‹ "обрабатывали" исключения РІ предыдущих главах.
Немного ранее было сказано, что выброшенное исключение "пролетает" через РІСЃСЋ программу. Что это означает? Рзменим программу листинга 21.2, вынеся деление РІ отдельный метод f(). Получим листинг 21.3.
Листинг 21.3. Выбрасывание исключения из метода
class SimpleExt2{
private static void f(int n){
System.out.println(" 10 / n = " + (10 / n));
}
public static void main(String[] args){ try{
int n = Integer.parseInt(args[0]);
System.out.println("After parseInt()"); f(n);
System.out.println("After results output");
}catch(ArithmeticException ae){
System.out.println("From Arithm.Exc. catch: " + ae);
}catch(ArrayIndexOutOfBoundsException arre){
System.out.println("From Array.Exc. catch: " + arre);
}finally{
System.out.println("From finally");
}
System.out.println("After all actions");
}
}
Скомпилировав Рё запустив программу листинга 21.3, убедимся, что вывод программы РЅРµ изменился, РѕРЅ такой же, как РЅР° СЂРёСЃ. 21.2. Рсключение, возникшее РїСЂРё делении РЅР° нуль РІ методе f(), "пролетело" через этот метод, "вылетело" РІ метод main(), там перехвачено Рё обработано.
Упражнения
1. Просмотрите внимательно листинги предыдущих глав и подумайте, где в них требуется обработка исключительных ситуаций.
2. Вставьте в листинги предыдущих глав обработку исключительных ситуаций.
Часть заголовка метода throws
То обстоятельство, что метод не обрабатывает возникающее в нем исключение, а выбрасывает (throws) его, следует отмечать в заголовке метода служебным словом throws и указанием класса исключения: