Как устроена иерархия исключений в Java?

Ответ

Все исключения и ошибки в Java происходят от общего предка — класса Throwable.

Основные ветви иерархии:

  • Error: Критические ошибки виртуальной машины (например, OutOfMemoryError). Обычно не обрабатываются в прикладном коде.
  • Exception: Базовый класс для исключений приложения. Делится на:
    • Проверяемые (checked) исключения (например, IOException). Требуют обязательной обработки.
    • Непроверяемые (unchecked) исключенияRuntimeException и его наследники (например, NullPointerException). Обработка не обязательна.

Ключевое отличие: проверяемые исключения контролируются на этапе компиляции, что заставляет разработчика явно предусмотреть их обработку.

Пример объявления проверяемого исключения:

// Метод объявляет, что может выбросить IOException
public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

Пример обработки:

try {
    String content = readFile("config.yaml");
} catch (IOException e) { // Обязательная обработка проверяемого исключения
    // Логируем ошибку и возвращаем значение по умолчанию
    logger.error("Не удалось прочитать файл", e);
    return "default";
}

Ответ 18+ 🔞

А, слушай, смотри, вся эта ваша иерархия исключений в Java — она как родословная у какого-нибудь блядского семейства, где все друг на друга похожи, но одни — просто неудачники, а другие — реально конченые, на уровне системы.

Вот есть общий папаша — Throwable. От него, сука, два отпрыска пошли.

Первый — Error. Это типа тот родственник, который приезжает на семейный ужин и начинает ломать мебель, потому что у него в голове, блядь, OutOfMemoryError. С ним не договоришься, его не обработаешь нормально — просто выкинуть нахуй из дома и ждать, пока система не накроется медным тазом. Это ошибки виртуальной машины, с ними в прикладном коде не борются.

А второй — Exception. Вот это уже наш, прикладной, так сказать, бытовой пиздец. Он тоже делится.

Есть проверяемые (checked) исключения. Это как мама, которая звонит тебе каждые пять минут: «А ты взял зонтик? А ты точно обработал IOException?». Компилятор, сука, стоит у тебя над душой и не даст собраться, пока ты не признаешь, что метод может выбросить эту хуйню, и не приготовишь try-catch или throws. Зато безопасно, блядь. Предусмотрительно.

// Метод как бы говорит: «Предупреждаю, я могу обосраться с файлом!»
public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

А есть непроверяемые (unchecked) — это RuntimeException и его банда. NullPointerException, ArrayIndexOutOfBoundsException... Это как твой кореш, который может в любой момент, ебать, нахулиганить. Никто тебя заранее не предупредит, компилятор махнёт рукой. Но если накосячит — получишь исключение прямо в рантайме, в лоб. Обрабатывай, если хочешь, но тебя к этому не принуждают. Свобода, блядь, она всегда риск.

Пример, как с этим жить, если ты не мудак:

try {
    String content = readFile("config.yaml"); // Пытаемся прочитать
} catch (IOException e) { // Ага! Ловим эту обязательную проверяемую заразу
    // Ну всё, обосрались. Хотя бы логируем, как взрослые люди.
    logger.error("Не удалось прочитать файл", e);
    return "default"; // И даём хоть что-то, чтоб не падать дальше
}

Вот и вся философия. Либо ты предвидишь пиздец и готовишься (checked), либо живёшь на удачу и ловишь сюрпризы в рантайме (unchecked). А Error... Ну, с Error просто смирись, это как метеорит упал на дом. Ёпта.