Как компилятор Java реагирует на непроверенные проверяемые исключения (checked exceptions)?

Ответ

Компилятор Java выполняет статическую проверку (compile-time checking) для проверяемых исключений (классы, наследующие Exception, но не RuntimeException).

Правило: Если метод может сгенерировать проверяемое исключение, он должен либо:

  1. Объявить его в своей сигнатуре с помощью throws.
  2. Обработать его внутри с помощью блока try-catch.

Пример 1: Объявление исключения

public void readFile(String path) throws IOException {
    // Код, который может вызвать FileNotFoundException (наследник IOException)
    Files.readAllLines(Paths.get(path));
}

Пример 2: Обработка исключения

public void readFile(String path) {
    try {
        Files.readAllLines(Paths.get(path));
    } catch (IOException e) { // IOException перехвачено и обработано
        System.err.println("Файл не найден: " + e.getMessage());
    }
}

Если правило нарушено, компилятор выдаст ошибку: unreported exception <ExceptionClass>; must be caught or declared to be thrown

Важно: Это правило не применяется к непроверяемым исключениям (RuntimeException, например, NullPointerException) и ошибкам (Error, например, OutOfMemoryError).

Ответ 18+ 🔞

Ну слушай, вот есть у нас в Java эта вся ебля с исключениями. Так вот, компилятор — он как занудный охранник в стрёмной конторе: если ты хочешь выкинуть проверяемое исключение (это те, что наследуются от Exception, но не от RuntimeException), то ты должен либо предупредить всех заранее, либо разобраться с ним на месте, блядь.

Короче, правило такое, ёпта:
Если твой метод может выкинуть проверяемое исключение, то ты либо:

  1. Объявляешь его в сигнатуре через throws — типа, "ребята, я тут такое могу устроить, будьте готовы".
  2. Обрабатываешь его сразу через try-catch — то есть сам всё замял, и никто даже не узнал.

Пример первый: предупреждаем, как честные люди

public void readFile(String path) throws IOException {
    // Тут может вылезти FileNotFoundException (а он наследник IOException)
    Files.readAllLines(Paths.get(path));
}

Вот смотри: метод говорит — "да, я могу кинуть IOException, потому что файл может быть не найден, например. Кто меня вызывает — тот пусть сам решает, что с этим делать".

Пример второй: сами всё похороним

public void readFile(String path) {
    try {
        Files.readAllLines(Paths.get(path));
    } catch (IOException e) { // Поймали эту гадость и тихо обработали
        System.err.println("Файл не найден, ёпта: " + e.getMessage());
    }
}

А тут мы сами всё порешали внутри метода. Вызвал кто-то — и даже не узнал, что там что-то пошло не так (ну, кроме сообщения в консоль, конечно).

А если проигнорируешь это правило?
Компилятор тебе сразу: unreported exception <ExceptionClass>; must be caught or declared to be thrown.
Перевод: "Мудак, ты либо поймай это исключение, либо объяви, что ты его кидаешь! Иначе не скомпилирую, иди нахуй".

Важный момент, блядь: это всё не касается непроверяемых исключений (RuntimeException, типа NullPointerException) и ошибок (Error, типа OutOfMemoryError). Их можно кидать как угодно, и компилятор даже бровью не поведёт — потому что они обычно от твоей криворукости или системных проблем, а не от логики работы метода.