Винятки в Java
Android Developer у emCodio, Викладач Комп'ютерної школи Hillel.
Виняток (виняткова ситуація) - проблема, що виникає під час виконання програми.
Виняток - подія, що відбувається під час виконання програми, що порушує нормальний потік інструкцій.
Євген Мица, Android Developer у emCodio та Викладач курсів з Java, розповідає, що таке винятки і що з ними робити.
Деякі причини:
- Користувач ввів неправильні дані
- Неможливо знайти файл, який потрібно відкрити
- Мережеве з'єднання було втрачено під час сеансу зв'язку
- JVM не вистачає пам'яті
Рекомендуємо публікацію на тему
Типи винятків:
- Перевірені винятки (Checked Exception)
- Неперевірені винятки (Unchecked Exception)
- Помилки (Errors)
Перевірені винятки
Виключення перевіряються компілятором під час компіляції. Також їх називають винятком часу компіляції. Не можна просто ігнорувати їх, програміст повинен подбати про ці винятки (обробити).
Наприклад, використовуємо клас FileReader для читання даних із файлу. Файл, вказаний у конструкторі, відсутній. Виникає виняток FileNotFoundException. Компілятор пропонує обробити виняток.
Неперевірені винятки
Неперевірені винятки виникають під час виконання програми. Також називають винятками часу виконання (Runtime Exceptions). До них належать помилки програмування, такі як логічні помилки або неправильне використання API. Виняток часу виконання ігнорується під час компіляції.
Наприклад, оголошено масив розміром із 7 елементів. Намагаємось викликати 8-й елемент масиву. Виникає виняток ArrayIndexOutOfBoundsException.
Помилки - це не винятки, а проблеми, що виникають поза контролем користувача або програміста.Помилки зазвичай ігноруються в коді, тому що рідко щось можна зробити з помилкою. Ігноруються під час компіляції.
Наприклад, такі як OutOfMemoryError, VirtualMachineError, IOError.
Класи помилок та винятків
Клас Throwable - Суперклас всіх помилок і винятків у Java.
Тільки об'єкти, які є екземплярами цього класу (або одного з його підкласів), генеруються віртуальною машиною Java або може бути викинуто оператором throw Java.
Примірники двох підкласів класу Throwable, Error і Exception зазвичай використовуються позначення виникнення виняткових ситуацій.
Exception - Більшість винятків, що генерують об'єкти в коді програми.
Error зазвичай використовується для серйозних помилок у системі, наприклад, що перешкоджають запуску JVM.
RuntimeException — суперклас тих винятків, які можуть виникати за нормальної роботи віртуальної машини Java. RuntimeException та його підкласи — неперевірені винятки.
RuntimeException зарезервований для винятків, що вказують на неправильне використання API.
Прикладом виключення часу виконання є виняток NullPointerException, що виникає, коли метод намагається отримати доступ до члена об'єкта через посилання null.
Обробка винятків
Обробка винятків — це процес визначення послідовності кроків у програмі обробки виключення.
Надаючи обробники винятків у програмі, ми можемо забезпечити нормальне виконання програми.
Без обробників винятків програма буде завершена, і нормальний потік виконання буде перерваний у разі виключення.
Варіанти обробки винятків:
- Перехоплення та обробка винятків
- Вказівка винятків, створюваних методом
Для перехоплення та обробки використовуються блоки try, catch та finally. Ключове слово try використовується для вказівки блоку, де ми повинні розмістити код виключення. Ми не можемо використовувати лише блок try.За блоком try повинен йти або catch, або finally.
Блок catch використовується для обробки винятків.
Блок використовується для виконання необхідного програмного коду. Виконується незалежно від того, чи опрацьовано виняток.
При зазначенні винятків, створюваних методом, метод повинен надати умову throws, у якому перераховані винятки.
Ключове слово throws використовується для оголошення винятків. Вказує, що у способі може виникнути виняток. Не викликає виняток. Завжди використовується із сигнатурою методу.
Рекомендуємо курс на тему
Android Developer у emCodio, Викладач Комп'ютерної школи Hillel.
Виправляємо 7 поширених помилок обробки винятків у Java
Привіт, Хабре! Представляю вашій увазі переклад статті Fixing 7 Common Java Exception Handling Mistakes автора Thorben Janssen.
Обробка виключення є одним з найпоширеніших, але не обов'язково одним із найпростіших завдань. Це все ще одна з тем, що часто обговорюються в досвідчених командах, і є кілька передових методів і поширених помилок, про які ви повинні знати.
Ось кілька речей, які слід уникати при обробці винятків у вашому додатку.
Помилка 1: оголошення java.lang.Exception або java.lang.Throwable
Як ви вже знаєте, вам потрібно або оголосити, або обробити виняток, що перевіряється. Але винятки, що перевіряються, — це не єдині, які ви можете вказати. Ви можете використовувати будь-який підклас java.lang.Throwable у пропозиції throws. Таким чином, замість вказівки двох різних винятків, які викидає наступний фрагмент коду, можна просто використовувати виняток java.lang.Exception у пропозиції throws.
public void doNotSpecifyException() throws Exception < doSomething(); >public void doSomething() throws NumberFormatException, IllegalArgumentException < // do something >
Але це не означає, що ви маєте це зробити.Вказівка Exeption або Throwable робить майже неможливим правильне поводження з ними при викликі вашого методу. Єдина інформація, яку отримує метод, що викликає вами, полягає в тому, що щось може піти не так. Але ви не ділитесь будь-якою інформацією про якісь виняткові події, які можуть статися. Ви приховуєте цю інформацію за узагальненими причинами викиду винятків. Стає ще гірше, коли ваша програма змінюється з часом. Викид узагальнених винятків приховує всі зміни винятків, які викликає повинен очікувати та обробляти. Це може призвести до кількох непередбачених помилок, які слід знайти в тестовому прикладі замість помилки компілятора.
Використовуйте конкретні класи
Набагато краще вказати найбільш конкретні класи винятків, навіть якщо вам доводиться використовувати кілька із них. Це повідомляє зухвалий пристрій, які виняткові подій потрібно обробляти. Це також дозволяє вам оновити пропозицію throw, коли ваш метод видає додатковий виняток. Таким чином, ваші клієнти знають про зміни і навіть отримують помилку, якщо ви змінюєте винятки, що викидаються. Такий виняток набагато простіше знайти та обробити, ніж виняток, який з'являється лише при запуску конкретного тестового прикладу.
public void specifySpecificExceptions() throws NumberFormatException, IlegallegalArgumentException
Помилка 2: перехоплення узагальнених винятків
Серйозність цієї помилки залежить від того, який програмний компонент ви реалізуєте, і де ви виявляєте виняток. Можливо, було б добре зловити java.lang.Exception в основному методі вашої програми Java SE. Але ви повинні віддати перевагу зловити певні винятки, якщо ви реалізуєте бібліотеку або працюєте над глибшими шарами вашої програми.
Це дає кілька переваг.Такий підхід дозволяє обробляти кожен клас винятків по-різному і не дозволяє перехоплювати винятки, яких ви не очікували.
Але майте на увазі, що перший блок catch, який обробляє клас виключення або один із його супер-класів, зловить його. Тому спочатку обов'язково впіймайте найбільш специфічний клас. В іншому випадку ваші IDE покажуть повідомлення про помилку або попередження про недосяжний блок коду.
try < doSomething(); >catch (NumberFormatException e) < // handle the NumberFormatException log.error(e); >catch (IllegalArgumentException e) < // handle the IllegalArgumentException log.error(e); >
Помилка 3: Логування та прокидання винятків
Це одна з найпопулярніших помилок при обробці виключень Java. Може здатися логічним реєструвати виняток там, де він був кинутий, а потім прокинути його об'єкту, що викликає, який може реалізувати конкретну обробку для конкретного випадку використання. Але ви не повинні робити це з трьох причин:
1. У вас недостатньо інформації про прецедент, який хоче реалізувати об'єкт вашого методу, що викликає. Виняток може бути частиною очікуваної поведінки та оброблятися клієнтом. І тут немає необхідності реєструвати його. Це додасть помилкове повідомлення про помилку у файл журналу, який має бути відфільтрований вашою операційною групою.
2. Повідомлення журналу не надає жодної інформації, яка ще не є частиною самого виключення. Його трасування та трасування стека повинні містити всю необхідну інформацію про виняткову подію. Повідомлення описує це, а трасування стека містить докладну інформацію про клас, метод і рядок, в якому вона відбулася.
3. Ви можете реєструвати те саме виняток кілька разів, коли ви реєструєте його в кожному блоці catch, який його ловить. Це зіпсує статистику у вашому інструменті моніторингу та ускладнює читання файлу журналу для ваших операцій та команди розробників.
Реєструйте виняток там, де його обробляєте
Таким чином, найкраще реєструвати виняток тоді, коли ви його обробляєте. Як у наступному фрагменті коду. Метод doSomething генерує виняток. Метод doMore просто вказує на нього, тому що у розробника недостатньо інформації для його обробки. Потім він обробляється в методі doEvenMore, який також записує повідомлення журналу.
public void doEvenMore() < try < doMore(); >catch (NumberFormatException e) < // handle the NumberFormatException >catch (IllegalArgumentException e) < // handle the IllegalArgumentException >> public void doMore() throws NumberFormatException, IllegalArgumentException < doSomething >public void doSomething() throws NumberFormatException, IllegalArgumentException < // do something >
Помилка 4: використання винятків для керування потоком
Використання винятків для керування потоком вашої програми вважається анти-шаблоном з двох основних причин:
Вони переважно працюють як оператор Go To, тому що вони скасовують виконання блоку коду і переходять до першого блоку catch, який обробляє виняток. Це робить код дуже важким для читання.
Вони не такі ефективні, як загальні структури управління Java. Як видно з назви, ви повинні використовувати їх тільки для виняткових подій, а JVM не оптимізує їх так само, як і інший код. коди повинні бути виконані.
Помилка 5: видалити причину виникнення виключення
Іноді вам може знадобитися обернути один виняток в інший. Можливо, ваша команда вирішила використати спеціальний виняток для бізнесу з кодами помилок та єдиною обробкою. Немає нічого поганого у цьому підході, якщо ви не усунете причину.
Коли ви створюєте новий виняток, ви завжди повинні встановлювати початковий виняток як причину.В іншому випадку ви втратите трасування повідомлення та стека, які описують виняткову подію, що викликала ваш виняток. Клас Exception та всі його підкласи надають кілька методів-конструкторів, які приймають вихідний виняток як параметр і задають його як причину.
try < doSomething(); >catch (NumberFormatException e) < throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); >catch (IllegalArgumentException e)
Помилка 6: Узагальнення винятків
Коли ви узагальнюєте виняток, ви ловите конкретний, наприклад, NumberFormatException, і натомість генеруєте неспецифічне java.lang.Exception. Це схоже, але навіть гірше ніж перша помилка, яку я описав у цій статті. Він не тільки приховує інформацію про конкретний випадок помилки на вашому API, але також ускладнює доступ.
public void doNotGeneralizeException() throws Exception < try < doSomething(); >catch (NumberFormatException e) < throw new Exception(e); >catch (IllegalArgumentException e) < throw new Exception(e); >>
Як ви можете бачити в наступному фрагменті коду, навіть якщо ви знаєте, які винятки може викликати метод, ви не можете їх зловити. Вам потрібно зловити загальний клас Exception, а потім перевірити тип його причини. Цей код не тільки громіздкий для реалізації, але його також важко читати. Стає ще гірше, якщо ви поєднуєте цей підхід з помилкою 5. Це видаляє всю інформацію про виняткову подію.
try < doNotGeneralizeException(); >catch (Exception e) < if (e.getCause() instanceof NumberFormatException) < log.error("NumberFormatException: " + e); >else if (e.getCause() instanceof IllegalArgumentException) < log.error("IllegalArgumentException: " + e); >else < log.error("Unexpected exception: " + e); >>
Отже, який підхід найкращий?
Будьте конкретні та зберігайте причину виникнення виключення.
Винятки, які ви кидаєте, повинні бути максимально конкретними.І якщо ви обертаєте виняток, ви також повинні встановити вихідний виняток як причину, щоб не втратити трасування стека та іншу інформацію, що описує виняткову подію.
try < doSomething(); >catch (NumberFormatException e) < throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); >catch (IllegalArgumentException e)
Помилка 7: додавання непотрібних перетворень винятків
Як я вже пояснював раніше, може бути корисно обернути винятки в користувацькі, якщо ви встановите вихідний виняток як причину. Але деякі архітектори перестараються і запроваджують спеціальний клас винятків для кожного архітектурного рівня. Таким чином, вони вловлюють виняток у рівні персистентності і переносять його в MyPersistenceException. Бізнес-рівень ловить і обгортає його в MyBusinessException, і це продовжується доти, доки воно не досягне рівня API або не буде оброблено.
public void persistCustomer(Customer c) throws MyPersistenceException < // persist a Customer >public void manageCustomer(Customer c) throws MyBusinessException < // manage a Customer try < persistCustomer(c); >catch (MyPersistenceException e) < throw new MyBusinessException(e, e.getCode()); >> public void createCustomer(Customer c) throws MyApiException < // create a Customer try < manageCustomer(c); >catch (MyBusinessException e) < throw new MyApiException(e, e.getCode()); >>
Легко бачити, що ці додаткові виняткові класи не дають жодних переваг. Вони просто вводять додаткові шари, які обертають виняток. І хоча було б смішно обернути подарунок у безлічі барвистого паперу, це не дуже гарний підхід до розробки програмного забезпечення.
Обов'язково додайте інформацію
Просто подумайте про код, який повинен обробляти виняток або про себе, коли вам потрібно знайти проблему, що викликала виняток. Спочатку потрібно прорватися через кілька рівнів винятків, щоб знайти вихідну причину.І до сьогодні я ніколи не бачив додаток, який використовував цей підхід, і додавав корисну інформацію з кожним шаром виключення. Вони або узагальнюють повідомлення про помилку та код, або надають надмірну інформацію.
Тому будьте обережні з кількістю класів винятків, які ви вводите. Ви завжди повинні запитувати себе, чи дає новий клас винятків додаткову інформацію чи інші переваги. У більшості випадків для досягнення цього вам не потрібно більше одного рівня винятків користувача.
public void persistCustomer(Customer c) < // persist a Customer >public void manageCustomer(Customer c) throws MyBusinessException < // manage a Customer throw new MyBusinessException(e, e.getCode()); >public void createCustomer(Customer c) throws MyBusinessException < // create a Customer manageCustomer(c); >
Винятки - Java: Введення в ОВП
У цьому уроці ми поговоримо про механізм винятків. З його допомогою відбувається керування помилками, які виникають під час виконання програми. Ми розберемо концепцію виключень, що перевіряються і не перевіряються, навчимося викидати і перехоплювати їх.
Концепція винятків не пов'язана з ООП, але у багатьох мовах, включаючи Java, винятки зав'язані класи, тому ця тема розглядається у цьому курсі.
Неперевірені винятки
Почнемо із проблеми. Не всі помилки можна виявити на етапі компіляції, наприклад звернення до неіснуючого індексу в масиві. Подібна помилка виникне вже під час роботи програми та швидше за все зупинить її виконання:
int[]
items
=
1,
2,
3>;
System.out.println(items[5]);
Запуск такого коду призведе до викидання (порушення) виключення та переривання роботи програми. У консолі це виглядатиме так:
in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3 at io.hexlet.Application.main (Application.java:24)
Помилка містить не тільки опис того, що сталося, але і вказує на те місце, де вона виникла, включаючи файл і рядок. Це важливо для налагодження.
Інша схожа ситуація – це поділ на нуль. Воно також призводить до виключення:
var x
=
0;
var
value
=
3
/
x;
// Exception в thread "main" java.lang.ArithmeticException: / by zero
Такі помилки майже завжди є багами, які потрібно виправити. У Java такі винятки називаються непроверяемыми (unchecked) оскільки вони спеціально не обробляються і відстежуються на відміну від винятків, що перевіряються.
Такі винятки можливі не тільки всередині Java, але і в коді бібліотек і навіть в коді вашого додатка. Наприклад, якщо бібліотека використовується неправильно, всередині неї може спрацювати код, який викидає відповідний виняток:
throw new
RuntimeException("Повідомлення про помилку");
З цього коду видно, що це виключення об'єкт. У цей об'єкт передається повідомлення про помилку плюс у нього автоматично записується інформація про те, де цей виняток було викинуто. Під викиданням мається на увазі використання конструкції throw, а не створення об'єкта виключення, об'єкт можна створити і заздалегідь.
var error
=
new
RuntimeException("Повідомлення про помилку");
throw
error;
В даному випадку використовується клас RuntimeException, але так буває не завжди. Під різні типи помилок створюються різні винятки зручності роботи з ними, наприклад, це допомагає під час аналізу тексту помилки, оскільки дивлячись на клас винятку відразу відомо про що йдеться.
Перевірені винятки
Перевірені винятки – це винятки, які можуть виникнути у будь-якому випадку, навіть якщо у програмі немає багів. Найчастіше вони виникають при взаємодії Java із зовнішнім світом. Найпростіше – це читання файлу, якщо файлу немає, під час його читання виникне виняток.
import java.nio.file.Files;
import
java.nio.file.Paths;
public
class
Application
public
static
void
main(String[]
args)
// Створюємо об'єкт Paths для опису шляху
var
path
=
Paths.get("path/to/file.txt");
// Читаємо файл і перетворимо на рядок
var
content
=
new
String(Files.readAllBytes(path));
// Виводимо вміст на екран
System.out.println(content);
>
>
Якщо ми напишемо такий код, компілятор видасть помилку. Він знає, що метод Files.readAllBytes() викидає виняток IOException , який перевіряється. Такий виняток має бути оброблений, оскільки він може виникнути незалежно від бажання програміста. Обробка винятків проводиться за допомогою конструкції try..catch.
import java.nio.file.Files;
import
java.nio.file.Paths;
import
java.io.IOException;
public
class
Application
public
static
void
main(String[]
args)
var
path
=
Paths.get("path/to/file.txt");
try
var
content
=
new
String(Files.readAllBytes(path));
System.out.println(content);
>
catch
(IOException
e)
// Обробляємо помилку оскільки потрібно у цій ситуації
System.out.println("Перевірте, що файл "
+
path
+
"є і до нього є доступ");
>
// Код, який йде тут, буде виконаний
>
>
Так виглядає текст помилки, якщо її вивести в консоль # e.printStackTrace() Exception in thread "main" java.nio.file.NoSuchFileException: path/to/file.txt at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:261) at java.base/java.nio.file.Files.newByteChannel(Files.java:379) at java.base/java.nio.file.Files.newByteChannel(Files.java:431) at java.base/java.nio.file.Files.readAllBytes(Files.java:3263) at io.hexlet.Application.main(Application.java:27)
Блок catch перехоплює виняток, який був викинутий у блоці try . Саме виняток потрапляє в catch як об'єкт e, який можна за необхідності використовувати. Решта обробки лежить на плечах програміста, аж до того, що всередині catch може бути викинуто якийсь інший виняток, але це вже просунута техніка, яка зазвичай використовується в бібліотеках.
Перехоплення виключення дозволяє програмі продовжити працювати далі без зупинки. Код після конструкції try..catch продовжить виконання як ні в чому не бувало.
У прикладі вище ми обробляємо виняток у тому місці де воно і сталося, але насправді, помилки зазвичай відбуваються там, де вони можуть бути оброблені. Понад те, сам механізм винятків з'явився саме з цієї причини. У коді реальних проектів десятки, сотні та мільйони рядків коду. Такий код розбитий на безліч шарів, в яких один метод викликає інший, цей викликає третій і так далі, подібні ланцюги можуть досягати сотні методів у глибину викликів. Тут проявляється те, що код, який викликається на нижньому рівні, може не знати як конкретно потрібно обробити виняток, що виник. Уявіть бібліотеку, яка вміє завантажувати файли по мережі. Ця бібліотека може використовуватися в різних додатках, які по-різному показують помилки завантаження.
Схожу ситуацію ми можемо імітувати в нашому прикладі, якщо винесемо код читання файлу в окремий метод:
public class
Application
public
static
void
main(String[]
args)
// Змінна задається до try..catch щоб до неї можна було звернутися з try та з catch
var
path
=
"/path/to/file.txt";
try
var
content
=
readFile(path);
System.out.println(content);
>
catch
(IOException
e)
System.out.println("Перевірте, що файл "
+
path
+
"є і до нього є доступ");
>
// Код, який йде тут, буде виконаний
>
public
static
String
readFile(String
path)
var
preparedPath
=
Paths.get(path);
var
content
=
new
String(Files.readAllBytes(preparedPath));
return
content;
>
>
Цей код не пройде компіляцію, тому що виняток IOException є перевіреним (checked), але в методі Application.readFile() немає його обробки, як того вимагають виключення, що перевіряються. У цьому місці виникає суперечність. Ми не хочемо обробляти виняток, тому що обробка буде десь далі, у нашому випадку методом Application.main() . Java дозволяє такі ситуації через вказівку того, що метод викидає виняток, що перевіряється. Визначення Application.readFile() буде виглядати так:
public static
String
readFile(String
path)
throws
IOException
// Тут вміст
>
Тепер будь-який метод, який викликає в собі метод Application.readFile() повинен зробити одне з двох:
- Вказати через throws які виключення, що перевіряються, можуть бути викинуті всередині нього.
- Обробити виняток, що перевіряється, і тоді не доведеться використовувати throws .
Вибір залежить від того, де ми хочемо обробляти винятки.
