Исключительные ситуации (exceptions) могут возникнуть во время выполнения (runtime) программы, прервав ее обычный ход. К ним относится деление на нуль, отсутствие загружаемого файла, отрицательный или вышедший за верхний предел индекс массива, переполнение выделенной памяти и масса других неприятностей, которые могут случиться в самый неподходящий момент.

Конечно, можно предусмотреть такие ситуации и застраховаться от них как-нибудь так:

if (something == wrong){

// Предпринимаем аварийные действия 

}else{

// Обычный ход действий 

}

Но при этом много времени уходит на проверки, и программа превращается в набор этих проверок. Посмотрите любую штатную производственную программу, написанную на языке С или Pascal, и увидите, что она на 2/3 состоит из таких проверок.

В объектно-ориентированных языках программирования принят другой подход. При возникновении исключительной ситуации исполняющая система создает объект определенного класса, соответствующего возникшей ситуации, содержащий сведения о том, что, где и когда произошло. Этот объект передается на обработку программе, в которой возникло исключение. Если программа не обрабатывает исключение, то объект возвращается обработчику по умолчанию исполняющей системы. Обработчик поступает очень просто: выводит на консоль сообщение о произошедшем исключении и прекращает выполнение программы.

Приведем пример. В программе листинга 16.1 может возникнуть деление на нуль, если запустить ее с аргументом 0. В программе нет никаких средств обработки такой исключительной ситуации. Посмотрите на рис. 16.1, какие сообщения выводит исполняющая система Java.

Листинг 16.1. Программа без обработки исключений

class SimpleExt {

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

int n = Integer.parselnt(args[0]); 

System.out.println("10 / n = " + (10 / n)); 

System.out.println("After all actions"); 

}

Рис. 16.1. Сообщения об исключительных ситуациях

Программа SimpleExt запущена три раза. Первый раз аргумент args[0] равен 5 и программа выводит результат: "ю / n = 2". После этого появляется второе сообщение: "After all actions".

Второй раз аргумент равен о, и вместо результата мы получаем сообщение о том, что в подпроцессе "main" произошло исключение класса ArithmeticException вследствие деления на нуль: "/ by zero". Далее уточняется, что исключение возникло при выполнении метода main класса SimpleExt, а в скобках указано, что действие, в результате которого возникла исключительная ситуация, записано в четвертой строке файла SimpleExt.java. Выполнение программы прекращается, заключительное сообщение не появляется.

Третий раз программа запущена вообще без аргумента. В массиве argsn нет элементов, его длина равна нулю, а мы пытаемся обратиться к элементу args [0]. Возникает исключительная ситуация класса ArrayindexOutofBoundsException вследствие действия, записанного в третьей строке файла SimpleExt.java. Выполнение программы прекращается, обращение к методу printino не происходит.

Блоки перехвата исключения

Мы можем перехватить и обработать исключение в программе. При описании обработки применяется бейсбольная терминология. Говорят, что исполняющая система или программа "выбрасывает" (throws) объект-исключение. Этот объект "пролетает" через всю программу, появившись сначала в том методе, где произошло исключение, а программа в одном или нескольких местах пытается (try) его "перехватить" (catch) и обработать. Обработку можно сделать полностью в одном месте, а можно обработать исключение в одном месте, выбросить снова, перехватить в другом месте и обрабатывать дальше.

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

Для того чтобы попытаться (try) перехватить (catch) объект-исключение, надо весь код программы, в котором может возникнуть исключительная ситуация, охватить оператором try{} catch о {}. Каждый блок catchou перехватывает исключение только одного типа, того, который указан в его аргументе. Но можно написать несколько блоков catch(){} для перехвата нескольких типов исключений.

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

Листинг 16.2. Программа с блоками обработки исключений

class SimpleExtlf

public static void main(String[] args){ 

try{

int n = Integer.parselnt(args[0]);

System.out.println("After parselnt());

System.out.println(" 10 / n = " + (10 / n) ) ;

Systfem. out. println ("After results output"); 

}catch(ArithmeticException ae){

System.out.println("From Arithm.Exc. catch: "+ae); 

}catch(ArraylndexOutOfBoundsException arre){

System.out.println("From Array.Exc.catch: "+arre); 

}finally{

System.out.println("From finally"); 

}

System.out.println("After all actions"); 

}

В программу листинга 16.1 вставлен блок try{} и два блока перехвата catchou для каждого типа исключений. Обработка исключения здесь заключается просто в выводе сообщения и содержимого объекта-исключения, как оно представлено методом tostring() соответствующего класса-исключения.

После блоков перехвата вставлен еще один, необязательный блок finaliy(). Он предназначен для выполнения действий, которые надо выполнить обязательно, чтобы ни случилось. Все, что написано в этом блоке, будет выполнено и при возникновении исключения, и при обычном ходе программы, и даже если выход из блока try{} осуществляется оператором return.

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

Кроме блоков перехвата в листинге 16.2 после каждого действия делается трассировочная печать, чтобы можно было проследить за порядком выполнения программы. Программа запущена три раза: с аргументом 5, с аргументом 0 и вообще без аргумента. Результат показан на рис. 16.2.

Рис. 16.2. Сообщения обработки исключений

После первого запуска, при обычном ходе программы, выводятся все сообщения.

После второго запуска, приводящего к делению на нуль, управление сразу

Же передается В соответствующий блок catch(ArithmeticException ае) {}, потом выполняется то, что написано в блоке finally{}.

После третьего запуска управление после выполнения метода parseinto передается В другой блок catch(ArraylndexOutOfBoundsException arre){}, затем в блок finally{}.

Обратите внимание, что во всех случаях — и при обычном ходе программы, и после этих обработок — выводится сообщение "After all actions". Это свидетельствует о том, что выполнение программы не прекращается при возникновении исключительной ситуации, как это было в программе листинга 16.1, а продолжается после обработки и выполнения блока finally*}.

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

Интересно, что пустой блок catch (){}, в котором между фигурными скобками нет ничего, даже пробела, тоже считается обработкой исключения и приводит к тому, что выполнение программы не прекратится. Именно так мы "обрабатывали" исключения в предыдущих главах.

Немного выше было сказано, что выброшенное исключение "пролетает" через всю программу. Что это означает? Изменим программу листинга 16.2, вынеся деление в отдельный метод f (). Получим листинг 16.3.

Листинг 16.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.parselnt(args[0]); 

System.out.println("After parselnt()); 

f (n);

System.out.println("After results output"); 

}catch(SrithmeticException ae){

System.out.println("From Arithm.Exc. catch: "+ae); 

}catch(ArraylndexQutOfBoundsException arre){

System.out.println("From Array.Exc. catch: "+arre); 

}finally{

System,out.println("From finally"); 

}

System.out.println("After all actions"); 

}

}

Откомпилировав и запустив программу листинга 16.3, убедимся, что вывод программы не изменился, он такой же, как на рис. 16.2. Исключение, возникшее при делении на нуль в методе f (), "пролетело" через этот метод, "вылетело" в метод main (), там перехвачено и обработано.