Java: исключения и их обработка. Исключения Java

Viber OUT 01.08.2019

В этой главе обсуждается используемый в Java механизм обработки исключений. Исключение в Java - это объект, который описывает исключительное состояние, воз­никшее в каком-либо участке программного кода. Когда возникает ис­ключительное состояние, создается объект класса Exception. Этот объект пересылается в метод, обрабатывающий данный тип исключительной ситуации. Исключения могут возбуждаться и «вруч­ную» для того, чтобы сообщить о некоторых нештатных ситуациях.

Основы

К механизму обработки исключений в Java имеют отношение 5 клю­чевых слов: - try , catch , throw , throws и finally . Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, ко­торое в зависимости от его типа вы можете перехватить (catch) или пере­дать умалчиваемому (finally) обработчику.

Ниже приведена общая форма блока обработки исключений.

try {

// блок кода }

catch (ТипИсключения1 е) {

// обработчик исключений типа ТипИсключения1 }

catch (ТипИсключения2 е) {

// обработчик исключений типа ТипИсключения2

throw(e)// повторное возбуждение исключения }

finally {

}

ЗАМЕЧАНИЕ

В языке Delphi вместо ключевого слова catch используется except.

Типы исключений

В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них - класс Ехception - используется для описания исключительных ситуации, кото­рые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов Throwable - класс Error, который предназначен для описания исклю­чительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.

Неперехваченные исключения

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

class Exc 0 {

int d = 0;

int a = 42 / d;

} }

Вот вывод, полученный при запуске нашего примера.

С:\> java Exc0

at Exc0.main(Exc0.java:4)

О братите внимание на тот факт что типом возбужденного исклю­чения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы. Вот другая версия того же класса, в кото­рой возникает та же исключительная ситуация, но на этот раз не в про­граммном коде метода main.

class Exc1 {

static void subroutine() {

int d = 0;

int a = 10 / d;

}

public static void main(String args) {

Exc1.subroutine();

} }

Вывод этой программы показывает, как обработчик исключений ис­полняющей системы Java выводит содержимое всего стека вызовов.

С:\> java Exc1

java.lang.ArithmeticException: / by zero

at Exc1.subroutine(Exc1.java:4)

at Exc1.main(Exc1.java:7)

try и catch

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

class Exc2 {

public static void main(String args) {

try {

int d = 0;

int a = 42 / d;

}

System.out.println("division by zero");

}

} }

Целью большинства хорошо сконструированных catch-разделов долж­на быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние - такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение – division by zero) .

Несколько разделов catch

В некоторых случаях один и тот же блок программного кода может воз­буждать исключения различных типов. Для того, чтобы обрабатывать по­добные ситуации, Java позволяет использовать любое количество catch-разделов для try-блока. Наиболее специализированные классы исключений должны идти первыми, поскольку ни один подкласс не будет достигнут, если поставить его после суперкласса. Следующая про­грамма перехватывает два различных типа исключений, причем за этими двумя специализированными обработчиками следует раздел catch общего назначения, перехватывающий все подклассы класса Throwable.

class MultiCatch {

public static void main(String args) {

try {

int a = args.length;

int b = 42 / a;

int c = { 1 };

c = 99;

}

catch (ArithmeticException e) {

}

}

} }

Этот пример, запущенный без параметров, вызывает возбуждение ис­ключительной ситуации деления на нуль. Если же мы зададим в командной строке один или несколько параметров, тем самым установив а в значение боль­ше нуля, наш пример переживет оператор деления, но в следующем опе­раторе будет возбуждено исключение выхода индекса за границы масси­ва ArrayIndexOutOf Bounds. Ниже приведены результаты работы этой программы, за­пущенной и тем и другим способом.

С:\> java MultiCatch

а = 0

div by 0: java.lang.ArithmeticException: / by zero

C:\> java MultiCatch 1

a = 1

array index oob: java.lang.ArrayIndexOutOfBoundsException: 42

Вложенные операторы try

Операторы try можно вкладывать друг в друга аналогично тому, как можно создавать вложенные области видимости переменных. Если у оператора try низкого уровня нет раздела catch, соответствующего возбужденному исключению, стек будет развернут на одну ступень выше, и в поисках подходящего обработчика будут прове­рены разделы catch внешнего оператора try. Вот пример, в котором два оператора try вложены друг в друга посредством вызова метода.

class MultiNest {

static void procedure() {

try {

int c = { 1 };

c = 99;

}

catch(ArrayIndexOutOfBoundsException e) {

System.out.println("array index oob: " + e);

} }

public static void main(String args) {

try {

int a = args.length();

System.out.println("a = " + a);

int b = 42 / a;

procedure();

}

catch (ArithmeticException e) {

System.out.println("div by 0: " + e);

}

} }

throw

Оператор throw используется для возбуждения исключения «вруч­ную». Для того, чтобы сделать это, нужно иметь объект подкласса клас­са Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new. Ниже приведена общая форма оператора throw.

throw ОбъектТипаThrowable;

При достижении этого оператора нормальное выполнение кода немед­ленно прекращается, так что следующий за ним оператор не выполня­ется. Ближайший окружающий блок try проверяется на наличие соот­ветствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему. Если нет, проверяется следующий из вложенных операторов try, и так до тех пор пока либо не будет най­ден подходящий раздел catch, либо обработчик исключений исполняю­щей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию, после чего то же исключение возбуждается повторно - на этот раз уже кодом перехватившего его в первый раз раздела catch.

class ThrowDemo {

static void demoproc() {

try {

throw new NullPointerException("demo");

}

catch (NullPointerException e) {

System.out.println("caught inside demoproc");

throw e;

} }

public static void main(String args) {

try {

demoproc();

}

catch(NulPointerException e) {

System.out.println("recaught: " + e);

}

} }

В этом примере обработка исключения проводится в два приема. Метод main создает контекст для исключения и вызывает demoproc. Метод demoproc также устанавливает контекст для обработки исключе­ния, создает новый объект класса NullPointerException и с помощью опе­ратора throw возбуждает это исключение. Исключение перехватывается в следующей строке внутри метода demoproc, причем объект-исключение доступен коду обработчика через параметр e. Код обработчика выводит сообщение о том, что возбуждено исключение, а затем снова возбуждает его с помощью оператора throw, в результате чего оно передается обра­ботчику исключений в методе main. Ниже приведен результат, получен­ный при запуске этого примера.

С:\> java ThrowDemo

caught inside demoproc

recaught: java.lang.NullPointerException: demo

throws

Если метод способен возбуждать исключения, которые он сам не об­рабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется ключе­вое слово throws . Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключе­ние соответствующего класса, тип класса исключений должен быть ука­зан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следую­щим образом:

тип имя_метода(список аргументов) throws список_исключений {}

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

class ThrowsDemo1 {

static void procedure() {

System.out.println("inside procedure");

}

public static void main(String args) {

procedure();

} }

Для того, чтобы мы смогли оттранслировать этот пример, нам при­дется сообщить транслятору, что procedure может возбуждать исключе­ния типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений:

class ThrowsDemo {

static void procedure() throws IllegalAccessException {

System.out.println(" inside procedure");

throw new IllegalAccessException("demo");

}

public static void main(String args) {

try {

procedure();

}

catch (IllegalAccessException e) {

System.out.println("caught " + e);

}

} }

Ниже приведен результат выполнения этой программы.

С:\> java ThrowsDemo

inside procedure

caught java.lang.IllegalAccessException: demo

finally

Иногда требуется гарантировать, что определенный участок кода будет выпол­няться независимо от того, какие исключения были возбуждены и пере­хвачены. Для создания такого участка кода используется ключевое слово finally. Даже в тех случаях, когда в методе нет соответствующего воз­бужденному исключению раздела catch, блок finally будет выполнен до того, как управление перейдет к операторам, следующим за разделом try. У каждого раздела try должен быть по крайней мере или один раз­дел catch или блок finally. Блок finally очень удобе н для закрытия файлов и освобождения любых других ресурсов, захваченных для времен­ного использования в начале выполнения метода. Ниже приведен пример класса сдвумяметодами,завершение которых происходит по разным причинам, нов обоихперед выходом выполняется код раздела finally.

class FinallyDemo {

static void procA() {

try {

System.out.println("inside procA");

throw new RuntimeException("demo");

}

finally {

System.out.println("procA"s finally");

} }

static void procB() {

try {

System.out.println("inside procB");

return;

}

finally {

System.out.println("procB"s finally");

} }

public static void main(String args) {

try {

procA();

}

catch (Exception e) {}

procB();

} }

В этом примере в методе procA из-за возбуждения исключения про­исходит преждевременный выход из блока try, но по пути «наружу» вы­полняется раздел finally. Другой метод procB завершает работу выпол­нением стоящего в try-блоке оператора return, но и при этом перед выходом из метода выполняется программный код блока finally. Ниже приведен результат, полученный при выполнении этой программы.

С:\> java FinallyDemo

inside procA

procA"s finally

inside procB

procB"s finally

Подклассы Exception

Только подклассы класса Throwable могут быть возбуждены или пере­хвачены. Простые типы - int, char и т.п., а также классы, не являю­щиеся подклассами Throwable, например, String и Object, использоваться в качестве исключений не могут. Наиболее общий путь для использова­ния исключений - создание своих собственных подклассов класса Ex­ception. Ниже приведена программа, в которой объявлен новый подкласс класса Exception.

class MyException extends Exception {

private int detail;

MyException(int a) {

detail = a:

}

public String toString() {

return "MyException[" + detail + "]";

}

}

class ExceptionDemo {

static void compute(int a) throws MyException {

System.out.println("called computer + a + ").");

if (a > 10)

throw new MyException(a);

System.out.println("normal exit.");

}

public static void main(String args) {

try {

compute(1);

compute(20);

}

catch (MyException e) {

System.out.println("caught" + e);

}

} }

Этот пример довольно сложен. В нем сделано объявление подкласса MyException класса Exception. У этого подкласса есть специальный кон­структор, который записывает в переменную объекта целочисленное значение, и совмещенный метод toString, выводящий значение, хранящееся в объекте-исключении. Класс ExceptionDemo определяет метод compute, который возбуждает исключение типа MyExcepton. Простая логика метода compute возбуждает исключение в том случае, когда значение пара-ветра метода больше 10. Метод main в защищенном блоке вызывает метод compute сначала с допустимым значением, а затем - с недопус­тимым (больше 10), что позволяет продемонстрировать работу при обоих путях выполнения кода. Ниже приведен результат выполнения програм­мы.

С:\> java ExceptionDemo

called compute(1).

normal exit.

called compute(20).

caught MyException

Заключительное резюме

Обработка исключений предоставляет исключительно мощный меха­низм для управления сложными программами. Try, throw, catch дают вам простой и ясный путь для встраивания обработки ошибок и прочих нештатных ситуаций в программную логи­ку. Если вы научитесь должным об­разом использовать рассмотренные в данной главе механизмы, это при­даст вашим классам профессиональный вид, и любые будущие пользователи вашего программного кода, несомненно, оценят это.

Оператор throw используется для возбуждения исключения «вручную». Для того чтобы сделать это, нужно иметь объект подкласса класса Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new. Ниже приведена общая форма оператораthrow ,

throw ОбъектТипа Throwable ;

При достижении этого оператора нормальное выполнение кода немедленно прекращается, так что следующий за ним оператор не выполняется. Ближайший окружающий блок try проверяется на наличие соответствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему. Если нет, то проверяется следующий из вложенных операторов try и так до тех пор, пока либо не будет найден подходящий раздел catch, либо обработчик исключений исполняющей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию, после чего то же исключение возбуждается повторно - на этот раз уже кодом перехватившего его в первый раз раздела catch.

class ThrowDemo {
static void demoproc() {
{
throw new NullPointerException("demo");
}
catch (NullPointerException e) {
System.out.println("caught inside demoproc");
throw e;
}
}

try {
demoproc();
}
catch(NulPointerException e) {
System.out.println("recaught:" + e);
}
}
}

В этом примере обработка исключения проводится в два приема. Метод main создает контекст для исключения и вызывает demoproc. Метод demoproc также устанавливает контекст для обработки исключения, создает новый объект класса NullPointerException и с помощью оператора throw возбуждает это исключение. Исключение перехватывается в следующей строке внутри метода demoproc, причем объект-исключение доступен коду обработчика через параметр «е». Код обработчика выводит сообщение о том, что возбуждено исключение, а затем снова возбуждает его с помощью оператора throw, в результате чего оно передается обработчику исключений в методе main. Ниже приведен результат, полученный при запуске этого примера.

С:\> java ThrowDemo
caught inside demoproc
recaught: java.lang.NullPointerException: demo

9.8. Оператор throws

Если метод способен возбуждать исключения, которые он сам не обрабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется оператор throws. Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключение соответствующего класса, тип класса исключений должен быть указан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следующим образом:

тип имя_метода(список аргументов) throws список исюпочений {}

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

class ThrowsDemo 1 {
static void procedure ()
{
System.out.println("inside procedure");
}
public static void main(String args) {
procedure ();
}
}

Для того чтобы мы смогли оттранслировать этот пример, нам придется сообщить транслятору, что procedure может возбуждать исключения типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений:

class ThrowsDemo {
static void procedure() throws IllegalAccessException
{
System.out.println(" inside procedure");
throw new IllegalAccessException("demo");
}
public static void main(String aigs) {
try {
procedure();
}
catch (IllegalAccessException e) {
System.out.println("caught" + e);
}
}
}

Ниже приведен результат выполнения этой программы.

С :\> java ThrowsDemo
inside procedure
caught javaJangIllegalAccessException: demo

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

Блок try, блок catch

Часть кода, где возможно возникновение исключительной ситуации, охватывают блоком try.

Обработчики исключений объявляются в блоке catch, который следует сразу за блоком try.

Блоков catch может быть несколько для одного блока try.

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

Блок finally

После блока(ов) ислючения может располагаться блок finally. Приемлем случай, когда нет блоков catch, но есть блок finally. Код блока finally выполняется всегда, а не только при возникновении исключения. В нем можно выполнять обязательные завершающие действия, очистку, например, закрыть поток, а не в блоке try. После finally выполнение программы продолжается со следующей строки кода.

Схематично всё выглядет так:

Public void aFunc() { try { //код } catch(TypeException e) { //код обработчика исключения типа //TypeException; } catch(SecondTypeException e) { //код обработчика исключения типа //SecondTypeException; } finally { //обязательные завершающие действия. //Код блока finally //выполняется всегда, а не только при //возникновении исключения. } }

а может быть без блоков catch, вот так:

Public void aFunc() { try { //код } finally { //обязательные завершающие действия. //Код блока finally //выполняется всегда, а не только при //возникновении исключения. } }

или без блока finally:

Public void aFunc() { try { //код } catch(TypeException e) { //код обработчика исключения типа //TypeException; } }

Рассмотрим подробнее, что происходит при возникновении исключительной ситуации.

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

Врезка

Стек вызовов(call stack)

Стек вызовов(call stack) – это пследовательность вызовов методов, начиная от метода main до интересующего нас метода. Рассмотрим условный пример. Есть класс:

Public class AClass { public AClass() {} public void methodC() { } public void methodB() { methodC(); } public void methodA() { try { methodB(); } catch(AnException e) { System.out.println(e.getMessage()); } } public static void main(String args) { AClass ac = new Aclass(); ac.methodA(); } }

Здесь стек вызовов такой: main вызывает methodA(), methodA() вызывает methodB(), methodB() вызывает methodС().

Конец врезки

Продолжим изучение действий системы после получения ею объекта исключения (предположим, что тип объекта исключения - AnException), выброшенного каким-то методом. Пусть это будет метод methodС(), см. врезку «Стек вызовов » выше. Система начинает просматривать стек вызовов для methodС() в обратном порядке, т.е, стартуя с самого метода methodС(), перемещается в напралении main. Цель этого просмотра – найти обработчик исключения, возникшего в methodС(). Сначала система просматривает methodС(), если в нём нет нужного обработчика исключения, то переходит к methodB(), если и в нём нет, то переходит к methodA(). methodA() содержит обработчик типа AnException, вот этому-то обработчику и передаёт система объект исключения на обработку. Говорят, что обработчик захватывает (catch) исключение. Если система не нашла бы обработчик ислючения нужного типа, то выполнение программы было бы завершено.

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

Три категории исключений

Контролируемые (checked) исключения . Например, ошибки ввода. Такие исключения рекомендуется обрабатывать;

Ошибка (error) . Это исключения, вызванные внешними, по отношению к приложению, причинами и приложение их, как правило, не обрабатывает. Такие исключения определяются классом Error или его подклассами;

Исключения времени выполнения (runtime exception) . Например, ошибки в коде. Такие исключения определяются классом RuntimeException или его подклассами. Эти исключения могут обрабатываться.

Исключения второго и третьего типа называют неконтролируемые (unchecked) исключения .

Метод может не обрабатывать исключение, но, предполагая, что оно может возникнуть, передать его на обработку в стек вызовов. Тогда в объявлении метода пишут оператор throws и в нём через запятую перечень возможных типов исключений. Пример:

Public void methodC(int a) throws AnException { //code; }

Метод может сам выбросить исключение. Для этого используют оператор throw и объект исключения в нём. Пример:

Public void methodC(int a) throws AnException { if(a < 10) { throw new AnException("Error in methodC"); } }

Если этом примере a < 10, то будет выброшено исключение AnException, так, как если бы здесь произошла соответствующая ошибка.

Выбросить исключение оператором throw можно и из тела оработчика другого исключения. Так возникает цепочка исключений (chained exceptions) . Пример:

Public void methodA() { try { methodB(); } catch(AnException e) { System.out.println(e.toString()); throw new NextException("methodA throws NextException."); } }

Все классы исключений восходят к классу Throwable . Непосредственными наследниками его являются классы Error и Exception . От Exception происходят RuntimeException и все классы контролируемых исключений.

Мы уже знаем, что исключения типа Error обрабатывает система, мы их обычно не касаемся.

Исключения типа RuntimeException говорят об ошибках в коде. Такие исключения могут обрабатываться, но лучше просто исправить ошибки кода.

Исключения типа Exception говорят о наличии проблем, но они не являются серьёзными системными. Такие ошибки следует обрабатывать.

Можно создавать свои классы исключений как прямых или нет наследков Exception . Принято к названиям таких классов добавлять слово Exception .

Если тип аргумента оператора catch есть Exception , то соотвестсвующий обработчик исключений сможет захватить все контролируемые исключения и исключения типа RuntimeException . Так можно поступить, если нет особых требований к обработчику или если могут возникнуть непредвиденные исключения. Но если требуется особый подход, то лучше использовать наследников Exception .

Как самому создать класс исключения?

Так как мы должны обрабатывать контролируемые исключения, то унаследуем наш класс от Exception .

Пример

В заключении привожу полный код примера, который рассматривался на протяжении этого раздела. Главный класс:

Package exceptionTest.test; public class TestClass { public TestClass() { } public void methodC(int a) throws AnException { if(a < 10) { throw new AnException("Error in methodC"); } } public void methodB() throws AnException { methodC(1); } public void methodA() { try { methodB(); } catch(AnException e) { System.out.println(e.toString()); throw new NextException("methodA throws NextException."); } } public static void main(String args) { TestClass tc = new TestClass(); try { tc.methodA(); } catch(NextException ex) { System.out.println(ex.toString()); } finally { System.out.print("Message from finally: The end."); } } }

Класс исключения AnException:

Public class AnException extends Exception { private static final long serialVersionUID = 1L; private String exceptionMessage; AnException() { exceptionMessage = null; } AnException(String exceptionMessage) { this.exceptionMessage = exceptionMessage; } public String toString() { return "Message from AnException: " + exceptionMessage; } }

Класс исключения NextException:

Package exceptionTest.test; public class NextException extends RuntimeException { private static final long serialVersionUID = 1L; private String exceptionMessage; NextException() { exceptionMessage = null; } NextException(String exceptionMessage) { this.exceptionMessage = exceptionMessage; } public String toString() { return "Message from NextException: " + exceptionMessage; } }

О чем и зачем эта статья?

В статье рассказывается об исключениях в языке Java, методах их обработки и некоторых особенностях работы с исключениями. Статья написана для семинара по технологиям Java, проводимого компанией i.Point .

Что такое исключения?

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

Итак, каждый раз, когда при выполнении программы происходит ошибка, то программа выбрасывает исключение - в этот момент создается специальный объект-исключение (exception-object), дальше будем называть его просто исключение. Этот объект содержит информацию о возникшей ошибке: тип ошибки, а также запись о состоянии программы на момент возникновения ошибки. Создание исключения и передача его среде выполнения называется выбрасыванием исключения (exception throwing). Посмотрим, как происходит выброс исключений при выполнении программы в Java:

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

Типы исключений в Java

Взглянем на иерархию классов объектов-исключений в Java:

Как мы видим, все исключения имеют общего предка - Throwable. Он имеет два важных подкласса - Exception и Error. Исключения (Exceptions) являются результатом возникших внутри программы проблем, которые в принципе решаемы и предсказуемы. Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться перехватить в приложении, написанном достаточно рационально (например ошибка OutOfMemoryError происходит в тех случаях, когда JVM не хватает памяти для выполнения программы). Кроме того, у Exception есть важный потомок - RuntimeException (исключение времени выполнения). Этот класс и его потомки представляют собой исключения, которые возникают во время "нормальной работы Java-машины" (примерами таких исключений являются попытки использования нулевых ссылок на объекты, деления на ноль или выход за границы массива).

В Java все исключения делятся на три типа: контролируемые исключения(checked), ошибки (Errors) и исключения времени выполнения (RuntimeExceptions) - последние два типа также объединяют в категорию неконтролируемых (unchecked) исключений. В чем различие? Все очень просто контролируемые исключения представляют собой те ошибки, которые могут быть обработаны в ходе выполнения программы, как вы уже догадались к этому типу относятся все потомки класса Exception (но не RuntimeException). Контролируемые исключения обязательны для обработки в коде программы, они должны быть обработаны либо включением в блок try-catch, либо объявлены в сигнатуре метода.

Неконтролируемые (unchecked) исключения не требуют обязательной обработки, поскольку представляют собой те ситуации, когда ошибка не зависит непосредственно от программиста (например произошёл сбой в аппаратном обеспечении), либо те, когда ошибку обрабатывать не имеет смысла, т.к. проще внести изменения в код - к ним относятся все потомки классов Error и RuntimeException.

Как обрабатывать исключения?

Все современные реализации языка Java придерживаются принципа обработки или объявления исключений (The Catch or Specify Requirement), который гласит, что код, который потенциально может сгенерировать контроллируемое исключение должен либо быть заключен в блок try-catch (таким образом в блоке catch мы предоставляем обработчик для исключительной ситуации), либо мы должны объявить , что наш метод может выбросить такое исключение (после ключевого слова throws, после имени метода).

Рассмотрим несколько примеров:

// Note: This class won"t compile by design! import java . io .*; import java . util . Vector ; public class ListOfNumbers { private Vector vector ; private static final int SIZE = 10 ; public ListOfNumbers () { vector = new Vector (SIZE ) ; for (int i = 0 ; i < SIZE ; i ++ ) { vector . addElement (new Integer (i )) ; } } public void writeList () { < SIZE ; i ++ ) { out . println (" Value at: " + i + " = " + vector . elementAt (i )) ; } out . close () ; } }

Этот код не скомпилируется, т.к. конструктор FileWriter требует от нас обработать IOException. Правильно написанный код должен выглядеть примерно так:

Try { //Необработанное контролируемое (checked) исключение IOException PrintWriter out = new PrintWriter (new FileWriter (" OutFile.txt " )) ; for (int i = 0 ; i < SIZE ; i ++ ) { // метод elementAt выбрасывает неконтролируемое исключение ArrayIndexOutOfBoundsException out . println (" Value at: " + i + " = " + vector . elementAt (i )) ; } catch (IOException e ){ //Пытаемся как-то исправить ситуацию, если ошибка возникла при создании файла OutFile.txt } catch (Exception e ){ //В случае если в блоке try будет сгенерировано не IOException, то управление перейдет сюда } finally { //В любом случае нам необходимо закрыть файл. if (out != null ){ out . close () ; } }

Таким образом мы обрабатываем исключение IOException. Обратите внимание на порядок объявления блоков catch - если поменять их местами, то код не скомпилируется, т.к. IOException подкласс Exception, то код обработки IOException станет недостижимым и компилятор выдаст ошибку. Особого внимания заслуживает блок finally - код в этом блоке выполняется всегда, независимо от того, что произошло в блоках try и catch.

Кроме непосредственно обработки исключения с помощью блока try-catch мы можем просто его объявить, предоставив пользователям метода самим разбираться с этой проблемой:

Public void writeList () throws IOException { //теперь нам не нужно самим обрабатывать исключение. }

Обрабатывать или объявлять?

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

Обрабатывайте исключение, когда вы можете это сделать; объявляйте исключение, когда вы вынуждены так поступить.

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

Преимущества, которые дает нам использование исключений

написать тут про errorcodes и раскрыть списочег

  1. Разделение обычного кода и кода обработки ошибок
  2. Возможность передачи исключений для обработки вверх по стеку вызовов
  3. Группировка и обработка ошибок по типам

Проблемы, связанные с обработкой исключений

"Потерянные исключения"

Вообще, исключения в Java очень удобны и просты, но, к сожалению, и у них есть недостаток. Хотя исключения являются индикаторами проблем в программе и не должны игнорироваться, возможна ситуация, при которой исключение просто потеряется. Это может произойти, если мы неправильно напишем код в блоке finally. Рассмотрим простой пример:

// Как может быть потеряно исключение. class VeryImportantException extends Exception { public String toString () { return " A very important exception! " ; } } class HoHumException extends Exception { public String toString () { return " A trivial exception " ; } } public class LostMessage { void f () throws VeryImportantException { throw new VeryImportantException () ; } void dispose () throws HoHumException { throw new HoHumException () ; } public static void main (String args ) throws Exception { LostMessage lm = new LostMessage () ; try { lm . f () ; } finally { lm . dispose () ; } } }

Что же мы получим в результате выполнения этого кода?

Exception in thread "main" A trivial exception
at LostMessage.dispose(LostMessage.java:21)
at LostMessage.main(LostMessage.java:29)

О ужас, мы потеряли очень важное для нас VeryImportantException, получив вместо него менее значительное.
Поэтому при написании кода в блоке finally нужно быть очень осторожными, чтобы не происходило подобных потерь информации.

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

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

Иерархия

Прежде чем мы перейдём к практике, давайте познакомимся с видами исключений Джава и их иерархией. В основе всего лежит класс Throwable. Все возможные конфликты кода с машиной и пользователем описаны здесь. Для удобства обработки и чтения класс Throwable имеет подклассы Error и Exception. Error – критические ошибки, которые не обязательно происходят по вине пользователя, обработать их невозможно. Exception – собственно конфликты нашей программы, которые необходимо отлавливать.

Взгляните на упрощённую схему иерархии исключений java:

Как видно, блоки делятся на «два лагеря» по цветам - проверяемые и непроверяемые java исключения. Данная классификация показывает, как их воспринимает компилятор: проверяемые – учитывает, непроверяемые – игнорирует. К первому относится Exception в полном составе, кроме RuntimeException. Все остальные классы исключений – непроверяемые компилятором.

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

Создание обработчика

Для обработки исключений java используются следующие операторы: try, catch, finally, throw, throws. Первые три - стандартная структура вашего блока. По шагам:

  1. Оператор или часть кода, в которой вам надо отыскать ошибку, помещается в блок try.
  2. Далее в блоке catch вы указываете, что за исключение надо ловить и как его обрабатывать.
  3. В блоке finally набор обязательных действий при возникновении ошибки. Обычно это запись данных, закрытие ресурсов и пр. Блок исполняется всегда, вне зависимости от срабатывания catch.

Рассмотрим структуру на примере Джава исключения:

try {

}
catch (тип_исключения объект_исключения) {
// код обработки
}
finally {

}

Если вы хотите обработать несколько исключений – просто создайте ещё один блок catch.

try {
// код, где мы хотим отследить ошибку
}
catch (тип_исключения_1 объект_исключения_1) {
// код обработки
}
catch (тип_исключения_2 объект_исключения_2) {
// код обработки
}
finally {
// что нужно выполнить после завершения блока try
}

С помощью оператора throw вы можете создавать исключения:

throw экземпляр_Throwable

На практике это выглядит так:

Student stud1;

if(stud1 == null){

}
}

Включим оператор throw в наш стандартный пример с try-catch:

public void onClick(View view) {
if (stud1 == null) {
try {
throw new NullPointerException("Студента не существует");
} catch (NullPointerException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}

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

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

тип имя_метода(список_параметров) throws список_исключений {
// код метода
}

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

Операторы try можно вкладывать друг в друга. При этом если вложенный обработчик не имеет своего блока catch, он осуществляет его поиск в родительском операторе. Если и там нет – блок обрабатывается системой.

Готовые и новые исключения

  • ArithmeticException - ошибки вычислений.
  • NullPointerException - ссылка на пустое место.
  • NegativeArraySizeException - массив отрицательной размерности.
  • ArrayStoreException - присвоение элементу массива неправильного типа.
  • NumberFormatException - невозможно преобразовать строку в число.
  • IllegalArgumentException - неправильный аргумент при вызове метода.
  • UnsupportedOperationException - указанной операции не существует.
  • TypeNotPresentException - указанного типа не существует.

Все указанные типы java исключений содержатся в классе RuntimeException, а значит, их не надо указывать в блоке throws.

Естественно, система не может содержать всевозможные исключения. Некоторые придётся создавать самостоятельно. Для того, чтобы создать собственное java исключение, вам необходимо унаследовать собственный класс от Exception и переопределить требуемые методы класса Throwable. Или унаследоваться от наиболее близкого по смыслу типа. Рассмотрим на примере программы под android создание java исключения:

package geekbrains.exception;

Import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

Public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

Public void testMethod() throws StudentException {
System.out.println("Возбуждаем StudentException из метода testMethod()");
throw new StudentException(); // конструктор по умолчанию
}

Public void testMethod2() throws StudentException {
System.out.println("Возбуждаем StudentException из метода testMethod2()");
throw new StudentException("Создано во втором методе");
}

Public void onClick(View view) {
try {
testMethod();

e.printStackTrace();
System.out.println("Исключение перехвачено");
}

Try {
testMethod2();
} catch (StudentException e) {
e.printStackTrace();
}
}

Class StudentException extends Exception {
StudentException() {
}

StudentException(String msg) {
super(msg);
}
}
}

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



Рекомендуем почитать

Наверх