Що робить ключове слово throws?
throws — це ключове слово у сигнатурі методу, яке документує контракт: воно повідомляє викликаючому коду, які checked винятки метод може викинути і які викликаючий код зобов'яза...
Junior Level
Визначення
throws — це ключове слово у сигнатурі методу, яке документує контракт: воно повідомляє викликаючому коду, які checked винятки метод може викинути і які викликаючий код зобов’язаний бути готовий обробити.
public void readFile() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
Що це означає
Метод каже викликаючому коду: «Я можу не впоратися, і ти зобов’язаний знати про це».
Викликаючий код повинен:
- Огорнути виклик у
try-catch - Або теж додати
throwsу свою сигнатуру
// Варіант 1: try-catch
public void safeRead() {
try {
readFile();
} catch (IOException e) {
log.error("Failed", e);
}
}
// Варіант 2: пробросити далі
public void process() throws IOException {
readFile();
}
Кілька винятків
public void process() throws IOException, SQLException {
// Може викинути обидва
}
Чому throws потрібен для checked, але не для unchecked
Checked винятки (наслідують Exception, але не RuntimeException) — компілятор вимагає їх обробки. throws — це спосіб методу делегувати відповідальність викликаючому: «Я не знаю, як обробити цю помилку, нехай викликаючий вирішує».
Unchecked винятки (наслідують RuntimeException) — компілятор не вимагає їх обробки. Вони представляють помилки програмування (NullPointerException, IllegalArgumentException), які не можна осмислено обробити — їх треба виправляти, а не ловити. Тому throws для них не потрібен.
// Checked: компілятор змушує оголосити або спіймати
public void readFile() throws IOException { ... }
// Unchecked: компілятор не вимагає throws
public void process(String s) {
if (s == null) throw new NullPointerException();
}
Unchecked винятки та throws
Для RuntimeException throws технічно не потрібен, хоча мова і дозволяє його написати:
// Не потрібно писати throws NullPointerException
public void process(String s) {
if (s == null) throw new NullPointerException();
}
Хоча void method() throws RuntimeException скомпілюється, це вважається поганим стилем — unchecked винятки не повинні засміювати сигнатуру.
Коли НЕ використовувати throws
- У публічному API бібліотек —
throws Exceptionзмушує клієнта ловити «все підряд» - В інтерфейсах з численними checked винятками — ознака порушення SRP, розбийте інтерфейс
- У Spring REST контролерах — використовуйте
@ControllerAdviceзамістьthrowsу кожному методі - У лямбда-виразах — функціональні інтерфейси не підтримують checked винятки
- Для помилок програмування —
IllegalArgumentException,NullPointerExceptionне оголошують черезthrows
Middle Level
Байт-код: атрибут Exceptions
У .class файлі інформація про throws зберігається в атрибуті методу Exceptions. JVM не використовує його для контролю в рантаймі — тільки для верифікатора компілятора.
При виклику через Reflection JVM не змусить ловити винятки:
Method m = MyClass.class.getMethod("readFile");
m.invoke(obj); // IOException не зобов'язана бути спійманою
Binary Compatibility
Додавання checked винятку ламає бінарну сумісність:
// Було:
public void process() { }
// Стало — усі клієнти перестануть компілюватися
public void process() throws IOException { }
Видалення винятку — бінарно сумісно (старий try-catch продовжить працювати).
Senior Tip: якщо потрібно додати перевірку в публічний API, використовуйте RuntimeException або створіть перевантажений метод.
Перевизначення та LSP
Принцип підстановки Лісков:
interface Service {
void process() throws IOException;
}
class Impl implements Service {
@Override
// МОЖНА: не викидати взагалі (звуження контракту)
public void process() { }
@Override
// МОЖНА: викидати підтип
public void process() throws FileNotFoundException { }
// НЕ МОЖНА: новий checked виняток — помилка компіляції
// public void process() throws IOException, SQLException { }
}
Unchecked та throws
Технічно можна написати void method() throws RuntimeException, але це поганий тон. Unchecked винятки не повинні засміювати сигнатуру. Для документації використовуйте @throws у JavaDoc.
Senior Level
Throws як документація
Розглядайте throws як частину контракту. throws InsufficientFundsException каже про бізнес-логіку більше, ніж будь-який коментар.
Generic Throws
Java дозволяє використовувати дженерики:
public <E extends Exception> void doWork(ThrowingRunnable<E> task) throws E {
task.run();
}
Активно використовується у функціональних інтерфейсах та обгортках.
Interface Segregation
Якщо інтерфейс викидає занадто багато різних throws — ознака порушення SRP. Метод робить занадто багато речей.
// Погано — занадто багато відповідальностей
public interface Service {
void process() throws IOException, SQLException, AuthException, ValidationException;
}
// Добре — розбити на інтерфейси
public interface DataService {
void save() throws DataAccessException;
}
public interface AuthService {
void authenticate() throws AuthException;
}
// Ще краще — у Spring використовувати @ControllerAdvice
@RestController
public class OrderController {
// throws не потрібен — @ControllerAdvice перехопить
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderRequest req) {
return orderService.create(req);
}
}
ReflectiveOperationException
При Reflection усі checked винятки викликаного методу огортаються в InvocationTargetException.
throws Exception — небажаний патерн
У більшості випадків не варто писати throws Exception у публічному API. Це змушує клієнта ловити «все підряд», позбавляючи можливості адекватно реагувати на конкретні збої.
Діагностика
javap -v MyClass.class— побачите атрибутExceptionsу байт-коді- Binary compatibility check — інструменти на кшталт JAPICC перевіряють сумісність версій
- Static Analysis — Sonar блокує
throws Exceptionу публічних API
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
throwsдокументує контракт методу — які checked винятки викликаючий зобов’язаний обробити- Checked винятки (не
RuntimeException) компілятор вимагає оголосити або спіймати - Unchecked винятки (
RuntimeException) не потребуютьthrows— це помилки програмування - При перевизначенні можна звузити
throws(прибрати або замінити підтипом), але не розширити - Додавання checked винятку ламає бінарну сумісність — клієнти перестануть компілюватися
- У Spring REST використовуйте
@ControllerAdviceзамістьthrowsу кожному методі throws Exceptionу публічному API — антипатерн, змушує клієнта ловити «все підряд»
Часті уточнюючі запитання:
- Обов’язково чи ловити checked винятки? — Так, або
try-catch, або свійthrows - Чи можна додати
throws RuntimeException? — Технічно так, але поганий стиль - Що буде при перевизначенні методу? — Можна прибрати
throwsабо звузити, але не розширити (LSP) - Як
throwsзберігається у байт-коді? — АтрибутExceptionsу.class, JVM не перевіряє в рантаймі
Червоні прапорці (НЕ говорити):
- “Пишу
throws Exceptionвсюди для простоти” — антипатерн, клієнт не може адекватно обробити - “Не оголошую checked винятки — компілятор не помітить” — не скомпілюється
- “
throwsвпливає на продуктивність у рантаймі” — ні, це лише компіляторна перевірка - “Можна додати новий checked виняток без наслідків” — ламає бінарну сумісність
Пов’язані теми:
- [[22. Чи можна викинути checked виняток з методу без throws]] — обхід через sneaky throws
- [[2. Що таке checked exception і коли його використовувати]] — природа checked винятків
- [[3. Що таке unchecked exception (Runtime Exception)]] — чому не потребують
throws - [[28. Чи можна перехопити і викинути виняток заново]] — rethrow зі збереженням
throws - [[13. Чи можна створювати кастомні винятки]] — свої винятки для
throws