Java se сервлеты и jsp паттерны Gof junit Log4j ant


Глава 8 ИСКЛЮЧЕНИЯ И ОШИБКИ



Pdf көрінісі
бет13/22
Дата11.12.2019
өлшемі8,99 Mb.
#53432
1   ...   9   10   11   12   13   14   15   16   ...   22
Байланысты:
JAVA Methods Programming v2.march2015 (2)

Глава 8
ИСКЛЮЧЕНИЯ И ОШИБКИ
Существуют только ошибки.
Аксиома Робертса
Что для одного ошибка, для другого —  
исходные данные.
Следствие Бермана из аксиомы Робертса
Никогда не выявляйте в программе ошибки,  
если не знаете, что с ними делать.
Руководство Штейнбаха
Иерархия исключений и ошибок
Исключительные ситуации (исключения) и ошибки возникают во время вы-
полнения программы, когда появившаяся проблема не может быть решена в те-
кущем  контексте  и  невозможно  продолжение  работы  программы.  Примерами 
«популярных» ошибок являются: попытка индексации вне границ массива, вы-
зов метода на нулевой ссылке или деление на нуль. При возникновении исклю-
чения в приложении создается объект, описывающий это исключение. Затем те-
кущий  ход  выполнения  приложения  останавливается,  и  включается  механизм 
обработки исключений. При этом ссылка на объект-исключение передается об-
работчику исключений, который пытается решить возникшую проблему и про-
должить выполнение программы. Если в классе используется метод, в котором 
может возникнуть проверяемая исключительная ситуация, но не предусмотре-
на ее обработка, то ошибка возникает еще на этапе компиляции. При создании 
такого метода программист обязан включить в код метода обработку исключе-
ний,  которые  могут  генерироваться  в  этом  методе,  или  передать  обработку 
исключения на более высокий уровень методу, вызвавшему данный метод.
Исключение не должно восприниматься как нечто вредное, от которого сле-
дует избавиться любой ценой. Исключение — это источник дополнительной 
информации  о  ходе  выполнения  приложения.  Такая  информация  позволяет 
лучше адаптировать код к конкретным условиям его использования, а также 
на ранней стадии выявить ошибки или защититься от их возникновения в бу-
дущем. В противном случае «подавление» исключений приведет к тому, что 

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
202
о возникшей ошибке никто не узнает или узнает 
на стадии некорректно обработанной информа-
ции.  Поиск  места  возникновения  может  быть 
затруднительным.
Каждой  исключительной  ситуации  постав-
лен в соответствие некоторый класс, экземпляр 
которого  инициируется  при  исключительной 
ситуации. Если подходящего класса не сущест-
вует, то он может быть создан разработчиком. 
Все  исключения  являются  наследниками  су-
перкласса  Throwable  и  его  подклассов  Error 
и Exception из пакета java.lang.
Исключительные ситуации типа Error возникают только во время выполне-
ния программы. Такие исключения связаны с серьезными ошибками, к приме-
ру, с переполнением стека, не подлежат исправлению и не могут обрабатывать-
ся приложением. Некоторые классы из иерархии наследуемых от класса Error 
приведены на рис. 8.2.
Ниже приведена иерархия классов проверяемых исключений, наследуемых 
от  класса  Exception  при  отсутствии  в  цепочке  наследования  класса 
RuntimeException.  Возможность  возникновения  проверяемого  исключения 
может быть отслежена еще на этапе компиляции кода. Компилятор проверяет, 
может ли данный метод генерировать или обрабатывать исключение.
Проверяемые исключения должны быть обработаны в методе, который мо-
жет их генерировать, или включены в throws-список метода для дальнейшей 
обработки в вызывающих методах.
Во  время  выполнения  могут  генерироваться  также  исключения,  которые 
могут быть обработаны без ущерба для выполнения программы. Список этих 
исключений приведен на рис. 8.4. В отличие от проверяемых исключений, класс 
RuntimeException и порожденные от него классы относятся к непрове ряемым 
исключениям. Компилятор не проверяет, может ли генерировать и/или обраба-
тывать метод эти исключения. Исключения типа RuntimeException генериру-
ются при возникновении ошибок во время выполнения приложения.
Рис. 8.1. 
Иерархия основных 
классов исключений
Рис. 8.2. 
Некоторые классы исключений, наследуемые от класса Error

ИСКЛЮЧЕНИЯ И ОШИБКИ
203
Почему  возникла  необходимость  деления  исключений  на  проверяемые 
и непроверяемые? Представим, что следующие ситуации проверяются на этапе 
компиляции, а именно:
•  деление в целочисленных типах вида a/b при b=0 генерирует исключение 
ArithmeticException;
•  индексация массивов. Выход за пределы массива приводит к исключению 
ArrayIndexOfBoundException;
•  вызов метода на ссылке вида obj.toString(), если obj ссылается на null.
Если  бы  возможность  появления  перечисленных  исключений  проверялась 
на этапе компиляции, то любая попытка индексации массива или каждый вызов 
метода требовали бы или блока try-catch, или секции throws. Такой код был бы 
практически непригоден для понимания и поддержки, поэтому часть исключе-
ний была выделена в группу непроверяемых и ответственность за защиту прило-
жения от последствий их возникновения возложена на программиста.
Ниже  приведен  список  часто  встречаемых  в  практике  программирования 
непроверяемых исключений, знание причин возникновения которых необхо-
димо при создании качественного кода.
Рис. 8.3. 
Иерархия классов проверяемых (checked) исключительных ситуаций

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
204
Способы обработки исключений
Если при возникновении исключения в текущем методе обработчик не будет 
обнаружен, то его поиск будет продолжен в методе, вызвавшем данный метод, 
и так далее вплоть до метода main() для консольных приложений или другого 
метода, запускающего соответствующий вид приложения. Если же и там исключе-
ние не будет перехвачено, то JVM выполнит аварийную остановку приложения 
с вызовом метода printStackTrace(), выдающего данные трассировки.
Для проверяемого исключения возможность его генерации отслеживается. 
Передача обработки вызывающему методу осуществляется с помощью опера-
тора throws. В конце концов исключение будет передано в метод main(), где 
и должна находиться крайняя точка обработки. Добавлять оператор throws ме-
тоду main() представляется дурным тоном программирования, как безответст-
венное действие программиста, не обращающего никакого внимания на аль-
тернативное выполнение программы.
На практике используется один из трех способов обработки исключений:
•  перехват и обработка исключения в блоке try-catch метода;
•  объявление исключения в секции throws метода и передача вызывающему 
методу (в первую очередь для проверяемых исключений);
•  использование собственных исключений.
Первый подход можно рассмотреть на следующем примере. При преобразова-
нии содержимого строки к числу в определенных ситуациях может возникать прове-
ряемое исключение типа ParseException. Например:
Исключение
Значение
ArithmeticException
Арифметическая ошибка: деление на нуль и др.
ArrayIndexOutOfBoundsException  Индекс массива находится вне границ
ArrayStoreException
Назначение элементу массива несовместимого типа
ClassCastException
Недопустимое приведение типов
ConcurrentModificationException
Некорректная модификация коллекции
IllegalArgumentException 
При вызове метода использован незаконный аргумент
IllegalMonitorStateException
Незаконная операция монитора на разблокированном экземпляре
IllegalStateException
Среда или приложение находятся в некорректном состоянии
IllegalThreadStateException
Требуемая операция не совместима с текущим состоянием потока
IndexOutOfBoundsException
Некоторый тип индекса находится вне границ
NegativeArraySizeException
Массив создавался с отрицательным размером
NullPointerException
Недопустимое использование нулевой ссылки
NumberFormatException
Недопустимое преобразование строки в числовой формат
StringIndexOutOfBoundsException
Попытка индексации вне границ строки
UnsupportedOperationException
Встретилась неподдерживаемая операция
Рис. 8.4. 
Классы непроверяемых исключений, наследуемых от класса RuntimeException

ИСКЛЮЧЕНИЯ И ОШИБКИ
205
public double
 parseFromFrance(String numberStr) {
 
NumberFormat nfFr = NumberFormat.getInstance(Locale.FRANCE);
 
try
 {
  double numFr = nfFr.parse(numberStr).doubleValue();
  return numFr;
 
catch (ParseException e) { // проверяемое исключение
// 1. генерация стандартного исключения, н-р: IllegalArgumentException() — не очень хорошо
// 2. генерация собственного исключения
// 3. return 0 или другого значения по умолчанию; — нежелательно
 
}
}
Исключительная ситуация возникнет в случае, если переданная строка со-
держит  нечисловые  символы  или  не  является  числом.  Генерируется  объект 
исключения, и управление передается соответствующему блоку catch, в кото-
ром  он  обрабатывается,  иначе  блок  catch  пропускается.  Блок  try  похож 
на обычный логический блок. Блок catch(){} похож на метод, принимающий 
в качестве единственного параметра ссылку на объект-исключение и обраба-
тывающий этот объект.
Второй подход демонстрируется на этом же примере. Метод может генери-
ровать исключения, которые сам не обрабатывает, а передает для обработки дру-
гим методам, вызывающим данный метод. В этом случае метод должен объявить 
о таком поведении с помощью ключевого слова throws, чтобы вызывающий ме-
тод  мог  защитить  себя  от  этих  исключений.  В  вызывающем  методе  должна 
быть предусмотрена или обработка этих исключений, или последующая пере-
дача вызывающему методу.
При этом сам таким образом объявляемый метод может содержать блоки 
try-catch,  а  может  и  не  содержать  их.  Например,  метод  parseFromFrance() 
можно объявить: 
public double
 parseFromFrance(String numberStr) throws ParseException {
 
NumberFormat nfFr = NumberFormat.getInstance(Locale.FRANCE);
 
double
 numFr = nfFr.parse(numberStr).doubleValue();
 
return
 numFr;
}
Ключевое слово throws позволяет разобраться с исключениями методов «чу-
жих» классов, код которых отсутствует. Обрабатывать исключение при этом 
должен будет метод, вызывающий parseFromFrance():
public
 void doAction() {
 
    // same code here
 try
 {
  ��parseFromFrance(numberStr);
 } 
catch
 (ParseException e) {
  // обработка
 }
}

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
206
Создание и применение собственных исключений будет рассмотрено позже 
в этой главе.
Обработка нескольких исключений
Если в блоке try может быть сгенерировано в разных участках кода несколь-
ко типов исключений, то необходимо наличие нескольких блоков catch, если 
только блок catch не обрабатывает все типы исключений.
/* # 1 # обработка двух типов исключений # TwoExceptionAction.java */
package 
by.bsu.exception;
public class
 TwoExceptionAction {
 public
 void doAction() {
  try
 {
   int
 a = (int)(Math.random() * 2);
   System.out.println("a = " + a);
   int c[] = { 1/a }; // опасное место #1
   c[a] = 71; // опасное место #2
  } 
catch
(ArithmeticException e) {
   System.err.println("деление на 0" + e);
  } 
catch
(ArrayIndexOutOfBoundsException e) {
   System.err.println("out of bound: " + e);
  } // окончание try-catch блока 
  System.out.println("after try-catch");
 }
}
Исключение «деление на 0» возникнет при инициализации элемента масси-
ва а=0. В противном случае (при а=1) генерируется исключение «превышение 
границ массива» при попытке присвоить значение второму элементу массива 
с[],  который  содержит  только  один  элемент.  Однако  пример,  приведенный 
выше, носит чисто демонстративный характер и не является образцом хоро-
шего кода, так как в этой ситуации можно было обойтись простой проверкой 
аргументов на допустиые значения перед выполнением операций. К тому же 
генерация и обработка исключения — операция значительно более ресурсо-
емкая, чем вызов оператора if для проверки аргумента. Исключения должны 
применяться только для обработки исключительных ситуаций, и если суще-
ствует возможность обойтись без них, то следует так и поступить.
Подклассы  исключений  в  блоках  catch  должны  следовать  перед  любым 
из их суперклассов, иначе суперкласс будет перехватывать эти исключения.
Например:
try
 { /* код, который может вызвать исключение */
catch(IllegalArgumentException e) {
catch(PatternSyntaxException e) { } /* никогда не может быть вызван: ошибка компиляции */

ИСКЛЮЧЕНИЯ И ОШИБКИ
207
где  класс  PatternSyntaxException  представляет  собой  подкласс  класса 
IllegalArgumentException. Корректно будет просто поменять местами блоки 
catch:
try
 { /* код, который может вызвать исключение */
catch(PatternSyntaxException e) { 
catch(IllegalArgumentException e) {
}
На практике иногда возникают ситуации, когда инструкций catch несколько 
и обработка производится идентичная, например, вывод сообщения об исклю-
чении в журнал. 
try
 {
 
// some operations
catch(NumberFormatException e) {
 
 e.printStackTrace();
catch(ClassNotFoundException e) {
 
 e.printStackTrace();
catch(InstantiationException e) {
 
 e.printStackTrace();
}
В  версии  Java  7  появилась  возможность  объединить  все  идентичные  ин-
струкции в одну, используя для разделения оператор «|».
try
 {
 
// some operations
catch(NumberFormatException | ClassNotFoundException | InstantiationException e) {
 
 e.printStackTrace();
}
Такая запись позволяет избавиться от дублирования кода.
Введено  понятие  более  точной  переброски  исключений  (more  precise  re-
throw). Это решение применимо в случае, если обработка возникающих исклю-
чений не предусматривается в методе и должна быть передана вызывающему 
данный метод методу.
До введения этого понятия код выглядел так:
public
 double parseFromFileBefore(String filename)
   throws FileNotFoundException, ParseException, IOException {
 
 
NumberFormat nfFr = NumberFormat.getInstance(Locale.FRANCE);
  double numFr = 0;
 
 
BufferedReader buff = null;
  try {
   FileReader 
fr 

new
 FileReader(filename);
   buff 

new
 BufferedReader(fr);
 
 
 
String number = buff.readLine();
   numFr 

nfFr.parse(number).doubleValue();
  } 
catch
 (FileNotFoundException e) {
   

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
208
throw
 e;

catch
 (IOException e) {
throw
 e;

catch
 (ParseException e) {
throw
 e;

finally
 {
if
(buff != null) {
buff.close();
}
}
return
 numFr;
}
More precise rethrow разрешает записать в единственную инструкцию catch 
более  общее  исключение,  чем  может  быть  генерировано  в  инструкции  try
с последующей генерацией перехваченного исключения для его передачи в вы-
зывающий метод. 
public
 double parseFromFile(String filename) 
throws
 FileNotFoundException, ParseException, IOException {
NumberFormat nfFr = NumberFormat.getInstance(Locale.FRANCE);
double
 numFr = 0;
BufferedReader buff = null;
try
 {
FileReader 
fr 

new
 FileReader(filename);
buff 

new
 BufferedReader(fr);
String number = buff.readLine();
numFr 

nfFr.parse(number).doubleValue();

catch
 (final Exception e) { // final — необязателен
throw
 e; // more precise rethrow

finally
 {
if
(buff != null) {
buff.close();
}
}
return
 numFr;
}
Наличие  секции  throws  контролируется  компилятором  на  предмет  точного 
указания списка проверяемых исключений, которые могут быть генерированы 
в блоке try-catch. При возможности возникновения непроверяемых исключе-
ний последние в секции throws обычно не указываются. Ключевое слово final 
не позволяет подменить экземпляр исключения для передачи за пределы мето-
да. Однако данную конструкцию можно использовать и без final.
Операторы try можно вкладывать друг в друга. Если у оператора try низко-
го  уровня  нет  раздела  catch,  соответствующего  возникшему  исключению, 

ИСКЛЮЧЕНИЯ И ОШИБКИ
209
поиск будет развернут на одну ступень выше, и будут проверены разделы catch 
внешнего оператора try.
/* # 2 # вложенные блоки try-catch # NestedTryCatchRunner.java */
package 
by.bsu.exception;
public class
 NestedTryCatchRunner {
public
 void doAction() {
try
 { // внешний блок
    int
 a = (int) (Math.random() * 2) — 1;
    System.out.println("a = " + a);
    try
 { // внутренний блок
int
 b = 1/a;
StringBuilder 
sb 

new
 StringBuilder(a);
    } catch (NegativeArraySizeException e) {
System.err.println("недопустимый размер буфера: " + e);
    }

catch
 (ArithmeticException e) {
    System.err.println("деление на 0: " + e);
     }
}
}
В результате запуска приложения при a=0 будет сгенерировано исключение 
ArithmeticException, а подходящий для его обработки блок try–catch является 
внешним по отношению к месту генерации исключения. Этот блок и будет за-
действован для обработки возникшей исключительной ситуации. Вкладывание 
блоков try-catch друг в друга загромождает код, поэтому такими конструкция-
ми следует пользоваться с осторожностью.
Оператор throw
При разработке кода возникают ситуации, когда в приложении необходимо 
инициировать  генерацию  исключения  для  указания,  напри мер,  на    заведомо 
ошибочный результат выполнения операции, на некорректные значения пара-
метра метода и др. Для генерации исключительной ситуации и создания экзем-
пляра исключения используется оператор throw. В качестве исключения дол-
жен  быть  использован  объект  подкласса  класса  Throwable,  а  также  ссылки 
на них. Общая форма записи инструкции throw, генерирующей исключение:
throw
 объектThrowable;
Объект-исключение может уже существовать или создаваться с помощью 
оператора new:
throw new 
IIlegalArgumentException();
При достижении оператора throw выполнение кода прекращается. Ближайший 
блок try проверяется на наличие соответствующего обработчика catch. Если 

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
210
он  существует,  управление  передается  ему,  иначе  проверяется  следующий 
из вложенных операторов try. Инициализация объекта-исключения без опера-
тора throw никакой исключительной ситуации не вызовет.
В  ситуации,  когда  получение  методом  достоверной  информации  критично 
для выполнения им своей функциональности, у программиста может возникнуть 
необходимость в генерации исключения, так как метод не может выполнить ожи-
даемых от него действий, основываясь на некорректных или ошибочных данных. 
Ниже приведен пример, в котором сначала создается объект-исключение, 
затем оператор throw генерирует исключение, обрабатываемое в разделе catch
в котором генерируется другое исключение. 
/* # 3 # генерация исключений # Connector.java # Runner.java # SameResource.java */
package
 by.bsu.conn;
public
 class Connector {
    public static
 void loadResource(SameResource f) {
if
 (f == null || !f.exists() || !f.isCreate()) {
throw
 new IllegalArgumentException(); /* генерация исключения */
   // или собственное, н-р, throw new IllegalResourceException();
  
}
 
// more code
 }
}
package
 by.bsu.conn;
public class
 Runner {
    public
 static void main(String[ ] args) {
 SameResource f = new SameResource(); // SameResource f = null;
try
 {// необязателен только при гарантированной корректности значения параметра
    Connector.loadResource(f);

catch
(IllegalArgumentException e) {
    System.err.print("обработка unchecked-исключения вне метода: " + e);
}
}
package
 by.bsu.conn;
public
 class SameResource {
  // поля, конструкторы
  public boolean isCreate() {
// more code
  } 
  public boolean exists() {
// more code
  }
  public
 void execute() {
// more code
  }
  public void close() {
// more code
}
}

ИСКЛЮЧЕНИЯ И ОШИБКИ
211
Вызываемый метод loadResource() может (при отсутствии требуемого ресурса 
или при аргументе null) генерировать исключение, перехватываемое обработчиком. 
В результате экземпляр непроверяемого исключения IllegalArgumentException как 
подкласса  класса  RuntimeException  передается  обработчику  исключений 
в методе main()
В случае генерации проверяемого исключения IllegalResourceException ком-
пилятор требует обра ботки объекта исключения в методе или передачи его с по-
мощью инструкции throws. Проверяемое исключение может быть создано как
public class
 IllegalResourceException extends Exception {}
Тогда методы loadResource() и main() будут выглядеть так:
public static
 void loadResource(SameResource f) throws IllegalResourceException {
if
 (f == null || !f.exists() || !f.isCreate()) {
throw
 new IllegalResourceException();
}
// more code
}
В этом случае в методе main() блок try-catch будет обязателен.
Если метод генерирует исключение с помощью оператора throw и при этом 
блок catch в методе отсутствует, то для передачи обработки исключения вызыва-
ющему методу тип проверяемого (checked) класса исключений должен быть ука-
зан в операторе throws при объявлении метода. Для исключений, являющихся 
подклассами класса RuntimeException (unchecked) и используемых для отобра-
жения программных ошибок, при выполнении приложения throws в объявлении 
может отсутствовать, так как играет только информационную роль.
Блок finally
Возможна ситуация, при которой нужно выполнить некоторые действия 
по завершению программы (закрыть поток, освободить соединение с базой 
данных)  вне  зависимости  от  того,  произошло  исключение  или  нет.  В  этом 
случае используется блок finally, который обязательно выполняется после ин-
струкций try или catch. Например:
try
 { /* код, который может вызвать исключение */ 
catch(OneClassException e) { /* обработка исключения */  // необязателен
catch(TwoClassException e) { /* обработка исключения */  // необязателен
finally { /* выполняется или после try, или после catch */ }
Каждому разделу try должен соответствовать по крайней мере один раздел 
catch или блок finally. Блок finally часто используется для закрытия файлов 
и освобождения других ресурсов, захваченных для временного использования 
в начале выполнения метода. Код блока выполняется перед выходом из метода 

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
212
даже в том случае, если перед ним были выполнены инструкции вида return
breakcontinue.
/* # 4 # выполнение блоков finally # ResourceAction.java */
package
 by.bsu.conn;
public class
 ResourceAction {
 
public void
 doAction() {
 
 
SameResource sr = null;
  try
 {
 
 
    // реализация — захват ресурсов
 
 
    sr = new SameResource(); // возможна генерация исключения
 
 
    // реализация — использование ресурсов
 
 
    sr.execute(); // возможна генерация исключения
 
 
    // sr.close(); // освобождение ресурсов (некорректно)
  } 
finally
 {
 
 
    // освобождение ресурсов (корректно)
 
 
    if (sr != null) { 
   sr.close();
 
 
    }
  }
  System.out.print("after finally");
 }

В методе doAction() при использовании ресурсов и генерации исключе-
ния осуществляется преждевременный выход из блока try с игнорировани-
ем всего оставшегося в нем кода, но до выхода из метода обязательно будет 
выполнен раздел finally. Освобождение ресурсов в этом случае произойдет 
корректно.  В  следующей  главе  будет  рассмотрен  новый  способ  закрытия 
ресурсов autocloseable.
Собственные исключения
Для повышения качества и скорости восприятия кода разработчик мо-
жет создать собственное исключение как подкласс класса Exception и затем 
использовать его при обработке ситуации, не являющейся исключением с точ-
ки  зрения  языка,  но  нарушающей  логику  вещей.  По  соглашению  наcледник 
любого класса-исключения должен заканчиваться словом Exception
Например, возможность появления объекта типа Coin с отрицательным 
значением поля diameter является предлогом для генерации собственного 
логического исключения CoinLogicException, хотя для языка Java появле-
ние у объекта поля с отрицательным значением исключением не является 
и впоследствии к возникновению других исключений само по себе привес-
ти не может.

ИСКЛЮЧЕНИЯ И ОШИБКИ
213
/* # 5 # метод, вызывающий исключение, созданное программистом # Coin.java */
package 
by.bsu.fund.entity;
import
 by.bsu.fund.exceptions.CoinLogicException;
public
 class Coin {
 
private
 double diameter;
 
private
 double weight;
 
public
 double getDiameter() {
  return diameter;
 }
 
public
 void setDiameter(double value) throws CoinLogicException {
  if(value <= 0) {
   throw new CoinLogicException("diameter is incorrect");
  }
 
 
diameter = value;
 }
 
public
 double getWeight() {
  return weight;
 }
 
public
 void setWeight(double value) {
 
 
weight = value;
 }
}
При  невозможности  присвоить  значение  генерируется  экземпляр 
CoinLogicException, используемый в качестве собственного исключения. 
/* # 6 # собственное «логическое» исключение # CoinLogicException.java */
package 
by.bsu.fund.exceptions;
public
 class CoinLogicException extends Exception {
 
public
 CoinLogicException() {
 }
 
public
 CoinLogicException(String message, Throwable exception) {
  super(message, exception);
 }
 
public
 CoinLogicException(String message) {
  super(message);
 }
 
public
 CoinLogicException (Throwable exception) {
  super(exception);
 }
}
Если же генерируется стандартное исключение или получены значения неко-
торых параметров — такие, что генерация какого-либо стандартного исключе-
ния становится неизбежной немедленно либо сразу по выходе из метода, то сле-
дует генерировать собственное техническое исключение CoinTechnicalException
Продемонстрировать процесс генерации и обработки логического и техни-
ческого собственных исключений можно на примере.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
214
/* # 7 # генерация и обработка собственных исключений */
public
 void doAction(String value) throws CoinTechnicalException {
 
 
Coin ob = new Coin();
  try {
   double d = Double.parseDouble(value);
   ob.setDiameter(d);
  } 
catch
 (NumberFormatException e) {
   throw new CoinTechnicalException("incorrect symbol in string", e);
  } 
catch
 (CoinLogicException e) {
   System.err.println(e.getCause());
  }
}
У  класса-наследника  Exception  обычно  определяются  четыре  конструктора, 
два из которых в качестве параметра принимают объект типа Throwable, что озна-
чает генерацию исключения на основе другого исключения. Такая ситуация в при-
веденном случае возможна, например, при предварительном определении диаме-
тра  монеты,  в  процессе  которого  произошла  ошибка  преобразования  строки 
в  базовый  тип,  спровоцировавшая  возникновение  NumberFormatException
Значение  диаметра  монеты  присвоить  все  равно  невозможно,  поэтому  есть 
смысл для более точной передачи причин некорректной работы приложения 
сгенерировать CoinTechnicalException, но с вызовом конструктора, обладаю-
щего параметром типа Throwable.
/* # 8 # собственное «техническое» исключение # CoinTechnicalException.java */
package 
by.bsu.fund.exceptions;
public
 class CoinTechnicalException extends Exception {
 
public
 CoinTechnicalException() {
 }
 
public
 CoinTechnicalException(String message, Throwable cause) {
  super(message, cause);
 }
 
public
 CoinTechnicalException(String message) {
  super(message);
 }
 
public
 CoinTechnicalException(Throwable cause) {
  super(cause);
 }
}
 Один из них — сообщение, которое может быть выведено в поток ошибок; 
другой  —  реальное  исключение,  которое  привело  к  вызову  технического 
исключения. Этот код показывает, как можно сохранить дополнительную ин-
формацию внутри пользовательского исключения. Преимущество этого сохра-
нения состоит в том, что если вызываемый метод захочет узнать реальную при-
чину  вызова  CoinTechnicalException,  он  всего  лишь  должен  вызвать  метод 

ИСКЛЮЧЕНИЯ И ОШИБКИ
215
getCause(). Это позволяет вызываемому методу решить, нужно ли работать со 
специфичным исключением или достаточно обработки CoinTechnicalException
Разработчики программного обеспечения стремятся к высокому уровню 
повторного  использования  кода,  поэтому  они  постарались  предусмотреть 
и  закодировать  все  возможные  исключительные  ситуации.  При  реальном 
программировании  создание  собственных  классов  исключений  позволяет 
разработчику  выделить  важные  аспекты  приложения  и  обратить  внимание 
на детали разработки.
Приведенные выше классы собственных исключений могут иметь общий 
суперкласс, например: CoinException, что позволит всегда определить, являет-
ся ли перехваченное исключение собственным или стандартным. Тогда преды-
дущий пример можно переписать в виде:
/* # 9 # генерация и переброска собственных исключений # */
public
 void doAction(String value) throws CoinLogicException  {
 
 
Coin ob = new Coin();
  try {
   double d = Double.parseDouble(value);
   ob.setDiameter(d);
  } 
catch
 (CoinException e) {
   throw e;
  }
}
Наследование и исключения
Создание сложных распределенных систем редко обходится без наследова-
ния  и  обработки  исключений.  Следует  знать  два  правила  для  проверяемых 
исключений при наследовании:
•  переопределяемый  метод  в  подклассе  не  может  содержать  в  инструк ции 
throws исключений, не обрабатываемых в соответствующем методе супер-
класса;
•  конструктор  подкласса  должен  включить  в  свой  блок  throws  все  классы 
исключений или их суперклассы из блока throws конструк тора суперкласса, 
к которому он обращается при создании объекта.
Первое правило имеет непосредственное отношение к расширяемости при-
ложения. Пусть при добавлении в цепочку наследования нового класса его по-
лиморфный метод включил в блок throws «новое» проверяемое исключение. 
Тогда методы логики приложения, принимающие объект нового класса в каче-
стве параметра и вызывающие данный полиморфный метод, не готовы обраба-
тывать  «новое»  исключение,  так  как  ранее  в  этом  не  было  необходимости. 
Поэтому при попытке добавления «нового» checked-исключения в полиморф-
ный метод компилятор выдает сообщение об ошибке.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
216
/* # 10 # полиморфизм и исключения # Stone.java # WhiteStone.java # BlackStone.java # 
StoneAction.java */
package 
by.bsu.polymorph;
public
 class Stone { // ранее созданный класс
 public
 void build(String data) throws ParseException {
  /* реализация */ 
 }
}
package 
by.bsu.polymorph;
public
 class WhiteStone extends Stone { // ранее созданный класс
 @Override
 public
 void build(String data) {
  /* 
реализация */ 
  System.out.println("белый каменный шар");
 }
}
package 
by.bsu.polymorph;
public
 class StoneAction { // ранее созданный класс
 public
 void buildHouse(Stone stone) {
  try 
{
   stone.build("some 
info"); 
 
 
// предусмотрена обработка ParseException и его подклассов
  } 
catch
(ParseException e) {
   System.err.print(e);
  }
 
}
}
package 
by.bsu.polymorph;
public
 class BlackStone extends Stone { // новый класс
 @Override
 public
 void build(String data) throws Exception { // ошибка компиляции
  System.out.println("черный каменный шар");
 
 
/* реализация*/
 }
}
Если же при объявлении метода суперкласса инструкция throws присутст-
вует,  то  в  подклассе  эта  инструкция  может  вообще  отсутствовать  или  в  ней 
могут быть объявлены любые исключения, являющееся подклассами исключе-
ния из блока throws метода суперкласса.
Второе правило позволяет защитить программиста от возникновения неиз-
вестных ему исключений при создании объекта.
/* # 11 # конструкторы и исключения # Resource.java # ConcreteResource.java */
package 
by.bsu.construction;
import
 java.io.FileNotFoundException;
import
 java.io.IOException;

ИСКЛЮЧЕНИЯ И ОШИБКИ
217
class
 Resource { // ранее созданный класс
public
 Resource(String filename) throws FileNotFoundException {
// more code
}
}
class
 ConcreteResource extends Resource { // ранее созданный класс
// 
ранее созданный конструктор
public
 ConcreteResource(String name) throws FileNotFoundException {
super
(name);
// 
more code
}
// 
ранее созданный конструктор
public
 ConcreteResource() throws IOException {
super
("file.txt");
// more code
}
// 
новый конструктор
public
 ConcreteResource(String name, int mode) { /* ошибка компиляции */
super
(name);
// more code
}
public
 ConcreteResource(String name, int mode, String type) throws ParseException {
/* ошибка компиляции */
super
(name);
// more code
}
}
Если разрешить создание экземпляра в виде
ConcreteResource inCorrect = new ConcreteResource("info", 1);
то конструктор суперкласса может генерировать исключение и никаких пред-
варительных действий по его предотвращению принято не будет.
try
 {
ConcreteResource correct = new ConcreteResource();
catch (IOException e) { 
// обработка 
}
В приведенном выше случае компилятор не разрешит создать конструктор 
подкласса, обращающийся к конструктору суперкласса без корректной инструк-
ции throws. Если бы это было возможно, то при создании объекта подкласса 
класса ConcreteResource не было бы никаких сообщений о возможности гене-
рации исключения, и при возникновении исключительной ситуации ее источ-
ник было бы трудно идентифицировать.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
218
Рекомендации по обработке исключений
В любом случае, если есть возможность не генерировать исключение, сле-
дует  ею  воспользоваться.  Генерация  исключения  —  процесс  ресурсоемкий, 
и слишком частая генерация исключений оказывает влияние на быстродействие.
• Не обрабатывать конкретное исключение или несколько исключений с ис-
пользованием в инструкции catch исключения более общего типа. 
try
 { 
// some code here
 int
 a = Integer.parseInt(args[0]);
StringBuilder sb = new StringBuilder(a);
// some code here
catch (Exception e) {
 System.err.println(e);

Следует классифицировать исключения. Вместо этого следует использовать:
try
 { 
 int
 a = Integer.parseInt(args[0]);
StringBuilder sb = new StringBuilder(a);
catch (NegativeArraySizeException e) {
 System.err.println("недопустимый размер буфера: " + e);
catch (NumberFormatException e) {
 System.err.println("недопустимый символ в числе: " + e);
}
• Не оставлять пустыми блоки catch. При генерации и перехвате исключения
никто  не  узнает,  что  исключительная  ситуация  имела  место,  и  не  станет
устранять ее причины
try 
{
// some code here
catch (NumberFormatException e) {
}
• По возможности не использовать одинаковую обработку различных исклю-
чений
try
 {
// some code here
catch (IOException e) {
e.printStackTrace();
catch (SAXException e) {
e.printStackTrace();
}
Для замены можно воспользоваться конструкцией multi-catch из Java 7 или
в каждую инструкцию catch помещать уникальную обработку.
•  Не создавать класс исключений, эквивалентный по смыслу уже существующему.
Прежде  чем  написать  свое  исключение,  необходимо  изучить  документацию,

ИСКЛЮЧЕНИЯ И ОШИБКИ
219
возможно, там найдется что-то подходящее. Например, вместо того, чтобы 
создавать исключение для информирования о некорректной работе с пере-
числением вида
public
 class EnumNotPresentException extends Exception { }
  следует применить класс EnumConstantNotPresentException.
•  Не создавать избыточное число классов собственных исключений. Прежде 
чем создавать новый класс исключений, следует подумать, что, возможно, 
ранее созданный в состоянии его обработать.
•  Не использовать исключения, которые могут ввести в заблуждение:
public
 class Human {
 private
 int year;
 public
 void setYear(int year) throws IOException {
  
if
 (year <= 0) {
 
  throw
 new IOException();
  
}
 
  this
.year = year;
 }
}
•  Не допускать, чтобы часть обработки ошибки присутствовала в блоке, гене-
рирующем исключение:
public
 void setDeduce(double deduce) throws TaxException {
 if
 (deduce < 0) {
  
this
.deduce = 0; // лишнее
  
recalculateAmount(); 
// совсем лишнее
  
System.err.print(DEDUCE_NEGATIVE);
  
throw
 new TaxException("VAT deduce < 0");
 }
  
this
.deduce = deduce;
  
recalculateAmount();
}
•  Никогда самостоятельно не генерировать NullPointerException и избегать 
случаев, когда такая генерация возможна в принципе. Проверка значения 
ссылки на null позволяет обойтись без генерации исключения. Если по ло-
гике приложения необходимо генерировать исключение, следует использо-
вать, например, IllegalArgumentException с соответствующей информаци-
ей об ошибке или собственное исключение.
•  Не следует в общем случае в секцию throws помещать unchecked-исключения.
•  Не рекомендуется вкладывать блоки try-catch друг в друга из-за ухудшения 
читаемости кода.
•  При  создании  собственных  исключений  следует  проводить  наследование 
от класса Exception, либо от другого проверяемого класса исключений, а не 
от RuntimeException.
•  Никогда не генерировать исключения в инструкции finally:

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
220
try
 {
 
// some code here
finally {
 
if
 (условие) { 
  
throw new
 ParseException();
 }
}
  При такой генерации исключения никто в приложении не узнает об исклю-
чении и, соответственно, не сможет обработать исключение, ранее сгенери-
рованное в инструкции try, в случае, если оно не было обработано в инструк-
ции catch. В связи со сказанным никогда не следует использовать в инструкции 
finally операторы returnbreakcontinue.
Отладочный механизм assertion
Борьба за качество программ ведется всеми возможными способами. На этапе 
отладки найти неявные ошибки в функционировании приложения бывает доволь но 
сложно. Например, в методе, устанавливающем возраст пользователя, информация 
о возрасте извлекается из внешних источников (файл, БД), и в результате получает-
ся отрицательное значение. Далее неверные данные влияют на результат вычисле-
ния среднего возраста пользователей и т. д. Определять и исправ лять такие ситуа-
ции позволяет механизм проверочных утверждений (assertion). При помощи этого 
механизма можно сформулировать требования к входным, выходным и промежу-
точным данным методов классов в виде некоторых логических условий. 
Попытка обработать ситуацию появления отрицательного возраста может 
выглядеть следующим образом:
int
 age = ob.getAge();
if
 (age >= 0) {
   // more code
else {
   // сообщение о неправильных данных
}
Теперь механизм assertion позволяет создать код, который будет генериро-
вать исключение на этапе отладки проверки постусловия или промежуточных 
данных в виде:
int
 age = ob.getAge();
assert
 (age >= 0): "NEGATIVE AGE!!!";
   // more code
Правописание инструкции assert:
assert
 boolexp : expression;
assert
 boolexp;

ИСКЛЮЧЕНИЯ И ОШИБКИ
221
Выражение boolexp может принимать только значение типов boolean или 
Boolean, а expression — любое значение, которое может быть преобра зовано 
к строке. Если логическое выражение получает значение false, то гене рируется 
исключение AssertionError и выполнение программы прекращается с выво-
дом на консоль значения выражения expression (если оно задано).
Механизм assertion хорошо подходит для проверки инвариантов, например, 
перечислений:
enum
 Mono { WHITE, BLACK }
String str = "WHITE"; // "GRAY"
Mono mono = Mono.valueOf(str);
// more code
switch
 (mono) {
 
case
 WHITE : // more code
  break;
 
case
 BLACK : // more code
  break;
 
default
 :
  assert false : "Colored!";
}
Создатели языка не рекомендуют использовать assertion при проверке парамет-
ров public-методов. В таких ситуациях лучше обрабатывать возможность генера-
ции исключения одного из типов: IllegalArgumentExceptionNullPointerException 
или собственное исключение. Нет также особого смысла в механизме assertion 
при проверке пограничных значений переменных, поскольку исключительные 
ситуации генерируются в этом случае без посторонней помощи.
Механизм assertion можно включать для отдельных классов и пакетов при 
запуске виртуальной машины в виде:
java -enableassertions RunnerClass 
или
java -ea RunnerClass
Для выключения применяется -da или -disableassertions.
Задания к главе 8
Вариант A
Выполнить задания на основе варианта А гл. 4, контролируя состояние пото-
ков ввода/вывода. При возникновении ошибок, связанных с корректностью вы-
полнения  математических  операций,  генерировать  и  обрабатывать  ис клю-
читель ные ситуации. Предусмотреть обработку исключений, возникающих при 
нехватке памяти, отсутствии требуемой записи (объекта) в файле, недопусти-
мом значении поля и т. д. 

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
222
Вариант B
Выполнить задания из варианта В гл. 4, реализуя собственные обработчики 
исключений и исключения ввода/вывода.
Тестовые задания к главе 8
Вопрос 8.1.
Выберите правильные утверждения (3):
1)  Проверяемые  (checked)  исключения  являются  наследниками  класса  
java.lang.Exception
2)  Непроверяемые  (unchecked)  исключения  являются  наследниками  класса 
java.lang.Error
3)  Непроверяемые  (unchecked)  исключения  являются  наследниками  класса 
java.lang.Exception
4)  Проверяемые (checked) исключения обязательно обрабатываются
5)  Непроверяемые (unchecked) исключения невозможно обработать
Вопрос 8.2.
Дан код:
try {  FileReader fr1 = new FileReader("test1.txt");
try {  FileReader fr2 = new FileReader("test2.txt");
 
catch (IOException e) {
 
 
System.out.print("test2");
 
}
 
System.out.print("+");
catch (FileNotFoundException e) {
 
System.out.print("test1");
}
System.out.print("+");
Какая строка выведется на консоль при компиляции и запуске этого кода, 
если файл test1.txt существует и доступен, а test2.txt нет (1)?
1)  test1
2)  test1+
3)  test1++
4)  test2
5)  test2+
6)  test2++
7)  ошибка компиляции

ИСКЛЮЧЕНИЯ И ОШИБКИ
223
Вопрос 8.3.
Дана иерархия исключений:
class A extends java.lang.Exception{}
class B extends A{}
class C extends B{}
class D extends A{}
class E extends A{}
class F extends D{}
class G extends D{}
class H extends E{}
Выберите  цепочки  блоков  catch,  использование  которых  не  приведет 
к ошибке компиляции, если в соответствующем блоке try могут генерировать-
ся исключения типа C,D,G,H (3):
1)  catch(C e){}catch(D e){}  catch(H e){}catch(A e){}
2)  catch(C e){}catch(D e){}catch(E e){}catch(A e){}
3)  catch(C e){}catch(D e){}catch(G e){}catch(A e){}
4)  catch(A e){}catch(D e){}catch(G e){}catch(H e){}
5)  catch(E e){}catch(D e){}catch(B e){}catch(A e){}
Вопрос 8.4.
Дан код:
class A{
 
public void f() throws IOException{}
}
class B extends A{}
Каким образом можно переопределить метод f() в классе B, не вызвав при 
этом ошибку компиляции (4)?
1)  public void f() throws Exception {}
2)  public void f() throws IOException {} 
3)  public void f() throws InterruptedException, IOException {}
4)  public void f() throws IOException, FileNotFoundException {}
5)  public void f() throws FileNotFoundException {}
6)  public void f() throws FileNotFoundException, InternalError {}

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
Вопрос 8.5.
Дан код:
public class Quest {
 
private int qQ;
 
public Quest(int q) {
 
 
qQ = 12 / q;//1
 
}
 
public int getQQ() {
 
 
return qQ;//2
 
}
 
public static void main(String[] args) {
 
 
Quest quest = null;
 
 
try {
 
 
 
quest = new Quest(0);//3
 
 
catch (Exception e) {//4
 
 
}
 
 
System.out.println(quest.getQQ());//5
 
}

Укажите строку, выполнение которой приведет к необрабатываемой в дан-
ном коде исключительной ситуации (1):
1)  1
2)  2
3)  3
4)  4
5)  5

225


Достарыңызбен бөлісу:
1   ...   9   10   11   12   13   14   15   16   ...   22




©engime.org 2024
әкімшілігінің қараңыз

    Басты бет