User Professor Hans Noodles
Professor Hans Noodles
41 уровень

Исключения: checked, unchecked и свои собственные

Статья из группы Java Developer
Привет! В прошлой лекции мы познакомились с таким аспектом языка Java как исключения и увидели примеры работы с ними. Исключения: checked, unchecked и свои собственные - 1Сегодня мы глубже рассмотрим их структуру, а также научимся писать собственные исключения :)

Виды исключений

Как мы и говорили, классов-исключений в Java очень много, почти 400! Но все они делятся на группы, так что запомнить их довольно легко. Вот как это выглядит: Исключения: checked, unchecked и свои собственные - 2У всех исключений есть общий класс-предок Throwable. От него происходят две большие группы — исключения (Exception) и ошибки (Error). Error — это критическая ошибка во время исполнения программы, связанная с работой виртуальной машины Java. В большинстве случаев Error не нужно обрабатывать, поскольку она свидетельствует о каких-то серьезных недоработках в коде. Наиболее известные ошибки: StackOverflowError — возникает, например, когда метод бесконечно вызывает сам себя, и OutOfMemoryError — возникает, когда недостаточно памяти для создания новых объектов. Как видишь, в этих ситуациях чаще всего обрабатывать особо нечего — код просто неправильно написан и его нужно переделывать. Exceptions — это, собственно, исключения: исключительная, незапланированная ситуация, которая произошла при работе программы. Это не такие серьезные ошибки, как Error, но они требуют нашего внимания. Все исключения делятся на 2 вида — проверяемые (checked) и непроверяемые (unchecked). Исключения: checked, unchecked и свои собственные - 3Все проверяемые исключения происходят от класса Exception. Что значит “проверяемые”? Мы частично упоминали об этом в прошлой лекции: “...компилятор Java знает о самых распространенных исключениях, и знает, в каких ситуациях они могут возникнуть”. Например, он знает, что если программист в коде считывает данные из файла, может легко возникнуть ситуация, что файл не существует. И таких ситуаций, которые он может заранее предположить, очень много. Поэтому компилятор заранее проверяет наш код на наличие потенциальных исключений. Если он их найдет, то не скомпилирует код, пока мы не обработаем их или не пробросим наверх. Второй вид исключений — “непроверяемые”. Они происходят от класса RuntimeException. Чем же они отличаются от проверяемых? Казалось бы, тоже есть куча разных классов, которые происходят от RuntimeException и описывают конкретные runtime-исключения. Разница в том, что этих ошибок компилятор не ожидает. Он как бы говорит: “На момент написания кода я ничего подозрительного не обнаружил, но при его работе что-то пошло не так. Видимо, в коде есть ошибки!” И это действительно так. Непроверяемые исключения чаще всего являются следствием ошибок программиста. А компилятор явно не в силах предусмотреть все возможные неправильные ситуации, которые люди могут создать своими руками :) Поэтому он не будет проверять обработку таких исключений в нашем коде. Ты уже сталкивался с несколькими непроверяемыми исключениями:
  • ArithmeticException возникает при делении на ноль
  • ArrayIndexOutOfBoundsException возникает при попытке обратиться к ячейке за пределами массива.
Теоретически, конечно, создатели Java могли бы ввести обязательную обработку таких исключений, но во что бы тогда превратился код? При любой операции деления чисел пришлось бы писать try-catch для проверки — не на ноль ли ты случайно делишь? При любом обращении к массиву надо было бы писать try-catch чтобы проверить, не вышел ли ты за эти пределы. Любой написанный код представлял бы собой спагетти и был бы совершенно нечитаемым. Логично, что от этой идеи отказались. Поэтому непроверяемые исключения не нужно обрабатывать в блоках try-catch или пробрасывать наверх, хотя технически это возможно, как и с Error. Исключения: checked, unchecked и свои собственные - 4

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

Разумеется, создатели Java не в состоянии предусмотреть все исключительные ситуации, которые могут возникнуть в программах. Программ в мире слишком много, и они слишком разные. Но это не страшно, поскольку при необходимости ты можешь создавать собственные исключения. Это делается очень легко. Тебе достаточно создать собственный класс. Его название должно заканчиваться на “Exception”. Компилятору это не нужно, но читающим твой код программистам сразу будет понятно, что это класс-исключение. Кроме того, нужно указать что класс происходит от класса Exception. Это уже нужно для компилятора и корректной работы. Например, у нас есть класс Собака — Dog. Мы можем погулять с собакой с помощью метода walk(). Но перед этим нам нужно проверить, надеты ли на нашего питомца ошейник, поводок и намордник. Если что-то из этого отсутствует, выбросим собственное исключение DogIsNotReadyException. Его код будет выглядеть так:

public class DogIsNotReadyException extends Exception { 

   public DogIsNotReadyException(String message) { 
       super(message); 
   } 
} 
Чтобы указать, что класс является исключением, нужно написать extends Exception после имени класса: это значит, что “класс происходит от класса Exception”. В конструкторе мы просто вызовем конструктор класса Exception со строкой message — она будет отображать пользователю сообщение от системы с описанием возникшей ошибки. Вот как это будет выглядеть в коде нашего класса:

public class Dog { 

   String name;
   boolean isCollarPutOn; 
   boolean isLeashPutOn; 
   boolean isMuzzlePutOn; 

   public Dog(String name) { 
       this.name = name; 
   } 

   public static void main(String[] args) { 

   } 

   public void putCollar() { 

       System.out.println("Ошейник надет!"); 
       this.isCollarPutOn = true; 
   } 

   public void putLeash() { 

       System.out.println("Поводок надет!"); 
       this.isLeashPutOn = true; 
   } 

   public void putMuzzle() { 
       System.out.println("Намордник надет!"); 
       this.isMuzzlePutOn = true; 
   } 

   public void walk() throws DogIsNotReadyException { 

   System.out.println("Собираемся на прогулку!"); 
   if (isCollarPutOn && isLeashPutOn && isMuzzlePutOn) { 
       System.out.println("Ура, идем гулять! " + name + " очень рад!"); 
   } else { 
       throw new DogIsNotReadyException("Собака " + name + " не готова к прогулке! Проверьте экипировку!"); 
   } 
 } 

} 
Теперь наш метод walk() выбрасывает исключение DogIsNotReadyException. Это делается с помощью ключевого слова throw. Как мы уже говорили ранее, исключение — это объект. Поэтому в нашем методе при возникновении исключительной ситуации — на собаке чего-то не хватает — мы создаем новый объект класса DogIsNotReadyException и выбрасываем в программу с помощью слова throw. К сигнатуре метода walk() мы прибавляем throws DogIsNotReadyException. Иными словами, теперь компилятор в курсе, что вызов метода walk() может обернуться исключительной ситуацией. Поэтому, когда мы вызовем это где-то в программе, исключение нужно будет обработать. Попробуем сделать это в методе main():

public static void main(String[] args) { 

   Dog dog = new Dog("Мухтар"); 
   dog.putCollar(); 
   dog.putMuzzle(); 
   dog.walk();//Unhandled exception: DogIsNotReadyException 
} 
Не компилируется, исключение не обработано! Обернем наш код в блок try-catch для обработки исключения:

public static void main(String[] args) { 

   Dog dog = new Dog("Мухтар"); 
   dog.putCollar(); 
   dog.putMuzzle(); 
   try { 
       dog.walk();
   } catch (DogIsNotReadyException e) { 
       System.out.println(e.getMessage()); 
       System.out.println("Проверяем снаряжение! Ошейник надет? " + dog.isCollarPutOn + "\r\n Поводок надет? " 
       + dog.isLeashPutOn + "\r\n Намордник надет? " + dog.isMuzzlePutOn); 
   } 
} 
Теперь посмотрим на вывод в консоль:

Ошейник надет! 
Намордник надет! 
Собираемся на прогулку! 
Собака Мухтар не готова к прогулке! Проверьте экипировку! 
Проверяем снаряжение! Ошейник надет? true
Поводок надет? false 
Намордник надет? true
Посмотри, насколько более информативным стал вывод в консоль! Мы видим каждый шаг, который происходил в программе; видим, в каком месте возникла ошибка, и сразу замечаем, чего именно не хватает нашей собаке :) Вот так и создаются собственные исключения. Как видишь, ничего сложного. И хотя разработчики Java не удосужились добавить в свой язык специальное исключение для неправильно экипированных собак, мы исправили их оплошность :)
Комментарии (127)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Aleksei Reinsalu Уровень 18, Таллинн, Эстония
20 ноября 2021
Программа выполнила действие, попадающее под определение "исключение", в результате возникла ситуация под названием "исключение", из-за чего программа выводит в консоль строку под названием.. под названием.. неужели догадались?
Darya Hom Уровень 19, Москва
5 ноября 2021
Почему в этой классификации нет NullPointerException?
Lexink Уровень 15, Москва
18 мая 2021
Не до конца понятно, если создавать свое исключение, наследуемое от Exception, оно будет checked или unchecked? Судя по тому, что в примере используется throws - исключение checked. Как тогда создать unchecked, наследоваться от RuntimeException или можно от Exception тоже это сделать?
Zakir T Уровень 20
16 апреля 2021
спасибо.
FreyGreen Уровень 25, Нижний Новгород, Россия
4 апреля 2021
Как то туго идет
Dmitry Stupakov Уровень 17, Архангельск, Россия
29 марта 2021
Спасибо, отличная статья
Александр Евтеев Уровень 20, Москва, Россия
17 марта 2021
Долго гуглил иерархию всех 400 видов эксепшенов Оказалось, что все есть в оф. доке )) Наследники Exception, у каждого из наследников - список своих наследников (раздел Direct Known Subclasses): https://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html Все перечисленные наследники - checked, кроме одного - unchecked RuntimeException RuntimeException со своими наследниками, которые проверять не надо: https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimeException.html
Карбофос Огарин Уровень 14, Санкт-Петербург
7 марта 2021
Всё понял, кроме того, что

(DogIsNotReadyException e) {
       System.out.println(e.getMessage());
Откуда взялось getMessage и что оно передаёт?
Марат Уровень 14
28 февраля 2021
Спасибо, отлично написано.
Константин Уровень 40, Харьков, Украина
27 февраля 2021
Как пишут в других источниках: если есть возможность решить проблему без исключений (путем простых проверок входящей/исходящей информации например), то лучше обходиться без них. Так как они ресурсоемкие. И в данной статье пример использования собственного исключения как раз из той серии где на мой взгляд можно было бы обойтись из без исключения: сделать проверку в методе walk(). Явно нужна практика чтоб понять как и где их правильно использовать, которой у меня нет. И этот пример не особо продемонстрировал как правильно использовать исключения 😞