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 — код не скомпілюється, оскільки компілятор розуміє, що в коді є недосяжні блоки.