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

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

Все исключения делятся на 4 вида, которые на самом деле являются классами, унаследованными друг от друга.

Класс Throwable

Самым базовым классом для всех исключений является класс Throwable. В классе Throwable содержится код, который записывает текущий стек-трейс вызовов функций в массив. Что такое стек-трейс вызовов, мы изучим немного позднее.

В оператор throw можно передать только объект класса-наследника Throwable. И хотя теоретическим можно написать код вида throw new Throwable(); — так обычно никто не делает. Главная цель существования класса Throwable — единый класс-предок для всех исключений.

Класс Error

Следующим классом исключений является класс Error — прямой наследник класса Throwable. Объекты типа Error (и его классов-наследников) создает Java-машина в случае каких-то серьезных проблем. Например, сбой в работе, нехватка памяти, и т.д.

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

Класс Exception

Исключения типа ExceptionRuntimeException) — это обычные ошибки, которые возникают во время работы многих методов. Цель каждого выброшенного исключения — быть захваченным тем блоком catch, который знает, что нужно сделать в этой ситуации.

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

Другими словами, если какая-то переменная оказалась равна null, метод выкинет NullPointerException, если в метод передали неверные аргументы — выкинет InvalidArgumentException, если в методе случайно было деление на ноль — ArithmeticException.

Класс RuntimeException

RuntimeException — это разновидность (подмножество) исключений Exception. Можно даже сказать, что RuntimeException — это облегченная версия обычных исключений (Exception): на такие исключения налагается меньше требований и ограничений

Об отличии Exception и RuntimeException вы узнаете дальше


2. Проверяемые исключения: throws, checked exceptions

Проверяемые исключения: throws, checked exceptions

Все исключения в Java делятся на 2 категории — проверяемые (checked) и непроверяемые (unchecked).

Все исключения, унаследованные от классов RuntimeException и Error, считаются unchecked-исключениями, все остальные — checked-исключениями.

Важно!

Спустя 20 лет после введения проверяемых исключений, почти все Java-программисты считают это ошибкой. 95% всех исключений в популярных современных фреймворках — непроверяемые. Тот же язык C#, который чуть ли не скопировал Java подчистую, не стал добавлять checked-исключения.

В чем же основное отличие checked-исключений от unchecked?

К checked-исключениям есть дополнительные требования. Звучат они примерно так.

Требование 1

Если метод выбрасывает checked-исключение, он должен содержать тип этого исключения в своем заголовке (сигнатуре метода). Чтобы все методы, которые вызывают данный метод, знали о том, что в нем может возникнуть такое «важное исключение».

Указывать checked-исключения надо после параметров метода после ключевого слова throws (не путать со throw). Выглядит это примерно так:

тип метод (параметры) throws исключение

Пример:

checked-исключение unchecked-исключение
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n равно нулю!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n равно нулю!");
}

В примере справа наш код выкидывает unchecked-исключение — никаких дополнительных действий не нужно. В примере слева метод выкидывает checked-исключение, поэтому в сигнатуру метода добавили ключевое слово throws и указали тип исключения.

Если метод планирует выкидывать несколько checked-исключений, все их нужно указать после ключевого слова throws через запятую. Порядок неважен. Пример:

public void calculate(int n) throws Exception, IOException
{
   if (n == 0)
      throw new Exception("n равно нулю!");
   if (n == 1)
      throw new IOException("n равно единице");
}

Требование 2

Если вы вызываете метод, у которого в сигнатуре прописаны checked-исключения, то вы не можете проигнорировать этот факт.

Вы должны либо перехватить все эти исключения, добавив блоки catch для каждого из них, либо добавить их в throws своего метода.

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

Пример:

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

Создаем Землю Примечание
public void создатьМир(int n) throws ПустойМир,ОдинокийМир
{
   if (n == 0)
      throw new ПустойМир("Людей вообще нет!");
   if (n == 1)
      throw new ОдинокийМир("Слишком мало людей!");
   System.out.println("Создан прекрасный мир. Население: " + n);
}
Метод потенциально кидает два checked-исключения:

  • ПустойМир
  • ОдинокийМир

Вызов этого метода можно обработать 3 способами:

1. Не перехватываем возникающие исключения

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

Код Примечание
public void создатьНаселенныйМир(int population)
throws ПустойМир, ОдинокийМир
{
   создатьМир(population);
}
Вызывающий метод не перехватывает исключения и вынужден информировать о них других: добавить их себе в throws

2. Перехватывать часть исключений

Обрабатываем понятные ошибки, непонятные — прокидываем в вызывающий метод. Для этого нужно добавить их название в throws:

Код Примечание
public void создатьНепустойМир(int population)
throws ПустойМир
{
   try
   {
      создатьМир(population);
   }
   catch (ОдинокийМир e)
   {
      e.printStackTrace();
   }
}
Вызывающий метод перехватывает только одно checked-исключениеОдинокийМир, второе он должен добавить в свою сигнатуру: указать после слова throws

3. Перехватываем все исключения

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

Код Примечание
public void создатьЛюбойМир(int population)
{
   try
   {
      создатьМир(population);
   }
   catch(ОдинокийМир e)
   {
      e.printStackTrace();
   }
   catch(ПустойМир e)
   {
      e.printStackTrace();
   }
}
В этом методе перехватываются все ошибки. Вызывающий метод будет уверен, что все прошло отлично.

undefined
20
Задача
Java Syntax Pro, 20 уровень, 3 лекция
Недоступна
Считаем клетки
Перед тобой поле 10x10, в котором в случайном порядке расставлены клетки зеленого цвета, а в них — числа. Чтобы это увидеть, можешь запустить программу. Твоя задача — реализовать метод showResult(), который должен: 1. Подсчитать сумму всех чисел в зеленых клетках и вывести в консоли: Сумма всех ч
undefined
20
Задача
Java Syntax Pro, 20 уровень, 3 лекция
Недоступна
Цвета радуги
Поработаем с методом setCellColor(int, int, Color), который будет разукрашивать клетку по переданным координатам. Приступим к отрисовке радуги. Необходимо создать игровое поле размером 10x7 в методе initialize() и разукрасить каждую строку разными цветами, используя метод setCellColor(int, int, Colo

3. Множественный перехват исключений

Программисты очень не любят дублирование кода. Даже придумали такой принцип разработки — DRY: Don’t Repeat Yourself. Однако при обработке исключений часто возникают ситуации, когда после блока try следует несколько блоков catch с одинаковым кодом.

Или может быть, например, 3 catch-блока с одним кодом и еще 2 catch-блока с другим. Стандартная в общем-то ситуация, когда у вас в проекте ответственно относятся к обработке исключений.

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

try
{
   код, где может возникнуть ошибка
}
catch(ТипИсключения1 | ТипИсключения2 | ТипИсключения3 имя)
{
   код обработки исключений
}

Блоков catch может быть сколько угодно. Однако в одном блоке catch нельзя указать исключения, которые наследуются друг от друга. Т.е. нельзя написать catch (Exception | RuntimeException e), т.к. класс RuntimeException унаследован от Exception.


undefined
20
Задача
Java Syntax Pro, 20 уровень, 3 лекция
Недоступна
Диалоговые окна
Реализуем отображение диалогового окна с текстом. По нажатию левой кнопки мыши выводим текст "Вы выиграли" зеленым цветом, а по нажатию правой кнопки мыши - "Вы проиграли" красным цветом. Чтобы работать с нажатиями кнопок мыши, нужно переопределить методы onMouseLeftClick(int, int) и onMouseRightC