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;

Throwable и Error

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

При наследовании методов допускается снимать checked исключения с метода дочернего класса.

1
2
3
4
5
6
7
8
public class Parent {
public void method() throws MyCustomException {}
}

public class Child extends Parent {
@Override
public void method() {}
}

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

1
2
3
4
5
6
7
8
public class Parent {
public void method() {}
}

public class Child extends Parent {
@Override
public void method() throws MyCustomException {} // ошибка компиляции
}

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

В блок try-with-resources можно добавлять объекты без инициализации, но те, которые должны быть закрыты при выходе из блока try. При этом, требование по реализации Autoclosable для них все так же актуально:

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

Но важным ограничением является использование переменных final или effective final - т.е. ссылки на переменные не должны меняться по ходу выполнения метода. Так, добавив

1
fis = null;

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

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 не должен (!) влиять на данные внутри объектов, которых он так или иначе касается.

NullPointerException

Начиная с версии Java 14 при подобном исключении при выводе журнала ошибки выводится так же подробное обозначение переменной, на которой произошла данная ошибка:

1
2
List<Object> list = null;
list.add(1);
1
2
Cannot invoke "java.util.List.add(Object)" because "list" is null
java.lang.NullPointerException: Cannot invoke "java.util.List.add(Object)" because "list" is null

Флаг -XX:-ShowCodeDetailsInExceptionMessages переданный при запуске приложения отключает журналирование NullPointerException с подобной детализацией.

 Comments
Comment plugin failed to load
Loading comment plugin