JavaRush /Курси /Java Syntax Zero /Типи виключень у Java

Типи виключень у Java

Java Syntax Zero
Рівень 15 , Лекція 4
Відкрита

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();
   }
}
У цьому методі перехоплюються всі помилки. Викликаючий метод буде впевнений, що все пройшло чудово.


3. Обгортання виключень

Checked-виключення здавалися класною штукою в теорії, але виявилися повним розчаруванням на практиці.

Припустімо, у вас в проєкті є суперпопулярний метод, який викликається з сотень місць програми. І ви вирішили додати в нього нове checked-виключення. І цілком може виявитися, що це checked-виключення дійсно настільки важливе та особливе, що тільки метод main() знає, що робити у разі перехоплення цього виключення.

Тому вам доведеться додати checked-виключення в throws усіх методів, які викликають ваш суперпопулярний метод. А також у throws усіх методів, які викликають ті методи. І в методи, які викликають ті методи.

У результаті у вас в throws у половини методів проєкту буде додано нове checked-виключення. А потім виявиться, що у вас проект покритий тестами, і тести не компілюються. І вам доведеться правити throws ще й у тестах.

А потім увесь ваш код (зміни в сотнях файлів) мають будуть рев'ювати інші програмісти. І тут ми задаємо собі питання: а заради чого ми вносили в проєкт купу змін? День(дні?) роботи, зламані тести, і все заради додавання одного checked-виключення?

А адже є ще проблеми з наслідуванням і перевизначенням методів. Проблем від checked-виключень значно більше, ніж користі. Зрештою, зараз мало хто їх любить і мало хто використовує.

Проте все одно багато коду (включаючи код стандартних бібліотек Java) містять ті самі checked-виключення. І що ж з ними робити? Ігнорувати не можна, обробляти – невідомо як.

Java-програмісти запропонували «загортати» checked-виключення всередину RuntimeException. Іншими словами, перехоплювати всі checked-виключення, створювати замість них unchecked-виключення (наприклад, RuntimeException) і викидати вже їх. Виглядає це все приблизно так:

try
{
   код, де може виникнути checked-виключення
}
catch(Exception exp)
{
   throw new RuntimeException(exp);
}

Не дуже красиве рішення, але нічого кримінального: виключення просто поклали всередину виключення RuntimeException.

За бажанням його можна звідти легко дістати. Приклад:

Код Примітка
try
{
   // код де ми запакували checked виключення
   // у RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // тут код по обробці Exception
   }
}







Отримуємо виключення, збережене всередині об'єкта RuntimeException. cause може бути null

Визначаємо його тип і перетворюємо в змінну checked-типу.


4. Множинне перехоплення винятків

Програмісти дуже не люблять дублювання коду. Навіть придумали такий принцип розробки — 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.



5. Власні виключення

Ти завжди можеш створити свій власний клас-виключення. Просто успадкувавши клас від, наприклад, RuntimeException. Виглядати це буде приблизно так:

class Ім'яКласу extends RuntimeException
{
}

Деталі ми обговоримо, коли ти вивчиш ООП, наслідування, конструктори та перевизначення методів.

Однак, навіть якщо у тебе є тільки такий простий клас — взагалі без коду, ти все одно можеш кидати його виключення:

Код Примітка
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




Кидаємо unchecked-виключення MyException.

Детальну роботу зі своїми виключеннями ми розглянемо у квесті Java Multithreading.

Коментарі (16)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Pavlo Kezin Рівень 23
16 вересня 2023
найдивніше, що всі задачі вирішив швидко. але опісля намагався вивчити як це працює... щось не чітко я розумію все. Вже дивився і підручники різні. але всеодно.... хто тут що ловить, а кто кидає... ловить і кидає далі...
SoniaSapsan Рівень 17
1 лютого 2023
Щодо задачі із групуванням винятків - автори самі дають нам підказку, просто згрупуйте винятки по буквам :)
Бородатий Рівень 17
3 січня 2023
шось не понятно де які винятки від чого вспадковуються
Yaroslav Tkachyk Рівень 23 Expert
12 січня 2023
В лекції аж 2 картинки на цю тему. Звісно тут не всі типи винятків, але найбільш популярні точно є.
Бородатий Рівень 17
12 січня 2023
По задачі там тих винятків немає... якщо не помиляюсь то найшов там 1 чи 2 ... друг програміст показав як дивитись через IDEY там простіше
Василь Рівень 4
23 вересня 2023
а ви б нам показали, в свою чергу, як дивитися!
Ва Дим Рівень 28
25 квітня 2024
так нам розкажи
WhoAMI Рівень 51
5 листопада 2022
усі вирішили задачки з першого разу і ні в кого не виникло питань?
Lazarenko Dmytro Рівень 30
8 листопада 2022
Взагалі не зрозуміло, навіщо далі викидувати винятки. Перша незрозуміла тема
10 листопада 2022
це все через те, що checked виключення потрібно обробити в любому випадку і якщо написати процедуру яка вимагає таке виключення і не обробити відразу, а викинути наверх, то чорт його знає де треба буде його обробити, а так ми міняємо на unchecked виключення і його не треба обробляти. Так я розумію)
Roma Chernesh Рівень 16
19 лютого 2023
тобто у темі із потрійними масивами (типу [ ] [ ] [ ] ) прям легко все було зрозуміти?)
Lazarenko Dmytro Рівень 30
27 лютого 2023
Так
Roma Chernesh Рівень 16
27 лютого 2023
Ну, ото не всім так пощастило
5 березня 2023
У друга спитав, він каже що так ніхто не робить, так що можеш не переживати
Андрій Рівень 18
9 лютого 2024
Rona Chernesh з багатовимірними масивам все було легко, а от з колекціями💀
27 березня 2025
ну раз друг сказав, не смію навіть спорити)))))😁😁😁