Вопрос 21 · Раздел 7

Что делает ключевое слово throws?

throws — это ключевое слово в сигнатуре метода, которое документирует контракт: оно сообщает вызывающему коду, какие checked исключения метод может выбросить и которые вызывающи...

Версии по языкам: English Russian Ukrainian

Junior Level

Определение

throws — это ключевое слово в сигнатуре метода, которое документирует контракт: оно сообщает вызывающему коду, какие checked исключения метод может выбросить и которые вызывающий код обязан быть готов обработать.

public void readFile() throws IOException {
    Files.readAllLines(Paths.get("file.txt"));
}

Что это значит

Метод говорит вызывающему коду: “Я могу не справиться, и ты обязан знать об этом”.

Вызывающий код должен:

  1. Обернуть вызов в try-catch
  2. Или тоже добавить 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

  1. В публичном API библиотекthrows Exception заставляет клиента ловить “всё подряд”
  2. В интерфейсах с множеством checked исключений — признак нарушения SRP, разбейте интерфейс
  3. В Spring REST контроллерах — используйте @ControllerAdvice вместо throws в каждом методе
  4. В лямбда-выражениях — функциональные интерфейсы не поддерживают checked исключения
  5. Для ошибок программирования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