Урок 28. Исключения – Devcolibri

Урок 28. Исключения

После просмотра видеоверсии урока обязательно изучите текстовый материал. Он дополняет видеоматериал и позволит вам полностью понять тему урока.

Исключения

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

  • try
  • catch
  • finally
  • throw
  • throws

Любое исключение является классом, которое наследуется от класса Throwable. Диаграмма показана здесь: ExceptionsDiagram Все исключительные ситуации делятся на «проверяемые» (checked) и «непроверяемые» (unchecked). Это свойство присуще базовым классам (Throwable, Error, Exception, RuntimeException) и передается по наследству. Никак не видимо в исходном коде класса исключения. В этой части мы с вами будем работать с непроверяемыми исключениями. Давайте сразу рассмотрим пример:

public class Main {

    public static void main(String[] args) {
        int result = divide(10, 0);
        System.out.println(result);
        System.out.println("Program finished");
    }

    public static int divide(int a, int b) {
        return a / b;
    }
}

Видим простейший пример деления двух чисел. Результат выполнения:

5
Program finished

Теперь вызовем функцию divide(10, 0) и посмотрим, что выведется в консоль.

Exception in thread "main" java.lang.ArithmeticException: / by zero.

Обратите внимание, что сообщение Pogram finished не вывелось, т.к произошло исключение ArithmeticException. Если происходит исключение, и вы его не обрабатываете, то программа прекращает своё выполнение. Это и есть пример исключения в программе – деление на нуль. Чтобы в клиентском коде обработать это исключение, нам необходимо воспользоваться конструкцией try/catch.

Try/catch

public class Main {

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
        }

        System.out.println("Program finished");
    }

    public static int divide(int a, int b) {
        return a / b;
    }
}

Результат выполнения:

Error: / by zero
Program finished

Мы перехватили исключение ArithmeticException и выводим в консоль сообщение об ошибке, используя метод объекта исключения getMessage(). Вывелось сообщение Program finished. Если вы перехватили исключение и обработали его, то это значит, что программа продолжит своё выполнение.

Catch полиморфен

В блоке catch можно указывать не только конкретный тип исключения, но также и одного из родителей класса исключения. Класс ArithmeticException наследуется от класса RuntimeException. Поэтому можем в catch блоке заменить ArithmeticException на RuntimeException, и программа будет выполняться так же:

public class Main {

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println(result);
        } catch (RuntimeException e) {
            System.out.println("Error: " + e.getMessage());
        }

        System.out.println("Program finished");
    }

    public static int divide(int a, int b) {
        return a / b;
    }
}

Если вы ожидаете конкретный вид исключения, то указывайте его. Как ArithmeticException в данном случае. Это позволит вам точно знать, какой вид исключения вы ожидаете. Бывают ситуации, когда может возникнуть несколько исключений. В таком случае можно использовать несколько catch блоков. Давайте представим, что метод может выбросить ещё одно исключение – NullPointerException. Просто добавим ещё один catch блок, который будет отлавливать это исключение:

    public static void main(String[] args) {
        try {
            int result = divide(0, 0);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
        } catch (NullPointerException e) {
            System.out.println("NullPointer exception");
        }

        System.out.println("Program finished");
    }

Стоит обратить внимание, что если вы ожидаете два вида исключения и одно из них более общее (RuntimeException), а второе более конкретное (ArithmeticException), то всегда пишите catch с более конкретным исключением выше:

    public static void main(String[] args) {
        try {
            int result = divide(0, 0);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
        } catch (RuntimeException e) {
            System.out.println("NullPointer exception");
        }

        System.out.println("Program finished");
    }

Результат:

ArithmeticException
Program finished

Если поменять catch блоки местами, то увидим ошибку компиляции Error:(12, 9) java: exception java.lang.ArithmeticException has already been caught.

Объединение catch блоков

Начиная с java7 появилась возможность объединять catch блоки, если необходимо обрабатывать несколько видов исключений одинаково:

    public static void main(String[] args) {
        try {
            int result = divide(0, 0);
            System.out.println(result);
        } catch (ArithmeticException | NullPointerException e) {
            System.out.println("Error: " + e.getMessage());
        }

        System.out.println("Program finished");
    }

Finally блок

Finally блок выполняется после того, как выполнился try/catch блок. Обычно его используют для очистки ресурсов, закрытия соединений (после записи в файл, базу данных).

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException");
        } finally {
            System.out.println("Clean up resources");
        }
        System.out.println("Program finished");
    }

Результат:

ArithmeticException
Clean up resources
Program finished

Блок finally можно использовать даже без catch блока. На практике редко встречается, но ошибок компиляции не будет. Чтобы не было ошибки вызываем divide(10, 1):

    public static void main(String[] args) {
        try {
            int result = divide(10, 1);
            System.out.println(result);
        } finally {
            System.out.println("Clean up resources");
        }
        System.out.println("Program finished");
    }

Результат:

10
Clean up resources
Program finished

Проверяемые и непроверяемые исключения

Всё это время мы с вами работали с непроверяемыми (unchecked) исключениями. Ключевое отличие этих двух типов исключений: мы можем не обрабатывать непроверяемые исключения. Когда мы делили на 0, наш код компилировался без ошибок и выполнялся. Давайте немного изменим код метода divide:

    public static int divide(int a, int b) {
        if(b == 0) throw new Exception("Dividing by zero");

        return a / b;
    }

Исключение Exception является проверяемым, поэтому сейчас при попытке скомпилировать этот код, мы увидим ошибку: Error:(16, 20) java: unreported exception java.lang.Exception; must be caught or declared to be thrown. Существует два механизма работы с такими исключениями:

  • Обработать исключение в методе, в котором оно возникает, используя конструкцию try/catch.
  • Не обрабатывать исключение на этом уровне, а пробросить (throws) исключение на уровень выше.

В данном случае используем второй вариант:

    public static int divide(int a, int b) throws Exception {
        if(b == 0) throw new Exception("Dividing by zero");

        return a / b;
    }

При вызове любого метода, у которого в объявлении (сигнатуре) есть слово throws с проверяемыми исключенями, мы обязаны или обрабатывать исключение с помощью try/catch, или пробрасывать (throws) его на уровень выше. Теперь в методе main мы вначале обработаем это исключение:

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("Exception " + e.getMessage());
        }
        System.out.println("Program finished");
    }

Результат:

Exception Dividing by zero
Program finished

Или можем добавить throws к сигнатуре метода:

    public static void main(String[] args) throws Exception {
        int result = divide(10, 0);
        System.out.println(result);
        System.out.println("Program finished");
    }

Результат:

Exception in thread "main" java.lang.Exception: Dividing by zero

Т.к. метод main является точкой входа в программу, то никто выше это исключение обработать не может и оно выводится в консоль. Заключение:

  1. Любая программа — это последовательность действий, которая может разветвляться. Для веток, которые являются ненормальными (в случае невозможности достижения цели) используются исключения. При корректной работе программы их использовать не стоит.
  2. Исключения выбрасываются (throw) там, где выполнение зашло в тупик и нет корректных альтернатив. А ловятся там, где могут быть обработаны. Т.е. это как бы резкий перескок из одного слоя логики в другой
  3. Обрабатываемые или нет? Как выбирать: если в правильно написанной программе такой ситуации произойти не может, то кидаете необрабатываемое исключение. В остальных случаях — обрабатываемые.
Возникли проблемы при прохождении? Напишите нам в чат поддержки Вконтакте или Facebook. Мы поможем вам решить проблему и вы сможете продолжить обучение.
УВИДЕТЬ ВСЕ Добавить заметку
ВЫ
Добавить ваш комментарий