Поиск…


Вступление

Объекты типа 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

Начиная с 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

Java SE 7

Как иллюстрирует пример утверждения 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 .

Заметки:

  1. В стеке нет сведений о самом исключении. Вы можете использовать метод toString() для получения этих деталей; например

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. Печать 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 будет обнаруживать и повторять повторяющиеся последовательности стека, как это может произойти, когда приложение выходит из строя из-за чрезмерной рекурсии.)

Цепочка исключений и вложенные стеки

Java SE 1.4

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

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 - это суперкласс всех исключений «фатальной ошибки».

Заметки:

  1. Различие между проверенными и непроверенными исключениями описано ниже.
  2. Класс Throwable , Exception и RuntimeException следует рассматривать как abstract ; см. Pitfall - Throwing Throwable, Exception, Error или RuntimeException .
  3. Исключения Error выбрасываются JVM в ситуациях, когда было бы опасно или неразумно для приложения пытаться восстановить.
  4. Было бы неразумно объявлять пользовательские подтипы 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 выполняется следующим образом:

  1. Начните выполнение кода в блоке try .
  2. Если в блоке try возникает исключение , немедленно прервите его и проверьте, не обнаружено ли это исключение блоком catch (в этом случае, когда Exception является экземпляром исключения ArithmeticException ).
  3. Если исключение поймано , оно назначается переменной e и выполняется блок catch .
  4. Если завершен блок 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;
        }
    }
}

Расширенные возможности исключений

В этом примере описаны некоторые дополнительные функции и прецеденты для исключений.

Проверка программной таблицы программно

Java SE 1.4

Основное использование стека стека исключений заключается в предоставлении информации об ошибке приложения и его контексте, чтобы программист мог диагностировать и исправлять проблему. Иногда его можно использовать для других вещей. Например, для класса 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());

Есть несколько важных предостережений по этому поводу:

  1. Информация, доступная в StackTraceElement , ограничена. Существует больше информации, чем отображается printStackTrace . (Значения локальных переменных в кадре недоступны.)

  2. В javadocs для getStackTrace() что JVM разрешено оставлять рамки:

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

Оптимизация конструкции исключения

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

Информация о кадре стека, необходимая для stacktraces, записывается, когда конструкторы Throwable называют метод Throwable.fillInStackTrace() . Этот метод является public , что означает, что подкласс может его переопределить. Хитрость заключается в том, чтобы переопределить метод, унаследованный от Throwable тем, который ничего не делает; например

  public class MyException extends Exception {
      // constructors

      @Override 
      public void fillInStackTrace() {
          // do nothing
      }
  }

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

Стирание или замена стека

Java SE 1.4

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

Например, для удаления информации об стеках исключений можно использовать следующее:

 exception.setStackTrace(new StackTraceElement[0]);

Подавленные исключения

Java SE 7

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 SE 7

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 в объявлении метода служит для двух целей:

  1. Он сообщает компилятору, какие исключения выбрасываются таким образом, чтобы компилятор мог сообщать об исключенных (отмеченных) исключениях как ошибки.

  2. Это говорит программисту, который пишет код, который вызывает метод, какие исключения ожидать. Для этой цели часто возникает вопрос о включении исключенных исключений в список 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 может выставить проверенное исключение, которое переопределенный метод не может выбрасывать, это приведет к разрыву подстановки.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow