В чем разница между checked (проверяемыми) и unchecked (непроверяемыми) исключениями в Java?

Ответ

Различие основано на обязательности обработки на этапе компиляции.

Критерий Checked Exceptions (Проверяемые) Unchecked Exceptions (Непроверяемые / Runtime)
Наследование Классы, наследующие от java.lang.Exception, но не от RuntimeException. Классы, наследующие от java.lang.RuntimeException (а она — от Exception).
Проверка компилятором Да. Компилятор требует либо обработать исключение в try-catch, либо объявить в throws сигнатуре метода. Нет. Компилятор не требует их явного объявления или обработки.
Когда возникают Внешние, часто восстанавливаемые условия, на которые программа может повлиять (отсутствие файла, разрыв сети, ошибка БД). Внутренние ошибки программиста или логики приложения (обращение к null, неверный индекс массива, ошибка бизнес-логики).
Примеры IOException, SQLException, ClassNotFoundException, FileNotFoundException. NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException.
Best Practice Использовать для ошибок, которые разумно ожидать и от которых вызывающий код может восстановиться. Использовать для ошибок, которые указывают на баг в программе и от которых обычно нельзя корректно восстановиться.

Пример кода:

// CHECKED Исключение: Компилятор заставит его обработать.
public String readConfigFile(String path) throws IOException { // Объявляем в throws
    return Files.readString(Path.of(path)); // Может выбросить FileNotFoundException (checked)
}
// Или обрабатываем:
public String readConfigFileSafe(String path) {
    try {
        return Files.readString(Path.of(path));
    } catch (IOException e) { // Ловим checked исключение
        return "default"; // Восстанавливаемся
    }
}

// UNCHECKED Исключение: Компилятор не проверяет.
public int calculateDiscount(int price, int percent) {
    if (percent < 0 || percent > 100) {
        // Ошибка программиста/логики. Бросаем unchecked исключение.
        throw new IllegalArgumentException("Процент должен быть от 0 до 100");
    }
    return price * percent / 100;
}
// Вызывающий код может, но не обязан, ловить его.
public void applyDiscount() {
    try {
        calculateDiscount(100, 150); // Выбросит IllegalArgumentException
    } catch (IllegalArgumentException e) {
        System.err.println("Неверные входные данные: " + e.getMessage());
    }
}

Современный подход (с Java 8+): Часто предпочтение отдается unchecked-исключениям, чтобы не загромождать код. Checked-исключения уместны, когда вызывающая сторона действительно может предпринять осмысленные действия для восстановления.

Ответ 18+ 🔞

Давай разберём эту дичь, которую в академиях называют "исключениями". Сидишь ты такой, пишешь код, всё летает, и тут — бац! — всё падает. Вот это и есть исключения, только в джаве их ещё и на два лагеря поделили, будто на войне.

Смотри, вся суть в том, обязан ли компилятор тебе мозги выносить или нет.

Критерий Checked Exceptions (Проверяемые) Unchecked Exceptions (Непроверяемые / Runtime)
От кого наследуются От java.lang.Exception, но НЕ от RuntimeException. От java.lang.RuntimeException (а она, блядь, сама от Exception).
Компилятор дрочит? Овердохуища! Он как мамаша-истеричка: "Либо обработай в try-catch, либо объяви в throws, иначе я тебя не скомпилирую, мудак!" Похуй. Компилятор смотрит на это как на твои личные проблемы. Не хочешь ловить — не лови. Упадешь — сам виноват.
Когда вылезают Когда что-то пошло не так снаружи, и с этим, в теории, можно что-то сделать. Файл не найден, сеть отвалилась, база данных послала тебя нахуй. Когда ты сам, как обезьяна с гранатой, накосячил внутри логики. Обратился к null, полез за границу массива, передал в метод какую-то дичь.
Примеры IOException, SQLException, ClassNotFoundException. NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException.
Как жить с этим Использовать, только если ты реально думаешь, что вызывающий код сможет с этой ошибкой что-то поделать (например, спросить у пользователя другой путь к файлу). Использовать для всего остального. Если ошибка — это твой косяк или невалидные данные, от которых не оправиться.

Ну и примеры, чтобы совсем пиздец стало понятно:

// CHECKED исключение. Компилятор будет орать, пока не сделаешь как он хочет.
public String readConfigFile(String path) throws IOException { // Объявляем в throws, типа "предупреждаю!"
    return Files.readString(Path.of(path)); // Может выбросить FileNotFoundException
}
// Или обрабатываем на месте:
public String readConfigFileSafe(String path) {
    try {
        return Files.readString(Path.of(path));
    } catch (IOException e) { // Ловим эту проверяемую заразу
        return "default"; // И как-то выкручиваемся
    }
}

// UNCHECKED исключение. Полная анархия, делай что хочешь.
public int calculateDiscount(int price, int percent) {
    if (percent < 0 || percent > 100) {
        // Ты чё, больной? Процент не может быть таким. Получай unchecked исключение!
        throw new IllegalArgumentException("Процент должен быть от 0 до 100, а не " + percent);
    }
    return price * percent / 100;
}
// А вызывающий код может быть похуистом:
public void applyDiscount() {
    try { // А может и не быть этого try-catch, компилятору всё равно
        calculateDiscount(100, 150); // Выбросит IllegalArgumentException
    } catch (IllegalArgumentException e) {
        System.err.println("Чувак, ты что-то попутал: " + e.getMessage());
    }
}

А теперь модный тренд, ёпта: Сейчас многие считают, что checked-исключения — это пережиток, который только код пухлым делает. Чаще кидают unchecked, чтобы не засирать сигнатуры методов этими throws. Checked оставляют только для ситуаций, где без них реально никак — когда ошибка ожидаемая и от неё можно по-человечески оправиться. А если всё летит в тартарары — значит, так и надо, пусть падает.