Статьи

Учебник по обработке исключений Java с примерами и рекомендациями

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

  1. Обзор обработки исключений Java
  2. Ключевые слова для обработки исключений
  3. Иерархия исключений
  4. Полезные методы исключения
  5. Java 7 Автоматическое управление ресурсами и улучшения блока Catch
  6. Создание пользовательских классов исключений
  7. Рекомендации по обработке исключений

Обзор обработки исключений Java

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

Java, являющийся объектно-ориентированным языком программирования, всякий раз, когда возникает ошибка при выполнении оператора, создает объект исключения, а затем нормальный поток программы останавливается, и JRE пытается найти кого-то, кто может обработать возникшее исключение. Объект исключения содержит много отладочной информации, такой как иерархия методов, номер строки, где произошло исключение, тип исключения и т. Д. Когда исключение возникает в методе, вызывается процесс создания объекта исключения и его передачи в среду выполнения. «Бросить исключение» .

Как только среда выполнения получает объект исключения, он пытается найти обработчик для исключения. Обработчик исключений — это блок кода, который может обрабатывать объект исключения. Логика поиска обработчика исключений проста — начать поиск в методе, в котором произошла ошибка, если соответствующий обработчик не найден, затем перейти к методу вызывающего и т. Д. Таким образом, если стек вызова методов равен A-> B-> C и исключение возникает в методе C, то поиск соответствующего обработчика будет перемещен из C-> B-> A. Если соответствующий обработчик исключений найден, объект исключения передается обработчику для его обработки. Говорят, что обработчик «ловит исключение» . Если подходящий обработчик исключений не найден, программа прекращает печать информации об исключении.

Обратите внимание, что обработка исключений Java — это среда, используемая для обработки только ошибок времени выполнения, ошибки времени компиляции не обрабатываются средой обработки исключений.

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

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

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

  1. throw: мы знаем, что если возникает какое-либо исключение, создается объект исключения, а затем среда выполнения Java начинает обработку для их обработки. Иногда нам может понадобиться явно генерировать исключение в нашем коде, например, в программе аутентификации пользователя мы должны выдать исключение клиенту, если пароль нулевой. Ключевое слово throw используется для выброса исключения во время выполнения для его обработки.
  2. throws — Когда мы генерируем любое исключение в методе и не обрабатываем его, нам нужно использовать ключевое слово throws в сигнатуре метода, чтобы программа-вызывающая программа знала исключения, которые могут быть выброшены методом. Метод вызывающей стороны может обрабатывать эти исключения или распространять его на метод вызывающей стороны, используя ключевое слово throws. Мы можем предоставить несколько исключений в предложении throws, и его также можно использовать с методом main () .
  3. try-catch — мы используем блок try-catch для обработки исключений в нашем коде. try — начало блока, а catch — в конце блока try для обработки исключений. Мы можем иметь несколько блоков catch с блоком try и try-catch, который также может быть вложенным. Блок catch требует параметр, который должен иметь тип Exception.
  4. finally — блок finally не является обязательным и может использоваться только с блоком try-catch. Поскольку исключение останавливает процесс выполнения, у нас могут быть открытые ресурсы, которые не будут закрыты, поэтому мы можем использовать блок finally. Блок finally выполняется всегда, независимо от того, произошло исключение или нет.

Давайте посмотрим на простое программирование, показывающее обработку исключений в Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.journaldev.exceptions;
 
import java.io.FileNotFoundException;
import java.io.IOException;
 
public class ExceptionHandling {
 
    public static void main(String[] args) throws FileNotFoundException, IOException {
        try{
            testException(-5);
            testException(-10);
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            System.out.println("Releasing resources");         
        }
        testException(15);
    }
 
    public static void testException(int i) throws FileNotFoundException, IOException{
        if(i < 0){
            FileNotFoundException myException = new FileNotFoundException("Negative Integer "+i);
            throw myException;
        }else if(i > 10){
            throw new IOException("Only supported for index 0 to 10");
        }
 
    }
 
}

Вывод вышеуказанной программы:

1
2
3
4
5
6
7
java.io.FileNotFoundException: Negative Integer -5
    at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
    at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
    at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
    at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)

Обратите внимание, что метод testException () вызывает исключение с помощью ключевого слова throw, а сигнатура метода использует ключевое слово throws, чтобы сообщить вызывающему типу исключений, которые он может выдать. В методе main () я обрабатываю исключение, используя блок try-catch в методе main (), и когда я не обрабатываю его, я распространяю его во время выполнения с помощью оператора throws в методе main. Обратите внимание, что testException(-10) никогда не выполняется из-за исключительной testException(-10) а затем выполняется блок finally после выполнения блока try-catch. PrintStackTrace () является одним из полезных методов в классе Exception и используется для целей отладки.

  • У нас не может быть оператора catch или finally без оператора try.
  • Оператор try должен иметь либо блок catch, либо блок finally, он может иметь оба блока.
  • Мы не можем написать код между блоком try-catch-finally.
  • Мы можем иметь несколько блоков catch с одним оператором try.
  • Блоки try-catch могут быть вложены аналогично операторам if-else.
  • У нас может быть только один блок finally с оператором try-catch.

Иерархия исключений

Как указывалось ранее, при возникновении любого исключения создается объект исключения . Исключения Java являются иерархическими, а наследование используется для классификации различных типов исключений. Throwable является родительским классом иерархии исключений Java и имеет два дочерних объекта — Error и Exception. Исключения далее делятся на проверенные исключения и исключения во время выполнения.

  1. Ошибки : ошибки — это исключительные сценарии, выходящие за пределы области применения, и их невозможно предвидеть и восстанавливать, например, сбой оборудования, сбой JVM или ошибка нехватки памяти. Вот почему у нас есть отдельная иерархия ошибок, и мы не должны пытаться справляться с этими ситуациями. Некоторые из распространенных ошибок: OutOfMemoryError и StackOverflowError.
  2. Проверенные исключения : отмеченные исключения — это исключительные сценарии, которые мы можем ожидать в программе и пытаться восстановить из нее, например FileNotFoundException. Мы должны перехватить это исключение и предоставить пользователю полезное сообщение и правильно зарегистрировать его для целей отладки. Исключение является родительским классом для всех проверенных исключений, и если мы генерируем проверенное исключение, мы должны перехватить его тем же методом или передать его вызывающей стороне с помощью ключевого слова throws.
  3. Исключение времени выполнения : исключения времени выполнения вызваны плохим программированием, например, попыткой получить элемент из массива. Прежде чем пытаться получить элемент, мы должны проверить длину массива, иначе он может ArrayIndexOutOfBoundException во время выполнения. RuntimeException является родительским классом всех исключений времени выполнения. Если мы генерируем какое-либо исключение времени выполнения в методе, нет необходимости указывать их в предложении throws сигнатуры метода. Исключений во время выполнения можно избежать с помощью лучшего программирования.

Исключение-иерархия

Полезные методы исключения

Исключение и все его подклассы не предоставляют никаких конкретных методов, и все методы определены в базовом классе Throwable. Классы исключений создаются для определения различных типов сценариев исключений, чтобы мы могли легко определить основную причину и обработать исключение в соответствии с его типом. Throwable класс реализует интерфейс Serializable для взаимодействия.

Вот некоторые из полезных методов класса Throwable;

  1. public String getMessage () — Этот метод возвращает сообщение String of Throwable, и сообщение может быть предоставлено при создании исключения через его конструктор.
  2. public String getLocalizedMessage () — Этот метод предоставлен, чтобы подклассы могли переопределить его, чтобы предоставить специфическое для локали сообщение вызывающей программе. Реализация класса Throwable этого метода просто использует метод getMessage() для возврата сообщения об исключении.
  3. public synchronized Throwable getCause () — Этот метод возвращает причину исключения или нулевой идентификатор, причина неизвестна.
  4. public String toString () — Этот метод возвращает информацию о Throwable в формате String, возвращаемая строка содержит имя класса Throwable и локализованное сообщение.
  5. public void printStackTrace () — этот метод выводит информацию трассировки стека в стандартный поток ошибок, этот метод перегружен, и мы можем передать PrintStream или PrintWriter в качестве аргумента для записи информации трассировки стека в файл или поток.

Java 7 Автоматическое управление ресурсами и улучшения блока Catch

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

1
2
3
4
catch(IOException | SQLException | Exception ex){
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

Существуют некоторые ограничения, такие как объект исключения является окончательным, и мы не можем изменить его внутри блока catch, читайте полный анализ в Java 7 Catch Block Improvements .

В большинстве случаев мы используем блок finally только для того, чтобы закрыть ресурсы, и иногда мы забываем закрыть их и получить исключения времени выполнения, когда ресурсы исчерпаны. Эти исключения трудно отладить, и нам, возможно, придется изучить каждое место, где мы используем этот тип ресурса, чтобы убедиться, что мы его закрываем. Так, в Java 7 одним из улучшений стала попытка с ресурсами, где мы можем создать ресурс в самом операторе try и использовать его внутри блока try-catch. Когда выполнение выходит из блока try-catch, среда выполнения автоматически закрывает эти ресурсы. Пример блока try-catch с этим улучшением:

1
2
3
4
5
try (MyResource mr = new MyResource()) {
            System.out.println("MyResource created in try-with-resources");
        } catch (Exception e) {
            e.printStackTrace();
        }

Прочитайте подробное объяснение этой функции в Java 7 Automatic Resource Management .

Создание пользовательских классов исключений

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

Вот пример пользовательского класса исключений и демонстрация его использования.

MyException.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.journaldev.exceptions;
 
public class MyException extends Exception {
 
    private static final long serialVersionUID = 4664456874499611218L;
 
    private String errorCode="Unknown_Exception";
 
    public MyException(String message, String errorCode){
        super(message);
        this.errorCode=errorCode;
    }
 
    public String getErrorCode(){
        return this.errorCode;
    }
 
}

CustomExceptionExample.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.journaldev.exceptions;
 
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
 
public class CustomExceptionExample {
 
    public static void main(String[] args) throws MyException {
        try {
            processFile("file.txt");
        } catch (MyException e) {
            processErrorCodes(e);
        }
 
    }
 
    private static void processErrorCodes(MyException e) throws MyException {
        switch(e.getErrorCode()){
        case "BAD_FILE_TYPE":
            System.out.println("Bad File Type, notify user");
            throw e;
        case "FILE_NOT_FOUND_EXCEPTION":
            System.out.println("File Not Found, notify user");
            throw e;
        case "FILE_CLOSE_EXCEPTION":
            System.out.println("File Close failed, just log it.");
            break;
        default:
            System.out.println("Unknown exception occured, lets log it for further debugging."+e.getMessage());
            e.printStackTrace();
        }
    }
 
    private static void processFile(String file) throws MyException {      
        InputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new MyException(e.getMessage(),"FILE_NOT_FOUND_EXCEPTION");
        }finally{
            try {
                if(fis !=null)fis.close();
            } catch (IOException e) {
                throw new MyException(e.getMessage(),"FILE_CLOSE_EXCEPTION");
            }
        }
    }
 
}

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

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

Рекомендации по обработке исключений

  1. Использовать конкретные исключения — Базовые классы иерархии исключений не предоставляют никакой полезной информации, поэтому в Java есть так много классов исключений, таких как IOException, с дополнительными подклассами, такими как FileNotFoundException, EOFException и т. Д. Мы всегда должны бросать и перехватывать определенные классы исключений, чтобы этот вызывающий абонент легко узнает причину исключения и обработает ее. Это облегчает отладку и помогает клиентскому приложению правильно обрабатывать исключения.
  2. Бросить рано или быстро потерпеть неудачу — мы должны попытаться выбросить исключения как можно раньше. Рассмотрим выше метод processFile (), если мы передадим этому методу пустой аргумент, мы получим следующее исключение.
    1
    2
    3
    4
    5
    Exception in thread "main" java.lang.NullPointerException
        at java.io.FileInputStream.<init>(FileInputStream.java:134)
        at java.io.FileInputStream.<init>(FileInputStream.java:97)
        at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
        at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

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

    1
    2
    3
    4
    private static void processFile(String file) throws MyException {
            if(file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
    //further processing
    }

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

    1
    2
    3
    com.journaldev.exceptions.MyException: File name can't be null
        at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
        at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
  3. Catch Late — поскольку java принудительно обрабатывает проверенное исключение или объявляет его в сигнатуре метода, иногда разработчики склонны перехватывать исключение и регистрировать ошибку. Но эта практика вредна, потому что вызывающая программа не получает никаких уведомлений об исключении. Мы должны ловить исключения только тогда, когда мы можем обработать их соответствующим образом. Например, в приведенном выше методе я добавляю исключение обратно в метод вызывающего для его обработки. Этот же метод может использоваться другими приложениями, которые могут захотеть обработать исключение другим способом. При реализации какой-либо функции мы всегда должны отправлять исключения вызывающей стороне и позволять им решать, как ее обрабатывать.
  4. Закрытие ресурсов — Поскольку исключения останавливают обработку программы, мы должны закрыть все ресурсы в блоке finally или использовать усовершенствование Java 7 try-with-resources, чтобы позволить среде исполнения java закрыть его для вас.
  5. Регистрация исключений — мы всегда должны регистрировать сообщения об исключениях и при выдаче исключения предоставлять четкое сообщение, чтобы вызывающий мог легко знать, почему возникло исключение. Мы всегда должны избегать пустого блока catch, который просто использует исключение и не предоставляет каких-либо значимых деталей исключения для отладки.
  6. Один блок перехвата для нескольких исключений. В большинстве случаев мы регистрируем детали исключений и отправляем сообщение пользователю, в этом случае мы должны использовать функцию java 7 для обработки нескольких исключений в одном блоке перехвата. Такой подход уменьшит размер нашего кода и будет выглядеть чище.
  7. Использование пользовательских исключений — всегда лучше определить стратегию обработки исключений во время разработки, и вместо того, чтобы генерировать и перехватывать несколько исключений, мы можем создать настраиваемое исключение с кодом ошибки, и программа-вызывающая программа может обработать эти коды ошибок. Также неплохо создать вспомогательный метод для обработки различных кодов ошибок и их использования.
  8. Соглашения об именах и упаковка — Когда вы создаете свое собственное исключение, убедитесь, что оно оканчивается на Exception, чтобы из самого имени было ясно, что это исключение. Также убедитесь, что они упакованы, как в JDK , например, IOException является базовым исключением для всех операций ввода-вывода.
  9. Правильно используйте исключения — исключения являются дорогостоящими, и иногда вообще не требуется генерировать исключения, и мы можем вернуть логическую переменную в программу-вызывающую программу, чтобы указать, была ли операция успешной или нет. Это полезно, когда операция необязательна, и вы не хотите, чтобы ваша программа зависала из-за сбоя. Например, при обновлении биржевых котировок в базе данных из стороннего веб-сервиса мы можем избежать исключения в случае сбоя соединения.
  10. Документирование брошенных исключений. Используйте javadoc @throws чтобы четко указать исключения, создаваемые методом, это очень полезно, когда вы предоставляете интерфейс для использования другими приложениями.

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