1. Исключения

Исключения в Java-программах

Наконец-то программисты додумались регламентировать и автоматизировать обработку ошибок. Произошло это когда изобрели исключения. Сейчас с помощью механизма исключений в мире обрабатывается 80% внештатных ситуаций.

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

Когда в Java-программе возникает ошибка, например, деление на 0, происходят такие замечательные вещи:

Шаг первый

Создается специальный объект-исключение, в котором содержится информация о произошедшей ошибке.

Все в Java является объектом, и исключения — не исключение 🙂 Объекты-исключения имеют свои классы, и все их отличие от обычных классов в том, что они унаследованы от класса Throwable.

Шаг второй

Объект-исключение «выбрасывается». Не сильно удачное название. «Выбрасывание исключения» по своей сути больше похоже на включение пожарной сигнализации или оповещение «боевая тревога».

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

Шаг третий

Текущий метод, в котором возникло (выброшено) исключение, немедленно завершается. Исключение передается в вызывающий метод, который тоже немедленно завершается. И так по цепочке, пока не завершится метод main. Вместе с завершением метода main завершается и программа.

Пример:

Код Вывод на экран
class Solution
{
   public static void main(String[] args)
   {
      System.out.println("Внимание! Подготовка к концу света");
      конецСвета();
      System.out.println("Конец света успешно завершён");
   }

   public static void конецСвета()
   {
      System.out.println("Делаем что-то важное");
      поделим(0);
      System.out.println("Все отлично работает");
   }

   public static void поделим(int n)
   {
      System.out.println("Ничего страшного не произойдет: " + n);
      System.out.println(2 / n);
      System.out.println("Ничего страшного не произошло: " + n);
   }
}
Внимание! Подготовка к концу света
Делаем что-то важное
Ничего страшного не произойдет: 0

В 20-й строке возникла ошибка — деление на 0. Java-машина тут же создала исключение — объект класса ArithmeticException и «выбросила» его в систему.

Метод поделим() сразу завершился, поэтому на экран не вывелась строка Ничего страшного не произошло: 0. Программа вернулась в метод конецСвета(), и ситуация повторилась: в системе есть исключение, а значит, метод конецСвета() тоже аварийно завершается. Затем завершается метод main, и выполнение программы прекращается.

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


2. Перехват исключений try-catch

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

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

Эта конструкция называется блок try-catch.

Код, в котором могут возникнуть исключения, оборачивается в фигурные скобки, перед которыми пишется слово try (пытаться).

После фигурных скобок пишется ключевое слово catch, внутри круглых скобок объявляется переменная типа-исключения. Затем следуют фигурные скобки, внутри которых пишется код, который нужно выполнить, если возникло исключение указанного типа.

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

Пример:

Код Вывод на экран
class Solution
{
   public static void main(String[] args)
   {
      System.out.println("Адронный Коллайдер запущен");

      try
      {
         запуститьАдронныйКоллайдер(1);
         запуститьАдронныйКоллайдер(0);
      }
      catch(Exception e)
      {
         System.out.println("Ошибка! Перехвачено исключение");
         System.out.println("Планету засосало в черную дыру!");
      }

      System.out.println("Адронный Коллайдер остановлен");
   }

   public static void запуститьАдронныйКоллайдер(int n)
   {
      System.out.println("Все отлично работает: " + n);
      System.out.println(2/n);
      System.out.println("Никаких проблем нет: " + n);
   }
}
Адронный Коллайдер запущен
Все отлично работает: 1
Никаких проблем нет: 1
Все отлично работает: 0
Ошибка! Перехвачено исключение
Планету засосало в черную дыру!
Адронный Коллайдер остановлен


3. Несколько блоков catch

Несколько блоков catch

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

Разработчики Java решили помочь вам и позволили писать после блока try не один блок catch, а несколько.

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

Пример:

Код Вывод на экран
class Solution
{
   public static void main(String[] args)
   {
      System.out.println("Начало метода main");
      try
      {
         calculate(0);
      }
      catch(ArithmeticException e)
      {
         System.out.println("Было деление на 0");
      }
      catch(Exception e)
      {
         System.out.println("Перехвачено какое-то исключение");
      }

      System.out.println("Конец метода main");
   }

   public static void calculate(int n)
   {
      System.out.println("calculate начало: " + n);
      System.out.println(2/n);
      System.out.println("calculate конец: " + n);
   }
}
Начало метода main
calculate начало: 0
Было деление на 0
Конец метода main


4. Порядок блоков catch

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

Однако порядок блоков имеет значение.

Может быть ситуация, когда исключение захвачено несколькими блоками. В этом случае оно будет захвачено блоком catch, который идет раньше (ближе к блоку try).

Как же может возникнуть ситуация, что одно исключение могут захватить несколько блоков catch?

Все исключения объединены в единую иерархию с помощью наследования — см. схему.

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

Объект-исключение типа ArithmeticException может быть присвоен переменной типа ArithmeticException, а также переменным его классов-предков: RuntimeException, Exception и Throwable — см. схему.

Подробнее о наследовании и классах-предках мы поговорим на 21 уровне.

Вот этот код будет отлично компилироваться:

Преимущества наследования:
ArithmeticException ae    = new ArithmeticException();
RuntimeException runtime  = new ArithmeticException();
Exception exception       = new ArithmeticException();
Throwable trwbl           = new ArithmeticException();

Поэтому и перехватить исключение типа ArithmeticException можно блоками catch любым из 4-х приведенных выше типов.

Пример 1:

Код Вывод на экран
class Solution
{
   public static void main(String[] args)
   {
      System.out.println("Начало метода main");
      try
      {
         calculate(0);
      }
      catch(ArithmeticException e)
      {
         System.out.println("Было деление на 0");
      }
      catch(Exception e)
      {
         System.out.println("Перехвачено какое-то исключение");
      }

      System.out.println("Конец метода main");
   }

   public static void calculate(int n)
   {
      System.out.println("calculate начало: " + n);
      System.out.println(2/n);
      System.out.println("calculate конец: " + n);
   }
}
Начало метода main
calculate начало: 0
Было деление на 0
Конец метода main

В данном примере исключение ArithmeticException может быть перехвачено и блоком catch(Exception e), и блоком catch(ArithmeticException e). Оно будет захвачено тем блоком, который идет ближе к блоку try — первым блоком catch.

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

Тип Throwable вообще способен перехватывать все возможные исключения в Java, если его разместить в первом блоке catch - код не скомпилируется, так как компилятор понимает, что в коде есть недосягаемые блоки кода.