Опубликован: 04.12.2009 | Доступ: свободный | Студентов: 8416 / 657 | Оценка: 4.30 / 3.87 | Длительность: 27:27:00
Лекция 7:

Важнейшие объектные типы

7.5. Исключительные ситуации

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

При работе программы выполнение операторов обычно идет в рамках "основного ствола" - в случае, когда все идет как надо. Но время от времени возникают исключительные ситуации ( исключения - exceptions), приводящие к ответвлению от основного ствола: деление на 0, отсутствие места на диске или попытка писать на защищенную для записи дискету, ввод с клавиатуры ошибочного символа (например, буквы вместо цифры). В отличие от катастрофических ситуаций (ошибок) такие ситуации в большинстве случаев могут быть учтены в программе, и, в частности, они не должны приводить к аварийному завершению программы.

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

В Java, как и в других современных языках программирования, для таких целей предусмотрено специальное средство — обработка исключительных ситуаций. При этом используется так называемый защищенный блок программного кода try ("попытаться"), после которого следует необязательные блоки перехвата исключений catch, за которыми идет необязательный блок очистки ресурсов finally.

Про наступившую исключительную ситуацию говорят, что она возникает, либо – что она возбуждается. В английском языке для этого используется слово throw – "бросить". Поэтому иногда в переводной литературе используют дословный перевод "бросается исключительная ситуация".

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

try{
 операторы0;
}
catch (ТипИсключения1 переменная1){
 операторы1;
}
catch (ТипИсключения2 переменная2){
 операторы2;
}
catch (ТипИсключенияN переменнаяN){
 операторыN;
}
finally{
 операторы;
}

Отметим, что при задании блоков try-catch-finally после фигурных скобок точку с запятой ";" можно не ставить, как и всегда в случае использования фигурных скобок. Но можно и ставить - по усмотрению программиста.

Если исключительных ситуаций не было, операторы0 в блоке try выполняются в обычном порядке, после чего выполняются операторы в блоке finally. Если же возникла исключительная ситуация в блоке try, выполнение блока прерывается, и идет перехват исключений в блоках catch ("перехватить"). В качестве параметра оператора catch задается ссылочная переменная, имеющая тип той исключительной ситуации, которую должен перехватить данный блок. Чаще всего эту переменную называют e (по первой букве от exception). Если тип исключения совместим с типом, указанном в качестве параметра, выполняется соответствующий оператор. После чего проверок в следующих блоках catch не делается.

После проверок и, возможно, перехвата исключения в блоках catch выполняются операторы блока finally. Его обычно используют для высвобождения ресурсов, и поэтому часто называют блоком "очистки ресурсов". Специальных операторов или зарезервированных конструкций для обработки в блоке finally нет. Отличие кода внутри блока finally от кода, стоящего после оператора try…finally, возникает только при наличии внутри блоков try или catch операторов break, continue, return или System.exit, то есть операторов, прерывающих работу блока программного кода. В этом случае независимо от их срабатывания или несрабатывания сначала происходит выполнение операторов блока finally, и только потом происходит переход в другое место программы в соответствии с оператором прерывания.

Пример обработки исключений:

void myETest(String s,double y){
    double x, z;
    try{
        x=Double.parseDouble(s);
        z=Math.sqrt(x/y);
    } catch(ArithmeticException e){
        System.out.println("Деление на ноль");
    } catch(NumberFormatException e){
        System.out.println("Корень из отрицательного числа!");
    }
};

Иерархия исключительных ситуаций

Исключительные ситуации в Java являются объектами. Их типы являются классами-потомками объектного типа Throwable (от throw able – "способный возбудить исключительную ситуацию"). От Throwable наследуются классы Error ("Ошибка") и Exception ("Исключение"). Экземплярами класса Error являются непроверяемые исключительные ситуации, которые невозможно перехватить в блоках catch. Такие исключительные ситуации представляют катастрофические ошибки, после которых невозможна нормальная работа приложения. Экземплярами класса Exception и его потомков являются проверяемые исключительные ситуации. Кроме одного потомка – класса RuntimeException (и его потомков). Имя этого класса переводится как "Исключительные ситуации времени выполнения".

Классы исключительных ситуаций либо предопределены в стандартных пакетах (существуют исключительные ситуации ArithmeticException для арифметических операций в пакете java.lang, IOException в пакете java.io, и так далее), либо описываются пользователем как потомки класса Exception или его потомков.

В Java типы-исключения принято именовать, оканчивая имя класса на "Exception" ("Исключение") для проверяемых исключений или на "Error" ("Ошибка") для непроверяемых.

По правилу совместимости типов исключительная ситуация типа-потомка всегда может быть обработана как исключение прародительского типа. Поэтому порядок следования блоков catch имеет большое значение: обработчик исключения более общего типа следует писать всегда после обработчика для его типа-потомка, иначе обработчик потомка никогда не будет вызван.

В приведенном выше примере вместо NumberFormatException можно поставить Exception, так как других типов исключений кроме NumberFormatException сюда доходить не может. В этом случае метод выглядит так:

void myETest(String s,double y){
    double x, z;
    try{
        x=Double.parseDouble(s);
        z=Math.sqrt(x/y);
    } catch(ArithmeticException e){
        System.out.println("Деление на ноль");
    } catch(Exception e){
        System.out.println("Корень из отрицательного числа!");
    }
};

Но если бы мы попробовали таким же образом заменить тип исключения в первом блоке catch, то блок для исключений типа Exception всегда перехватывал бы управление, и обработчик для NumberFormatException никогда бы не срабатывал. Пример такого неправильно написанного кода:

void myETest(String s,double y){
    double x, z;
    try{
        x=Double.parseDouble(s);
        z=Math.sqrt(x/y);
    } catch(Exception e){
        System.out.println("Деление на ноль");
    } catch(NumberFormatException e){
        System.out.println("Корень из отрицательного числа!");
    }
};

В таких случаях среда разработки NetBeans выдаст сообщение вида "exception java.lang.NumberFormatException has already been caught" – "исключение java.lang.NumberFormatException уже было перехвачено".

Объявление типа исключительной ситуации и оператор throw

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

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

class WrongPasswordException extends Exception {
   WrongPasswordException(){ // конструктор
     System.out.println("Wrong password!");
   }
}

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

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

throw new ТипИсключения();

Например,

throw new WrongPasswordException();

Если после частичной обработки требуется повторно возбудить исключительную ситуацию e, используется вызов

throw e;

Для проверяемых исключений всегда требуется явное возбуждение. При возбуждении исключения во время выполнения какого-либо метода прерывается основной ход программы, и идет процесс обработки исключения. Его иногда называют "всплыванием" исключения по аналогии со всплыванием пузырька. Если в методе исключение данного типа не перехватывается, исполняется соответствующий блок finally, если он есть, и всплывание продолжается – происходит выход из текущего метода в метод более высокого уровня. Соответственно, исключение начинает обрабатываться на уровне этого метода. Если оно не перехватывается, происходит выход на еще более высокий уровень, и так далее.

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

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

Задание: усовершенствовать класс WrongPasswordException таким образом, чтобы сообщение об ошибке появлялось в виде диалогового окна.

Объявление метода, который может возбуждать исключительную ситуацию. Зарезервированное слово throws

Формат объявления функции, которая может возбуждать проверяемые исключительные ситуации, следующий:

Модификаторы Тип Имя(список параметров)
 throws ТипИсключения1, ТипИсключения2,…, ТипИсключенияN
{
  Тело функции
}

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

Модификаторы ИмяКласса(список параметров)
 throws ТипИсключения1, ТипИсключения2,…, ТипИсключенияN
{
  Тело конструктора
}

Слово throws означает "возбуждает исключительную ситуацию" (дословно – "бросает").

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

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

Как мы уже знаем, в теле метода может быть использован оператор throw, возбуждающий исключительную ситуацию. В этом случае если объект-исключение не перехвачен, в заголовке метода требуется указывать соответствующий тип возбуждаемого исключения. В частности, оператор throw может быть использован для порождения исключения другого типа после обработки перехваченного исключения в блоке catch.

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

Пример: проверка пароля, введенного пользователем.

class CheckPasswordDemo{
  private String password="";
  
  public String getPassword(){
     return password;
  };
  
  public void setPassword(){
     ...//реализация метода
  };
  
  public void checkPassword(String pass)
    throws WrongPasswordException {
     if(!pass.equals(password))
        throw new WrongPasswordException();
  };
}

При вызове метода checkPassword в случае неправильного пароля, переданного в качестве параметра, возбуждается исключительная ситуация. Следует обратить внимание, что сравнение pass!=password всегда будет давать true, так как строки сравниваются как объекты. То есть при сравнении "==" проверяется идентичность адресов в памяти, а не содержание строк.

Еще один момент, на котором следует остановиться: не используйте возбуждение исключительных ситуаций для нормального режима функционирования программы! Не используйте его вместо блока else в операторе if! Возбуждение исключения выполняется намного дольше, потребляет много ресурсов и при неудачном использовании только ухудшает программу. Например, в нашем случае имело бы смысл при неправильном вводе пароля предусмотреть возможность еще двух попыток ввода в обычном режиме – и только после третьей неудачной попытки возбуждать исключение.

Полетаев Дмитрий
Полетаев Дмитрий
Не очень понятно про оболочечные Данные,ячейки памяти могут наверно размер менять,какое это значение те же операции только ячейки больше,по скорости тоже самое
Максим Старостин
Максим Старостин

Код с перемещением фигур не стирает старую фигуру, а просто рисует новую в новом месте. Точку, круг.