Java Basics: Исключения

Исключения

Для стандартного блока try-catch-finally требуется указания либо (от одного и более) блока catch, либо блок finally, либо комбинации блоков.

Для блока try-with-resources данные блоки не обязательны, если метод close у имплементации не выкидывает исключений:

1
try(var noExceptionCloseable = new NoExceptionCloseable()) { ... }

Иерархия

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                                 :::::::::::::::::::::::::::::::::::
: -
: java.lang.Throwable -
: -
=:::::::::::::::::::::::::::::::::=
/ \
/ \
/ \
/ \
/ \
::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::
: : : :
: java.lang.Exception : : java.lang.Error :
: : : :
=---------------------------------= =---------------------------------=
|
|
|
|
:::::::::::::::::::::::::::::::::::::::::::::::
: :
: java.lang.RuntimeException :
: :
=:::::::::::::::::::::::::::::::::::::::::::::=

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

Основные Runtime исключения:

  • ArithmeticException - например, при делении на 0;
  • ArrayIndexOutOfBoundsException;
  • ClassCastException;
  • NullPointerException;
  • IllegalArgumentException - часто возникает в связи с ограничениями безопасности; или когда Java приложение еще не готово выполнить код;
  • NumberFormatException - например, при преобразовании строки в число (Integer.parseInt("the number"));
  • IllegalArgumentException;
  • MissingResourceException;
  • NumberFormatException;
  • NullPointerException;
  • UnsupportedOperationException;
  • ArrayStoreException - попытка сохранения в массив определенного типа, объектов другого типа:
1
2
Object[] arr = new String[3];
arr[0] = new Integer(1); // ArrayStoreException

Основные checked исключения:

  • IOException;
  • FileNotFoundException;
  • DataFormatException;
  • InterruptedException;
  • ParseException;
  • SQLException;

Наиболее часто встречающиеся Error:

  • AssertionError;
  • ExceptionInInitializerError;
  • StackOverflowError;
  • NoClassDefFoundError;

Multi-catch

Перехват исключений в 1 блоке catch не допускает обработки наследуемых классов исключений (т.е. связанных):

1
2
3
try {
...
} catch(FileNotFoundException | IOException e) { ... } // ошибка компиляции

Компилятор посчитает такой catch избыточным - достаточно обрабатывать родительский IOException.

Finally

Если в finally есть return и в приложение во время выполнения до него дошло, то метод будет возвращать именно это значение, а не расположеное в блоке try:

1
2
3
4
5
try {
return 1;
} finally {
return 5; // всегда будет 5
}

Правило “последнее слово за finally“ актуально и для исключений:

1
2
3
4
5
6
7
try {
throw new RuntimeException();
} catch(RuntimeException re) {
throw new RuntimeException();
} finally {
throw new Exception(); // метод всегда будет выкидывать именно это исключение
}

try-with-resources

Ресурсы закрываются при выходе из блока try, т.е. до catch и до finally. Закрытие ресурсов происходит в обратном направлении относительно открытия - т.е. при визуальном просмотре снизу вверх:

1
2
3
4
5
6
try(TestCloseable tc1 = new TestCloseable("tc1"); 
var tc2 = new TestCloseable("tc2")) {
out.println("try block");
} finally {
out.println("finally block");
}

Вывод:

1
init tc1 -> init tc2 -> try block -> close tc2 -> close tc1 -> finally block

При try-with-resources создается псевдо-finally, который всегда закрывается перед указанным finally, и перед(!) catch с целевой ошибкой:

  1. try
  2. hidden finally
  3. catch
  4. finally

Если переменные с ресурсами finally или effective finally, то их можно инициализировать вне finally:

1
2
3
4
final FileInputStream fis = ...;
final var closeableRef = ...;
try(fis; FileReader reader = ...; var closeableDocument = ...; closeableRef;) {
}

Suppressed

Метод close может выкидывать ошибки внутри своей реализации. В этом случае они будут обрабатываться как “обычные” исключения в рамках собственного catch блока:

1
2
3
4
5
6
7
try(var closeInternalEx1 = Internal.create(1); 
Internal closeInternalEx2 = Internal.create(2);) {
return 1;
} catch(InternalException ie) {
out.println("error on close.suppressed exception: " + ie.getSuppressed().length);
for(var se : ie.getSuppressed()) se.printStackTrace();
}

В примере выше будет выведена только 1 ошибка с 1 suppressed исключением и только (!) для второго (closeInternalEx2) ресурса, поскольку он будет закрываться первым (и до закрытия 1-го рерсурса (closeInternalEx1) дело даже не дойдет).

Но если в блоке try возникнет какая-то ошибка, то перехват исключения будет выполнен уже для него, а в качестве suppressed будет указано уже 2 исключения (на каждый ресурс):

1
2
3
4
5
6
7
8
9
try(var closeInternalEx1 = Internal.create(1); 
Internal closeInternalEx2 = Internal.create(2);) {
throw new RuntimeException();
} catch(InternalException ie) {
out.println("missing exception");
} catch(RuntimeException re) {
out.println("error on close.suppressed exception: " + ie.getSuppressed().length);
for(var se : ie.getSuppressed()) se.printStackTrace();
}

Override правила

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

1
2
3
4
5
6
7
8
void method1() {...}
@Override void method1() throws Exception {...} // ошибка компиляции

void method2() throws Exception {...}
@Override void method2() {...}

void method3() throws IOException {...}
@Override void method3() throws FileNotFoundException {...}

assert

По умолчанию Java выключает assert. Включается посредством флага -enableassertions или -ea при запуске Java приложения.

В случае если assert нужно включить только для одного пакета или класса, то они указываются после флага и разделяются символом “;”:

1
2
java -ea:my.pack.assertions.on RunProgramClass
java -ea:another.assertions.on.CustomClass RunProgramClass

Для принудительного отключения можно использовать флаги -disableassertions или -da. Так же доступно отключение assert конкретных пакетов и классов:

1
java -ea:another.assertions.off RunProgramClass -da:another.assertions.off.CustomClass RunProgramClass

В примере выше, assert для пакета another.assertions.off включены, но только для класса CustomClass отключены.

Важнейшим правилом при написании assert является то, что assert не должен (!) влиять на данные внутри объектов, которых он так или иначе касается.

 Comments
Comment plugin failed to load
Loading comment plugin