Что делает ключевое слово 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 исключение без последствий” — ломает бинарную совместимость
Связанные темы:
- [[21. Можно ли пробросить checked exception из метода без throws]] — обход через sneaky throws
- [[2. Что такое checked exception и когда его использовать]] — природа checked исключений
- [[3. Что такое unchecked exception (Runtime Exception)]] — почему не требуют
throws - [[27. Можно ли повторно бросить исключение]] — rethrow с сохранением
throws - [[13. Можно ли создавать кастомные исключения]] — свои исключения для
throws