1. Вступ
У стандартній бібліотеці Java вже є безліч винятків: NullPointerException, IllegalArgumentException, IOException тощо. Та іноді стандартних винятків недостатньо, щоб чітко й зрозуміло описати помилку, яка виникла у вашій програмі.
Приклад із життя:
Ви пишете банківський застосунок. Користувач намагається зняти більше грошей, ніж є на рахунку. Можна кинути IllegalArgumentException. Але буде значно зрозуміліше, якщо використати власний виняток, наприклад, InsufficientFundsException. Тоді з коду одразу видно, що сталося.
Власні винятки — це спосіб грамотно налаштувати обробку помилок у застосунку. Вони дають змогу тонко керувати реакцією на проблеми, а вдала назва (якщо назвати логічно!) одразу підказує, що саме сталося. Крім того, вони самодокументовані: методи з throws MyException у сигнатурі чітко вказують, які помилки можливі. А ще завжди можна додати корисні поля (наприклад, баланс, суму операції тощо).
2. Як створити власний виняток?
Усе дуже просто: створіть новий клас, який наслідує один із стандартних класів винятків.
- Для перевірюваних (checked) винятків — наслідуйтеся від Exception.
- Для неперевірюваних (unchecked) — від RuntimeException.
Приклад: перевірюваний виняток
public class InvalidCredentialsException extends Exception {
public InvalidCredentialsException(String message) {
super(message); // Передаємо повідомлення до батьківського класу
}
}
Тепер ви можете кидати цей виняток у своєму коді:
if (!login.equals("admin") || !password.equals("1234")) {
throw new InvalidCredentialsException("Невірний логін або пароль");
}
Приклад: неперевірюваний виняток
public class NegativeBalanceException extends RuntimeException {
public NegativeBalanceException(String message) {
super(message);
}
}
Коли використовувати checked, а коли unchecked?
- Checked — якщо помилка очікувана й її можна обробити (наприклад, помилка валідації, відсутність файлу, некоректні дані користувача).
- Unchecked — якщо помилка пов’язана з багом у логіці програми (наприклад, ділення на нуль, порушення інваріанта).
3. Конструктори: як зробити виняток інформативним
Зазвичай у своєму класі винятку реалізують щонайменше один конструктор із параметром String message. Але часто додають і інші:
public class ScoreLimitExceededException extends Exception {
public ScoreLimitExceededException() {
super();
}
public ScoreLimitExceededException(String message) {
super(message);
}
public ScoreLimitExceededException(String message, Throwable cause) {
super(message, cause);
}
public ScoreLimitExceededException(Throwable cause) {
super(cause);
}
}
Пояснення:
- message — текстовий опис помилки.
- cause — причина (інший виняток), якщо ви хочете «загорнути» одну помилку в іншу.
Порада: якщо не знаєте, які конструктори потрібні, — додайте бодай той, що приймає рядок.
4. Використання власних винятків у коді
Розгляньмо приклад: користувач має бали, і не можна додати понад 100.
public class User {
private String name;
private int score;
public User(String name) {
this.name = name;
this.score = 0;
}
public void addScore(int points) throws ScoreLimitExceededException {
if (score + points > 100) {
throw new ScoreLimitExceededException("Перевищено ліміт балів! Спроба додати: " + points);
}
this.score += points;
}
}
Клас винятку:
public class ScoreLimitExceededException extends Exception {
public ScoreLimitExceededException(String message) {
super(message);
}
}
Обробка:
try {
user.addScore(60);
user.addScore(50); // Тут буде кинуто виняток!
} catch (ScoreLimitExceededException e) {
System.out.println("Помилка: " + e.getMessage());
}
Результат:
Помилка: Перевищено ліміт балів! Спроба додати: 50
У вас, напевно, могло виникнути запитання: а що, якщо замість винятку просто використати умову if і, скажімо, повертати false або інше спеціальне значення, щоб показати, що операція не вдалася? Наприклад, так:
public boolean addScore(int points) {
if (score + points > 100) {
return false; // Або кинути якийсь RuntimeException, якщо не плануєте його обробляти
}
this.score += points;
return true;
}
Хоча такий підхід може здаватися простішим, він має недоліки, коли йдеться про серйозні помилки або порушення логіки застосунку.
По‑перше, повернення false або іншого значення для позначення помилки зобов’язує код, що викликає, завжди перевіряти повернене значення. Якщо програміст забуде це зробити, помилка може залишитися непоміченою, що призведе до непередбачуваної поведінки програми. Винятки, навпаки, змушують обробляти (для перевірюваних винятків) або, щонайменше, явно сигналізують про проблему, якщо їх не перехоплено.
По‑друге, винятки виразніше передають семантику помилки. Повернення false може означати що завгодно: «не вдалося», «не застосовно», «недоступно». Виняток ScoreLimitExceededException чітко й однозначно каже: «ліміт балів перевищено». Це покращує читабельність і підтримуваність коду.
По‑третє, винятки дають змогу централізувати обробку помилок. Замість розкидання if-перевірок по всьому коду, де викликається addScore, ви можете перехопити виняток в одному місці та ухвалити відповідне рішення: вивести повідомлення користувачеві, зробити запис у журнал або виконати відкат транзакції.
Нарешті, у разі таких проблем, як перевищення ліміту, це справді виняткова ситуація (звідси й назва). Звичайний потік виконання програми передбачає, що бали буде успішно додано. Якщо це не так — порушено бізнес‑логіку або інваріанти обʼєкта, тож це ідеальний сценарій для використання винятків.
5. Додавання власних полів до винятків
Іноді варто додати до власного винятку додаткові дані, які допоможуть обробити помилку.
Приклад:
public class ScoreLimitExceededException extends Exception {
private int currentScore;
private int attemptedAdd;
public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
super(message);
this.currentScore = currentScore;
this.attemptedAdd = attemptedAdd;
}
public int getCurrentScore() {
return currentScore;
}
public int getAttemptedAdd() {
return attemptedAdd;
}
}
Використання:
if (score + points > 100) {
throw new ScoreLimitExceededException(
"Перевищено ліміт балів!",
this.score,
points
);
}
6. Корисні нюанси
Як називати свої винятки?
У Java прийнято називати власні винятки із суфіксом Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.
Не називайте власні винятки просто Error або Warning — це може ввести в оману інших розробників (і навіть вас самих за кілька тижнів).
Де й коли кидати свої винятки?
- Під час валідації даних користувача (наприклад, порожнє імʼя, відʼємний вік).
- За порушення бізнес‑правил (наприклад, перевищення ліміту, спроба зняти більше грошей, ніж є на рахунку).
- У разі помилок у роботі із зовнішніми сервісами (наприклад, сервіс недоступний, перевищено час очікування).
7. Типові помилки під час створення власних винятків
Помилка № 1: Наслідування не від того класу.
Наслідуйтеся від Exception (або RuntimeException), а не від Throwable чи Error.
Помилка № 2: Не додано конструктор із повідомленням.
Без конструктора, що приймає рядок (String message), ваші винятки будуть «німі», і їх важко налагоджувати.
Помилка № 3: Використання стандартних винятків для бізнес‑логіки.
Не варто кидати NullPointerException або IllegalArgumentException там, де потрібен власний «промовистий» виняток.
Помилка № 4: Зловживання власними винятками.
Не варто створювати окремий клас винятку для кожної дрібниці. Якщо помилка не унікальна для вашої предметної області, використовуйте стандартні винятки.
Помилка № 5: Відсутність серіалізації (рідко, але трапляється).
Якщо ваш виняток передаватиметься мережею або зберігатиметься, варто додати implements Serializable. Втім, для простих застосунків це не критично.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ