Код
#База знаний

Исключения в Java. Часть 2

Учимся футболить исключения и исследуем их различия на практике.

Иллюстрация: Dana Moskvina / Skillbox Media

Из этой статьи вы узнаете:

  • как пробросить исключение наверх;
  • когда и зачем это делать;
  • о checked- и unchecked-исключениях;
  • о хоткеях, ускоряющих обработку исключений.

А сперва советуем перечитать первую часть.

Проброс исключений

Ранее мы рассказали, что все исключения в Java — это классы, наследники Throwable и Exception; мельком взглянули на класс Error, от которого наследуются ошибки уровня Java-машины.

Верхушка иерархии исключений Java. Схема: Мария Помазкина для Skillbox Media

Сегодня поговорим только про семейство Exception.

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

Напомню, что есть три способа обойтись с исключением:

  1. Ничего не делать.
  2. Обработать в конструкции try-catch.
  3. Пробросить наверх.

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

Наверх — это куда? Это выше по стеку вызовов. То есть если метод A вызвал метод B, метод B вызвал метод C, а методC пробросил исключение наверх, то оно всплывёт до метода B — и станет его проблемой.

Как пробросить исключение?

Проще простого.

Вернёмся к примеру из предыдущей статьи. Помните, там был метод hereWillBeTrouble, который мог вызвать исключение ArithmeticException?

Чтобы пробросить это исключение, достаточно добавить в сигнатуру метода hereWillBeTrouble ключевое слово throws, а после него — название класса пробрасываемого исключения.

Вот так:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws ArithmeticException {
    System.out.println("Всё, что было до...");
    int oops = 42 / 0;
    System.out.println("Всё, что будет после...");
}

Теперь метод hereWillBeTrouble объявляет всему миру, что может кинуть исключение ArithmeticException.

Посмотрим, что произойдёт, если он исполнит эту угрозу.

Изначально исключение возникнет напротив стрелки 1. Но throws передаст его в строку, на которую указывает стрелка 2.

Скриншот: Мария Помазкина для Skillbox Media

Исключение передалось как эстафетная палочка. И теперь источником исключения является не только строка int oops = 42 / 0, но и строка hereWillBeTrouble();.

Что нам дал проброс исключения?

  1. Так мы говорим разработчику, который вызывает метод hereWillBeTrouble, что в нём может возникнуть исключение типа ArithmeticException.
  2. В вызывающем метод hereWillBeTrouble коде это исключение можно обработать с помощью конструкции try-catch.
public static void main(String[] args) {
    try {
        hereWillBeTrouble();
    } catch (ArithmeticException ex) {
        System.out.println("Да, это случилось");
    }
}

private static void hereWillBeTrouble() throws ArithmeticException {
    System.out.println("Всё, что было до...");
    int oops = 42 / 0;
    System.out.println("Всё, что будет после...");
}

Вернёмся к первому блоку кода (в котором нет конструкции try-catch).

Вы наверняка заметили, что и без throws код работал точно так же. Стектрейс (разбирали его здесь) не изменился, исключение в итоге бросалось пользователю.

Но теперь разработчики, которые используют метод hereWillBeTrouble, предупреждены и, если нужно, обезопасят свой код с помощью try-catch.

И это всё, что дало слово throws?

В случае с непроверяемыми исключениями — да. Но вот в случае с проверяемыми… пристегнитесь, нас ждёт новый поворот в деле об исключениях!

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

Все исключения в Java делятся на две группы. Зовутся они checked и unchecked («проверяемые» и «непроверяемые»). Иногда их также называют «обрабатываемые» и «необрабатываемые».

Запомните! Проверяемые исключения обязательно нужно обрабатывать либо пробрасывать. Непроверяемые — по желанию.

Как понять, проверяемое исключение или нет?

Разработчики языка Java решили так: исключения, которые наследуются от RuntimeException, — непроверяемые, а все остальные — проверяемые.

Исследуем различия на практике

Раз ArithmeticException — непроверяемое исключение, то его можно обрабатывать, а можно и не обрабатывать. Мы обрабатывали.

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

Без проблем компилируется и код, в котором кидают исключение явно:

private static void hereWillBeTrouble(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("Ты опять делишь на ноль?");
    }
    int oops = a / b;
    System.out.println(oops);
}

Компиляция успешна только потому, что исключение непроверяемое.

А теперь проделаем то же самое с проверяемым исключением:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() {
    throw new CloneNotSupportedException();
}

Вжух — и наш код перестал компилироваться!

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

Итак, вариантов у нас два:

  • обработать исключение с помощью try-catch;
  • пробросить исключение выше.

Оборачивать код конструкцией try-catch вы уже умеете. Рассмотрим второй вариант:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

А код всё ещё не компилируется. Но если в первом случае ошибка компиляции возникала в методе hereWillBeTrouble — в строке, где кидалось проверяемое, но при этом необработанное исключение, то теперь ошибка будет в методе main, потому что проверяемое и необработанное исключение поднялось до него.

Теперь исключение возникает в строке, где вызывается метод hereWillBeTrouble. Повторимся, так происходит потому, что этот метод кидает проверяемое исключение (он продекларировал это с помощью ключевого слова throws).

И теперь методу main тоже нужно что-то с исключением делать: либо обработать его с помощью try-catch, либо пробросить ещё выше.

Первый вариант, try-catch:

public static void main(String[] args) {
    try {
        hereWillBeTrouble();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}

Второй вариант, проброс выше:

public static void main(String[] args) throws CloneNotSupportedException {
    hereWillBeTrouble();
}

Несмотря на то что метод main запускается первым, ему тоже разрешено кидать исключения.

Что мы выяснили про проброс исключения наверх?

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

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

Компилятор и throws

Интересный момент. Если метод способен кинуть проверяемое исключение, то компилятор не будет перепроверять: ему всё равно, что происходит внутри этого метода. Для него главное, что метод о такой возможности заявил.

Убедитесь, что следующий код не скомпилируется:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws CloneNotSupportedException {
    System.out.println("Я не буду ничего кидать, не просите");
}

Горячие клавиши

Напоследок — небольшая подсказка.

Среда разработки стремится упростить нам жизнь: когда в коде обнаруживается проверяемое исключение — она предлагает реализовать его обработку.

Нам остаётся только выбрать, чего мы хотим: обработать исключение с помощью try-catch или пробросить выше.

Горячие клавиши, которые позволяют это сделать:

  • в среде IntelliJ IDEA — Alt + Enter / ⌥Enter,
  • для среды разработки Eclipse — F2.

Например:

IntelliJ IDEA:

Скриншот: Мария Помазкина для Skillbox Media

Eclipse:

Скриншот: Мария Помазкина для Skillbox Media

Что дальше?

В следующей статье я расскажу обо всей мощи конструкции try-catch.

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


Курс

ПрофессияJava-developer PRO

Вы с нуля освоите востребованный язык программирования, научитесь создавать качественные приложения под разные платформы и станете ценным Java-специалистом уровня middle.

Узнать про курс

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать
Обучение: ПрофессияJava-developer PRO Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована