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-винятки:

  • ПорожнійСвіт
  • ОдинокийСвіт

Виклик цього методу можна обробити трьома способами:

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
а ви б нам показали, в свою чергу, як дивитися!
hidden #3421731 Рівень 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
ну раз друг сказав, не смію навіть спорити)))))😁😁😁