Java Language
Исключения и обработка исключений
Поиск…
Вступление
Throwable
и его подтипы могут быть отправлены в стек с ключевым словом throw
и пойманы с помощью try…catch
statement.
Синтаксис
void someMethod () throws SomeException {} // объявляет метод, заставляет вызывающие вызовы метода catch, если SomeException - это проверенный тип исключения
пытаться {
someMethod(); //code that might throw an exception
}
catch (SomeException e) {
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
в конце концов {
//code that will always run, whether try block finishes or not
}
Захват исключения с помощью try-catch
Исключение можно поймать и обработать с помощью инструкции try...catch
. (На самом деле утверждения try
принимают другие формы, как описано в других примерах о try...catch...finally
и try-with-resources
.)
Уловка с одним блоком catch
Самая простая форма выглядит так:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
Поведение простого try...catch
выглядит следующим образом:
- Выполняются операторы в блоке
try
. - Если исключение не вызывается операторами в блоке
try
, тогда управление переходит к следующему утверждению послеtry...catch
. - Если в блоке
try
выбрано исключение.- Объект исключения проверяется, является ли он экземпляром
SomeException
или подтипом. - Если это так, то
catch
блок будет поймать исключение:- Переменная
e
привязана к объекту исключения. - Выполняется код в блоке
catch
. - Если этот код генерирует исключение, то вновь созданное исключение распространяется вместо исходного.
- В противном случае управление переходит к следующему утверждению после
try...catch
.
- Переменная
- Если это не так, исходное исключение продолжает распространяться.
- Объект исключения проверяется, является ли он экземпляром
Попытка с несколькими уловами
У try...catch
также может быть несколько блоков catch
. Например:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
Если существует несколько блоков catch
, они проверяются по одному за раз, начиная с первого, пока не будет найдено совпадение для исключения. Соответствующий обработчик выполняется (как указано выше), а затем управление передается следующему оператору после инструкции try...catch
. Блоки catch
после того, который совпадает, всегда пропускаются, даже если код обработчика генерирует исключение .
Стратегия совпадения «сверху вниз» имеет последствия для случаев, когда исключения в блоках catch
не пересекаются. Например:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
Этот фрагмент кода выводит «Исключение», а не «Исключение RuntimeException». Поскольку RuntimeException
является подтипом Exception
, первый (более общий) catch
будет сопоставлен. Второй (более конкретный) catch
никогда не будет выполнен.
Урок, который следует извлечь из этого, состоит в том, что сначала должны появиться наиболее специфические блоки catch
(с точки зрения типов исключений), а самые общие из них должны быть последними. (Некоторые компиляторы Java предупреждают вас, если catch
никогда не будет выполнена, но это не ошибка компиляции.)
Блоки с несколькими исключениями
Начиная с Java SE 7, один блок catch
может обрабатывать список несвязанных исключений. Отображается тип исключения, разделенный символом вертикальной полосы ( |
). Например:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
Поведение множественного исключения - это простое расширение для случая с одним исключением. catch
совпадает, если выбранное исключение совпадает (хотя бы) с одним из перечисленных исключений.
В спецификации есть некоторые дополнительные тонкости. Тип e
является синтетическим объединением типов исключений в списке. Когда используется значение e
, его статический тип является наименее распространенным супертипом объединения типов. Однако, если e
забрасывается внутри блока catch
, типы исключений, которые выбрасываются, являются типами объединения. Например:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
В приведенном выше IOException
и SQLException
проверяются исключениями, наименее распространенным супертипом которых является Exception
. Это означает, что метод report
должен соответствовать report(Exception)
. Однако компилятор знает, что throw
может вызывать только IOException
или SQLException
. Таким образом, method
может быть объявлен как throws Exception
throws IOException, SQLException
а не throws Exception
. (Что хорошо: см. Pitfall - Throwing Throwable, Exception, Error или RuntimeException .)
Выброс исключения
В следующем примере показаны основы исключения исключения:
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
Исключение составляет 3-я строка. Это утверждение можно разбить на две части:
new IllegalArgumentException(...)
создает экземпляр классаIllegalArgumentException
с сообщением, описывающим ошибку, о которой сообщается исключение.throw ...
затем бросает объект исключения.
Когда исключение выбрасывается, это приводит к тому, что закрывающие операторы прерываются аномально до тех пор, пока не будет обработано исключение. Это описано в других примерах.
Хорошей практикой является как создание, так и создание объекта исключения в одном выражении, как показано выше. Также хорошей практикой является включение значимого сообщения об ошибке в исключение, чтобы помочь программисту понять причину проблемы. Однако это не обязательно сообщение, которое вы должны показывать конечному пользователю. (Для начала Java не имеет прямой поддержки для интернационализации сообщений об исключениях.)
Есть еще несколько моментов:
Мы объявили
checkNumber
какcheckNumber
throws IllegalArgumentException
. Это не было строго необходимо, поскольку исключениеIllegalArgumentException
является проверенным исключением; см . Иерархию исключений Java - Непроверенные и проверенные исключения . Тем не менее, это хорошая практика, чтобы сделать это, а также включить исключения, вызванные комментариями javadoc метода.Код сразу после того, как заявление о
throw
недостижима . Следовательно, если бы мы написали это:throw new IllegalArgumentException("it is bad"); return;
компилятор сообщит об ошибке компиляции для оператора
return
.
Цепочка исключений
Многие стандартные исключения имеют конструктор со вторым аргументом cause
в дополнение к обычному аргументу message
. cause
позволяет вам перехватывать исключения. Вот пример.
Сначала мы определяем неконтролируемое исключение, которое наше приложение бросает, когда оно встречает непоправимую ошибку. Обратите внимание, что мы включили конструктор, который принимает аргумент cause
.
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
Далее приведен код, иллюстрирующий цепочку исключений.
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
throw
внутри блока try
обнаруживает проблему и сообщает об этом через исключение простым сообщением. Напротив, throw
внутри блока catch
обрабатывает IOException
, обертывая его в новое (отмеченное) исключение. Однако это не исключает первоначальное исключение. IOException
в качестве cause
, мы записываем его так, чтобы его можно было напечатать в стеке, как описано в разделе «Создание и чтение стеков» .
Пользовательские исключения
В большинстве случаев проще с точки зрения кодового дизайна использовать существующие общие классы Exception
при бросании исключений. Это особенно верно, если вам нужно только исключение, чтобы нести простое сообщение об ошибке. В этом случае исключение RuntimeException обычно предпочтительнее, поскольку это не проверенное исключение. Другие классы исключений существуют для общих классов ошибок:
- UnsupportedOperationException - некоторая операция не поддерживается
- IllegalArgumentException - недопустимое значение параметра передавалось методу
- IllegalStateException - ваш API имеет внутреннее состояние, которое никогда не должно происходить, или которое происходит в результате неправильного использования вашего API
Случаи , когда вы хотите использовать пользовательский класс исключений включают в себя следующее:
- Вы пишете API или библиотеку для использования другими пользователями, и вы хотите, чтобы пользователи вашего API могли специально улавливать и обрабатывать исключения из вашего API и иметь возможность отличать эти исключения от других, более общих исключений .
- Вы бросаете исключения для определенной ошибки в одной части вашей программы, которую вы хотите поймать и обработать в другой части вашей программы, и хотите отличать эти ошибки от других, более общих ошибок.
Вы можете создавать свои собственные исключения, расширяя RuntimeException
для неконтролируемого исключения или проверяя исключение, расширяя любое Exception
которое также не является подклассом RuntimeException , потому что:
Подклассы исключения, которые также не являются подклассами RuntimeException, проверяются исключениями
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
Они могут использоваться как предопределенные исключения:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
И поля могут использоваться там, где исключение поймано и обработано:
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
Имейте в виду, что, согласно Oracle Java Documentation :
[...] Если клиент может разумно ожидать восстановления от исключения, сделайте это проверенным исключением. Если клиент не может ничего сделать для восстановления из исключения, сделайте его незафиксированным исключением.
Больше:
Оператор try-with-resources
Как иллюстрирует пример утверждения try-catch-final , очистка ресурсов с использованием предложения finally
требует значительного количества кода «котельной пластины» для правильной реализации краев. Java 7 предоставляет гораздо более простой способ решения этой проблемы в форме инструкции try-with-resources .
Что такое ресурс?
Java 7 представила интерфейс java.lang.AutoCloseable
позволяющий управлять классами с помощью инструкции try-with-resources . Экземпляры классов, которые реализуют AutoCloseable
, называются ресурсами . Обычно их нужно утилизировать своевременно, а не полагаться на сборщик мусора, чтобы распоряжаться ими.
Интерфейс AutoCloseable
определяет один метод:
public void close() throws Exception
Метод close()
должен распоряжаться ресурсом соответствующим образом. В спецификации указано, что безопасно вызывать метод на ресурсе, который уже был удален. Кроме того, для классов, реализующих Autocloseable
, настоятельно рекомендуется объявить метод close()
для исключения более конкретного исключения, чем Exception
, или вообще никакого исключения.
Широкий спектр стандартных классов Java и интерфейсов реализует AutoCloseable
. Они включают:
-
InputStream
,OutputStream
и их подклассы -
Reader
,Writer
и их подклассы -
Socket
иServerSocket
и их подклассы -
Channel
и его подклассы и - JDBC интерфейсы
Connection
,Statement
иResultSet
и их подклассы.
Приложения и сторонние классы могут это сделать.
Основная инструкция try-with-resource
Синтаксис try-with-resources основан на классических формах try-catch , try-finally и try-catch-finally . Вот пример «базовой» формы; т.е. форму без catch
или, finally
.
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
Ресурсы для управления объявляются как переменные в разделе (...)
после предложения try
. В приведенном выше примере мы объявляем stream
переменной ресурса и инициализируем его для вновь созданного PrintStream
.
После инициализации переменных ресурса выполняется блок try
. Когда это будет завершено, stream.close()
будет вызываться автоматически, чтобы гарантировать, что ресурс не протекает. Обратите внимание, что вызов close()
происходит независимо от того, как выполняется блок.
Расширенные инструкции try-with-resource
Оператор try-with-resources может быть расширен с помощью блоков catch
и finally
, как и в синтаксисе pre-Java 7 try-catch-finally . Следующий фрагмент кода добавляет catch
блок к нашему предыдущему , чтобы иметь дело с FileNotFoundException
что PrintStream
конструктор может бросить:
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
Если либо инициализация ресурса, либо блок try генерирует исключение, тогда будет выполняться блок catch
. Блок finally
всегда будет выполнен, как и в обычном заявлении try-catch-finally .
Следует отметить еще пару вещей:
- Переменная ресурса выходит из области действия в блоках
catch
иfinally
. - Очистка ресурсов произойдет до того, как оператор попытается сопоставить блок
catch
. - Если автоматическая очистка ресурсов породила исключение, то это можно было бы поймать в одном из блоков
catch
.
Управление несколькими ресурсами
В приведенных выше фрагментах кода отображается один управляемый ресурс. На самом деле, try-with-resources могут управлять несколькими ресурсами в одном заявлении. Например:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
Это ведет себя так, как вы ожидали. Оба is
и os
автоматически закрываются в конце блока try
. Есть несколько замечаний:
- Инициализация происходит в порядке кода, а позже инициализаторы переменных ресурсов могут использовать значения предыдущих.
- Все переменные ресурсов, которые были успешно инициализированы, будут очищены.
- Переменные ресурсов очищаются в обратном порядке их деклараций.
Таким образом, в приведенном выше примере, is
инициализируется перед os
и очищена после него, и is
будет очищена , если есть исключение при инициализации os
.
Эквивалентность try-with-resource и классического try-catch-finally
Спецификация языка Java определяет поведение форм try-with-resource в терминах классического заявления try-catch-finally . (Подробную информацию см. В JLS.)
Например, этот базовый try-with-resource :
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
определяется как эквивалент этого try-catch-finally :
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(JLS указывает, что фактические переменные t
и primaryException
будут невидимы для обычного Java-кода.)
Расширенная форма try-with-resources определяется как эквивалентность базовой форме. Например:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
эквивалентно:
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
Создание и чтение стоп-кадров
Когда создается объект исключения (т. Throwable
Когда вы его new
), конструктор Throwable
захватывает информацию о контексте, в котором было создано исключение. Позже эта информация может выводиться в виде stacktrace, которая может использоваться для диагностики проблемы, вызвавшей исключение в первую очередь.
Печать стоп-кадра
Печать stacktrace - это просто вызов метода printStackTrace printStackTrace()
. Например:
try {
int a = 0;
int b = 0;
int c = a / b;
} catch (ArithmeticException ex) {
// This prints the stacktrace to standard output
ex.printStackTrace();
}
Метод printStackTrace()
без аргументов будет печатать на стандартный вывод приложения; т.е. текущий System.out
. Существуют также printStackTrace(PrintStream)
и printStackTrace(PrintStream)
printStackTrace(PrintWriter)
перегружают эту печать в указанный Stream
или Writer
.
Заметки:
В стеке нет сведений о самом исключении. Вы можете использовать метод
toString()
для получения этих деталей; например// Print exception and stacktrace System.out.println(ex); ex.printStackTrace();
Печать Stacktrace следует использовать экономно; см. Pitfall - чрезмерные или неуместные стеки . Часто лучше использовать фреймворк протоколирования и передавать объект исключения для регистрации.
Понимание stacktrace
Рассмотрим следующую простую программу, состоящую из двух классов в двух файлах. (Мы показали имена файлов и добавили номера строк для иллюстрации.)
File: "Main.java"
1 public class Main {
2 public static void main(String[] args) {
3 new Test().foo();
4 }
5 }
File: "Test.java"
1 class Test {
2 public void foo() {
3 bar();
4 }
5
6 public int bar() {
7 int a = 1;
8 int b = 0;
9 return a / b;
10 }
Когда эти файлы будут скомпилированы и запущены, мы получим следующий результат.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)
Прочитайте эту строку за раз, чтобы понять, что она нам говорит.
Строка №1 говорит нам, что поток, называемый «main», завершился из-за неперехваченного исключения. Полное имя исключения - java.lang.ArithmeticException
, а сообщение об ошибке - «/ нолем».
Если мы рассмотрим javadocs для этого исключения, в нем говорится:
Брошено, когда произошло исключительное арифметическое условие. Например, целое число «делить на ноль» выдает экземпляр этого класса.
В самом деле, сообщение «/ by zero» является сильным намеком на то, что причиной исключения является то, что какой-то код попытался что-то делить на ноль. Но что?
Остальные 3 строки - это трассировка стека. Каждая строка представляет вызов метода (или конструктора) в стеке вызовов, и каждый из них сообщает нам три вещи:
- имя выполняемого класса и метода,
- имя файла исходного кода,
- номер строки исходного кода выполняемого оператора
Эти строки stacktrace перечислены с фреймом для текущего вызова вверху. Верхний кадр в нашем примере выше находится в методе Test.bar
и в строке 9 файла Test.java. Это следующая строка:
return a / b;
Если мы посмотрим пару строк ранее в файле, где b
инициализируется, очевидно, что b
будет иметь нулевое значение. Мы можем без всякого сомнения сказать, что это причина исключения.
Если нам нужно идти дальше, мы можем видеть из stacktrace, что bar()
вызывается из foo()
в строке 3 Test.java и что foo()
в свою очередь, вызывался из Main.main()
.
Примечание. Названия классов и методов в кадрах стека являются внутренними именами для классов и методов. Вам нужно будет признать следующие необычные случаи:
- Вложенный или внутренний класс будет выглядеть как «OuterClass $ InnerClass».
- Анонимный внутренний класс будет выглядеть как «OuterClass $ 1», «OuterClass $ 2» и т. Д.
- Когда выполняется код в конструкторе, инициализатор поля экземпляра или блок инициализатора экземпляра, имя метода будет «".
- Когда выполняется код в инициализаторе статического поля или статическом инициализаторе, имя метода будет «".
(В некоторых версиях Java код форматирования stacktrace будет обнаруживать и повторять повторяющиеся последовательности стека, как это может произойти, когда приложение выходит из строя из-за чрезмерной рекурсии.)
Цепочка исключений и вложенные стеки
Цепочка исключений происходит, когда кусок кода ловит исключение, а затем создает и генерирует новый, передавая первое исключение в качестве причины. Вот пример:
File: Test,java
1 public class Test {
2 int foo() {
3 return 0 / 0;
4 }
5
6 public Test() {
7 try {
8 foo();
9 } catch (ArithmeticException ex) {
10 throw new RuntimeException("A bad thing happened", ex);
11 }
12 }
13
14 public static void main(String[] args) {
15 new Test();
16 }
17 }
Когда вышеуказанный класс скомпилирован и запущен, мы получаем следующую стек:
Exception in thread "main" java.lang.RuntimeException: A bad thing happened
at Test.<init>(Test.java:10)
at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at Test.foo(Test.java:3)
at Test.<init>(Test.java:8)
... 1 more
Стек strace начинается с имени класса, метода и стека вызовов для исключения, которое (в данном случае) вызвало сбой приложения. За ним следует строка «Caused by:», которая сообщает об исключении cause
. Сообщается имя класса и сообщение, за которым следуют стеки кадров исключения. Трассировка заканчивается символом «... N больше», который указывает, что последние N кадров такие же, как и для предыдущего исключения.
«Причиненный:» включается только в вывод, если cause
первичного исключения не равна null
). Исключения могут быть цепочки неограниченно, и в этом случае stacktrace может иметь несколько следов «Caused by:».
Примечание: механизм cause
был обнаружен только в Throwable
API в Java 1.4.0. До этого цепочка исключений должна была быть реализована приложением с использованием настраиваемого поля исключения для представления причины и пользовательского метода printStackTrace
.
Захват stacktrace как строки
Иногда приложение должно иметь возможность захватывать стек в качестве String
Java, чтобы его можно было использовать для других целей. Общий подход для этого заключается в создании временного OutputStream
или Writer
который записывает в буфер в памяти и передает его в printStackTrace(...)
.
Библиотеки Apache Commons и Guava предоставляют утилиты для захвата stacktrace как строки:
org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)
com.google.common.base.Throwables.getStackTraceAsString(Throwable)
Если вы не можете использовать сторонние библиотеки в базе кода, то выполните следующий метод:
/**
* Returns the string representation of the stack trace.
*
* @param throwable the throwable
* @return the string.
*/
public static String stackTraceToString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
Обратите внимание, что если вы намерены проанализировать stacktrace, проще использовать getStackTrace()
и getCause()
чем пытаться проанализировать stacktrace.
Обработка InterruptedException
InterruptedException
- запутанный зверь - он проявляется в кажущихся безобидными методами, таких как Thread.sleep()
, но некорректная обработка его приводит к жесткому управлению кодом, который плохо работает в параллельных средах.
В самом Thread.interrupt()
случае, если InterruptedException
пойман, это означает, что кто-то, где-то, вызвал Thread.interrupt()
в потоке, в котором работает ваш код. Возможно, вы склонны сказать: «Это мой код! Я никогда его не прерываю! " и поэтому сделать что-то вроде этого:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
Но это точно неправильный способ справиться с «невозможным» событием. Если вы знаете, что ваше приложение никогда не столкнется с InterruptedException
вы должны рассматривать такое событие как серьезное нарушение предположений вашей программы и выходить как можно быстрее.
Правильный способ обработки «невозможного» прерывания выглядит так:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
Это делает две вещи; он сначала восстанавливает статус прерывания потока (как будто InterruptedException
не было выбрано в первую очередь), а затем оно выдает AssertionError
указывающее, что основные инварианты вашего приложения были нарушены. Если вы точно знаете, что никогда не будете прерывать поток, этот код работает в этом, это безопасно, поскольку блок catch
никогда не должен быть достигнут.
Использование класса Guava Uninterruptibles
помогает упростить этот шаблон; вызов Uninterruptibles.sleepUninterruptibly()
игнорирует прерванное состояние потока до истечения времени ожидания (после чего он восстанавливается для последующих вызовов, чтобы проверить и выбросить собственное InterruptedException
). Если вы знаете, что никогда не будете прерывать такой код, это безопасно избегает необходимости обертывания вызовов сна в блоке try-catch.
Чаще всего, однако, вы не можете гарантировать, что ваш поток никогда не будет прерван. В частности, если вы пишете код, который будет исполнен Executor
или каким-либо другим управлением потоками, важно, чтобы ваш код быстро реагировал на прерывания, иначе ваше приложение остановится или даже закроется.
В таких случаях лучше всего разрешить InterruptedException
распространять стек вызовов, добавляя к каждому методу throws InterruptedException
. Это может показаться kludgy, но это на самом деле желательное свойство - подписи вашего метода теперь указывают абонентам, что он будет оперативно реагировать на прерывания.
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
В ограниченных случаях (например, при переопределении метода, который не throw
никаких проверенных исключений) вы можете сбросить прерванный статус без повышения исключения, ожидая, что какой-либо код будет выполнен рядом с обработкой прерывания. Это задерживает обработку прерывания, но не полностью подавляет его.
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
Иерархия исключений Java - неконтролируемые и проверенные исключения
Все исключения Java являются экземплярами классов в иерархии классов Exception. Это можно представить следующим образом:
-
java.lang.Throwable
- это базовый класс для всех классов исключений. Его методы и конструкторы реализуют целый ряд функций, общих для всех исключений.-
java.lang.Exception
- это суперкласс всех нормальных исключений.- различные стандартные и настраиваемые классы исключений.
-
java.lang.RuntimeException
- это суперкласс всех нормальных исключений, которые являются неконтролируемыми исключениями .- различные стандартные и настраиваемые классы исключений во время выполнения.
-
java.lang.Error
- это суперкласс всех исключений «фатальной ошибки».
-
Заметки:
- Различие между проверенными и непроверенными исключениями описано ниже.
- Класс
Throwable
,Exception
иRuntimeException
следует рассматривать какabstract
; см. Pitfall - Throwing Throwable, Exception, Error или RuntimeException . - Исключения
Error
выбрасываются JVM в ситуациях, когда было бы опасно или неразумно для приложения пытаться восстановить. - Было бы неразумно объявлять пользовательские подтипы
Throwable
. Java-инструменты и библиотеки могут предполагать, чтоError
иException
являются единственными прямыми подтипамиThrowable
и неверноThrowable
, если это предположение неверно.
Проверено против исключенных исключений
Одна из критических замечаний в отношении поддержки исключений на некоторых языках программирования заключается в том, что трудно узнать, какие исключения может дать данный метод или процедура. Учитывая, что необработанное исключение может привести к сбою программы, это может сделать исключения источником хрупкости.
Язык Java решает эту проблему с помощью механизма проверенных исключений. Во-первых, Java классифицирует исключения на две категории:
Проверенные исключения обычно представляют собой ожидаемые события, с которыми приложение должно иметь дело. Например,
IOException
и его подтипы представляют собой условия ошибки, которые могут возникать в операциях ввода-вывода. Например, файл открывается с ошибкой, потому что файл или каталог не существует, чтение и запись в сети происходит неудачно, поскольку сетевое соединение было повреждено и так далее.Исключенные исключения обычно представляют собой непредвиденные события, с которыми приложение не справляется. Как правило, это результат ошибки в приложении.
(В дальнейшем « throw
» ссылается на любое исключение, явно выраженное (с помощью оператора throw
) или неявно (в случае неудачной разыменования, типа cast и т. Д.). Аналогично, «распространяемый» относится к исключению, которое было выбрано в вложенный вызов и не попадает в этот вызов. Пример кода ниже иллюстрирует это.)
Вторая часть проверенного механизма исключений заключается в том, что существуют ограничения на методы, в которых может произойти проверочное исключение:
- Когда проверяемое исключение выбрано или распространено в методе, оно должно быть либо поймано методом, либо указано в предложении
throws
метода. (Значение примераthrows
описано в этом примере .) - Когда проверенное исключение генерируется или распространяется в блоке инициализатора, оно должно быть уловлено блоком.
- Проверенное исключение не может быть передано вызовом метода в выражении инициализации поля. (Невозможно поймать такое исключение).
Короче говоря, проверенное исключение должно быть обработано или объявлено.
Эти ограничения не применяются к исключенным исключениям. Сюда относятся все случаи, когда исключение выбрано неявно, поскольку все такие случаи вызывают неконтролируемые исключения.
Проверенные примеры исключений
Эти фрагменты кода предназначены для иллюстрации проверенных ограничений исключений. В каждом случае мы показываем версию кода с ошибкой компиляции, а вторую версию с исправленной ошибкой.
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
В первом примере показано, как явно заброшенные проверенные исключения могут быть объявлены как «брошенные», если они не должны обрабатываться в методе.
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
Во втором примере показано, как можно обработать распространенное проверенное исключение.
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
В последнем примере показано, как обрабатывать проверенное исключение в инициализаторе статического поля.
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
Обратите внимание, что в этом последнем случае нам также приходится иметь дело с проблемами, которые is
могут быть назначены более одного раза, и при этом также необходимо назначать даже в случае исключения.
Вступление
Исключения - это ошибки, возникающие при выполнении программы. Рассмотрим программу Java, ниже которой делятся два целых числа.
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
Теперь мы компилируем и выполняем приведенный выше код и видим результат для попытки деления на ноль:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
Деление на ноль - это недопустимая операция, которая приведет к значению, которое не может быть представлено как целое число. Java справляется с этим, вызывая исключение . В этом случае исключение является экземпляром класса ArithmeticException .
Примечание . Пример создания и чтения стеков стека объясняет, что означает результат после двух чисел.
Утилита исключения - это контроль потока, который он позволяет. Без использования исключений типичным решением этой проблемы может быть, прежде всего, проверить, если b == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
Это выводит сообщение. You cannot divide by zero.
на консоль и изящно выходит из программы, когда пользователь пытается делить на ноль. Эквивалентным способом справиться с этой проблемой с помощью обработки исключений будет замена управления if
с помощью блока try-catch
:
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
Блок catch try выполняется следующим образом:
- Начните выполнение кода в блоке
try
. - Если в блоке try возникает исключение , немедленно прервите его и проверьте, не обнаружено ли это исключение блоком
catch
(в этом случае, когда Exception является экземпляром исключенияArithmeticException
). - Если исключение поймано , оно назначается переменной
e
и выполняется блокcatch
. - Если завершен блок
try
илиcatch
(т.е. во время выполнения кода не выполняются неперехваченные исключения), продолжайте выполнение кода ниже блокаtry-catch
.
Обычно считается хорошей практикой использовать обработку исключений как часть нормального управления потоком приложения, где поведение в противном случае было бы неопределенным или неожиданным. Например, вместо того, чтобы возвращать значение null
когда метод терпит неудачу, обычно лучше использовать исключение, чтобы приложение, использующее метод, могло определить свой собственный контроль потока для ситуации посредством обработки исключений вида, показанного выше. В некотором смысле это оборачивается проблемой возврата определенного типа , поскольку любой из нескольких видов исключений может быть брошен, чтобы указать конкретную проблему, которая произошла.
Дополнительные советы о том, как и как не использовать исключения, см. В разделе «Явные ошибки» - использование исключений
Операторы возврата в блоке try catch
Хотя это плохая практика, в блок обработки исключений можно добавить несколько операторов return:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
Этот метод всегда будет возвращать 7, поскольку блок finally, связанный с блоком try / catch, выполняется прежде, чем что-либо будет возвращено. Теперь, как наконец, return 7;
, это значение заменяет значения возврата try / catch.
Если блок catch возвращает примитивное значение и это примитивное значение впоследствии изменяется в блоке finally, возвращается значение, возвращаемое в блоке catch, и изменения из блока finally будут проигнорированы.
В приведенном ниже примере будет напечатан «0», а не «1».
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
Расширенные возможности исключений
В этом примере описаны некоторые дополнительные функции и прецеденты для исключений.
Проверка программной таблицы программно
Основное использование стека стека исключений заключается в предоставлении информации об ошибке приложения и его контексте, чтобы программист мог диагностировать и исправлять проблему. Иногда его можно использовать для других вещей. Например, для класса SecurityManager
может потребоваться проверка стека вызовов, чтобы решить, следует ли доверять коду, вызывающему вызов.
Вы можете использовать исключения для проверки стека вызовов программно следующим образом:
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
Есть несколько важных предостережений по этому поводу:
Информация, доступная в
StackTraceElement
, ограничена. Существует больше информации, чем отображаетсяprintStackTrace
. (Значения локальных переменных в кадре недоступны.)В javadocs для
getStackTrace()
что JVM разрешено оставлять рамки:Некоторые виртуальные машины могут при некоторых обстоятельствах опустить один или несколько кадров стека из трассировки стека. В крайнем случае виртуальная машина, которая не имеет информации о трассировке стека, относящейся к этому методу, разрешает возвращать массив нулевой длины из этого метода.
Оптимизация конструкции исключения
Как упоминалось в другом месте, построение исключения является довольно дорогостоящим, поскольку оно влечет за собой захват и запись информации обо всех кадрах стека в текущем потоке. Иногда мы знаем, что эта информация никогда не будет использоваться для данного исключения; например, stacktrace никогда не будет напечатана. В этом случае существует трюк реализации, который мы можем использовать в пользовательском исключении, чтобы не захватывать информацию.
Информация о кадре стека, необходимая для stacktraces, записывается, когда конструкторы Throwable
называют метод Throwable.fillInStackTrace()
. Этот метод является public
, что означает, что подкласс может его переопределить. Хитрость заключается в том, чтобы переопределить метод, унаследованный от Throwable
тем, который ничего не делает; например
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
Проблема с этим подходом заключается в том, что исключение, которое переопределяет fillInStackTrace()
никогда не может захватить стек, и бесполезно в сценариях, где вам это нужно.
Стирание или замена стека
В некоторых ситуациях stacktrace для исключения, созданного обычным способом, содержит либо неверную информацию, либо информацию, которую разработчик не хочет показывать пользователю. Для этих сценариев Throwable.setStackTrace
может использоваться для замены массива объектов StackTraceElement
который содержит информацию.
Например, для удаления информации об стеках исключений можно использовать следующее:
exception.setStackTrace(new StackTraceElement[0]);
Подавленные исключения
Java 7 представила конструкцию try-with-resources и связанную с ней концепцию исключения исключений. Рассмотрим следующий фрагмент:
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
Когда выбрано исключение, try
вызовет функцию close()
на w
которая будет очищать любой буферный вывод, а затем закрыть FileWriter
. Но что произойдет, если IOException
будет IOException
при очистке вывода?
Случается, что любое исключение, которое бросается при очистке ресурса, подавляется . Исключение поймано и добавлено в список исключенных исключений первичного исключения. Затем try-with-resources продолжат очистку других ресурсов. Наконец, основное исключение будет восстановлено.
Аналогичная картина возникает, если исключение было выбрано во время инициализации ресурса или если блок try
завершился нормально. Исключение составляет первое исключение, которое является основным исключением, а последующие, возникающие из-за очистки, подавляются.
Подавленные исключения могут быть извлечены из основного объекта исключения, вызвав getSuppressedExceptions
.
Утверждения try-finally и try-catch-finally
Команда try...catch...finally
объединяет обработку исключений с кодом очистки. Блок finally
содержит код, который будет выполняться при любых обстоятельствах. Это делает их подходящими для управления ресурсами и других видов очистки.
Примерка наконец
Вот пример более простой ( try...finally
):
try {
doSomething();
} finally {
cleanUp();
}
Поведение try...finally
выглядит следующим образом:
- Выполняется код в блоке
try
. - Если исключение не было выбрано в блоке
try
:- Выполняется код в блоке
finally
. - Если блок
finally
генерирует исключение, это исключение распространяется. - В противном случае управление переходит к следующему утверждению после
try...finally
.
- Выполняется код в блоке
- Если в блоке try было выбрано исключение:
- Выполняется код в блоке
finally
. - Если блок
finally
генерирует исключение, это исключение распространяется. - В противном случае исходное исключение продолжает распространяться.
- Выполняется код в блоке
Код внутри блока finally
всегда будет выполнен. (Единственные исключения - если вызывается System.exit(int)
или паника JVM.) Таким образом, finally
блок - это правильный код места, который всегда необходимо выполнить; например, закрытие файлов и других ресурсов или освобождение блокировок.
попробуй поймать, наконец,
Наш второй пример показывает, как catch
и, finally
можно использовать вместе. Это также иллюстрирует, что очистка ресурсов не является простой.
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
Полный набор (гипотетических) поведений try...catch...finally
в этом примере слишком сложно описать здесь. Простая версия заключается в том, что код в блоке finally
всегда будет выполняться.
Рассматривая это с точки зрения управления ресурсами:
- Мы объявляем «ресурс» (т.
reader
Переменнуюreader
) перед блокомtry
чтобы он был доступен для блокаfinally
. - Помещая
new FileReader(...)
,catch
может обрабатывать любое исключениеIOError
из броска при открытии файла. - Нам нужен
reader.close()
в блокеfinally
потому что есть некоторые пути исключения, которые мы не можем перехватывать ни в блокеtry
ни в блокеcatch
. - Однако, поскольку исключение могло быть выброшено до того, как был инициализирован
reader
, нам также понадобится явный критерийnull
. - Наконец,
reader.close()
может (гипотетически) вызвать исключение. Нас это не волнует, но если мы не поймаем исключение в источнике, нам нужно будет разобраться с ним в стеке вызовов.
Java 7 и более поздние версии предоставляют альтернативный синтаксис try-with-resources, который значительно упрощает очистку ресурсов.
Предложение 'throws' в объявлении метода
Механизм исключенных исключений Java требует, чтобы программист объявил, что определенные методы могут вызывать указанные проверенные исключения. Это делается с использованием предложения throws
. Например:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
throws OddNumberException
объявляют, что вызов checkEven
может генерировать исключение, которое имеет тип OddNumberException
.
Предложение throws
может объявлять список типов и может включать в себя неконтролируемые исключения, а также проверенные исключения.
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
В чем смысл объявления исключенных исключений?
Предложение throws
в объявлении метода служит для двух целей:
Он сообщает компилятору, какие исключения выбрасываются таким образом, чтобы компилятор мог сообщать об исключенных (отмеченных) исключениях как ошибки.
Это говорит программисту, который пишет код, который вызывает метод, какие исключения ожидать. Для этой цели часто возникает вопрос о включении исключенных исключений в список
throws
.
Обратите внимание: список throws
также используется инструментом javadoc при создании документации API, а также типичными подсказками метода типичного IDE.
Броски и метод переопределения
Предложение throws
является частью сигнатуры метода для переопределения метода. Метод переопределения может быть объявлен с тем же набором проверенных исключений, что и метод переопределения, или подмножество. Однако метод переопределения не может добавлять дополнительные проверенные исключения. Например:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
Причиной этого правила является то, что если метод overriden может выставить проверенное исключение, которое переопределенный метод не может выбрасывать, это приведет к разрыву подстановки.