Menu
Coddy logo textTech

Проверяемые и непроверяемые ошибки

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 61 из 87.

Вы видели, что иерархия исключений Java разделяется на разные ветви. Ключевое различие, которое вам необходимо понять, — это разница между checked (проверяемыми) и unchecked (непроверяемыми) исключениями. Это влияет на то, как вы должны обрабатывать их в своем коде.

Проверяемые исключения — это исключения, которые компилятор заставляет вас обрабатывать. Они наследуются напрямую от Exception (не через RuntimeException). Вы должны либо перехватить их, либо объявить их с помощью throws:

// Необходимо обработать IOException — это проверяемое исключение
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);  // Может выбросить IOException
}

// Или перехватить его
public void readFileSafe(String path) {
    try {
        FileReader reader = new FileReader(path);
    } catch (IOException e) {
        System.out.println("File error: " + e.getMessage());
    }
}

Непроверяемые исключения наследуют RuntimeException. Компилятор не требует их обработки — обычно они указывают на ошибки программирования, такие как обращения по пустой ссылке или недопустимый доступ к элементам массива:

// Объявление throws не требуется
public int divide(int a, int b) {
    return a / b;  // Может выбросить ArithmeticException (непроверяемое)
}

String name = null;
name.length();  // NullPointerException (непроверяемое)

При создании пользовательских исключений выбирайте родительский класс в зависимости от того, как вызывающая сторона должна их обрабатывать. Используйте проверяемые исключения (checked exceptions) для восстановимых условий, которые вызывающая сторона должна предвидеть. Используйте непроверяемые исключения (unchecked exceptions) для программных ошибок, которые не должны возникать в корректном коде.

challenge icon

Задание

Легко

Давайте создадим систему проверки возраста, которая демонстрирует ключевое различие между проверяемыми (checked) и непроверяемыми (unchecked) исключениями! Вы создадите пользовательские исключения обоих типов и увидите, как компилятор обрабатывает их по-разному.

Вы организуете свой код в четырех файлах:

  • InvalidAgeException.java: Создайте проверяемое исключение для ошибок валидации возраста. Это исключение должно наследоваться напрямую от Exception, что означает, что вызывающая сторона будет обязана его обработать. Включите конструктор, который принимает сообщение и передает его родительскому классу.
  • NegativeAgeException.java: Создайте непроверяемое исключение для случаев, когда кто-то указывает отрицательный возраст (ошибка программирования, которой не должно быть при корректном вводе). Оно должно наследоваться от RuntimeException. Включите конструктор, который принимает и передает сообщение родителю.
  • AgeVerifier.java: Создайте класс, который проверяет возраст, используя оба типа исключений. Включите два статических метода:

    verifyAge(int age) - Этот метод должен выбрасывать NegativeAgeException (непроверяемое) с сообщением "Age cannot be negative: " + age, если возраст меньше 0. Если возраст меньше 18, выбросьте InvalidAgeException (проверяемое) с сообщением "Must be 18 or older: " + age. В противном случае выведите "Age " + age + " verified successfully". Поскольку этот метод выбрасывает проверяемое исключение, вы должны объявить его с помощью throws InvalidAgeException.

    verifyAgeUncheckedOnly(int age) - Этот метод использует только непроверяемые исключения. Если возраст отрицательный, выбросьте NegativeAgeException с тем же форматом сообщения. Если возраст меньше 18, выбросьте IllegalArgumentException с сообщением "Too young: " + age. В противном случае выведите "Age " + age + " verified (unchecked method)". Обратите внимание, что этому методу не требуется объявление throws!

  • Main.java: Объедините вашу систему проверки! Вы получите один входной параметр: возраст (целое число).

    Сначала выведите "=== Checked Exception Method ===" и вызовите verifyAge с введенным возрастом. Поскольку он выбрасывает проверяемое исключение, вы должны обернуть его в try-catch. Перехватите InvalidAgeException и выведите "Checked exception caught: " + e.getMessage(). Также перехватите NegativeAgeException и выведите "Unchecked exception caught: " + e.getMessage().

    Затем выведите пустую строку и "=== Unchecked Exception Method ===". Вызовите verifyAgeUncheckedOnly с тем же возрастом. Хотя этот метод может выбрасывать исключения, вы не обязаны их перехватывать. Тем не менее, все равно оберните его в try-catch для корректной обработки ошибок. Перехватите NegativeAgeException и выведите "Runtime exception caught: " + e.getMessage(). Перехватите IllegalArgumentException и выведите "Illegal argument caught: " + e.getMessage().

Вы получите один входной параметр: целое число, представляющее возраст для проверки.

Обратите внимание на ключевое различие: метод с проверяемым исключением требует объявления throws и заставляет вызывающую сторону обрабатывать его, в то время как непроверяемый метод компилируется без проблем без какой-либо обработки исключений. Это фундаментальное различие между проверяемыми и непроверяемыми исключениями в Java!

Шпаргалка

Исключения в Java делятся на проверяемые (checked) и непроверяемые (unchecked), что определяет, как компилятор контролирует их обработку.

Проверяемые исключения наследуются напрямую от Exception (не через RuntimeException). Компилятор заставляет вас либо перехватывать их, либо объявлять их с помощью throws:

// Необходимо объявить с помощью throws
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
}

// Или перехватить его
public void readFileSafe(String path) {
    try {
        FileReader reader = new FileReader(path);
    } catch (IOException e) {
        System.out.println("File error: " + e.getMessage());
    }
}

Непроверяемые исключения наследуются от RuntimeException. Компилятор не требует их обязательной обработки. Обычно они указывают на ошибки программирования:

// Объявление throws не требуется
public int divide(int a, int b) {
    return a / b;  // Может выбросить ArithmeticException (непроверяемое)
}

String name = null;
name.length();  // NullPointerException (непроверяемое)

При создании собственных исключений выбирайте родительский класс в зависимости от того, как вызывающая сторона должна их обрабатывать:

  • Используйте проверяемые исключения (наследуйте Exception) для восстановимых условий, которые вызывающая сторона должна предвидеть
  • Используйте непроверяемые исключения (наследуйте RuntimeException) для ошибок программирования, которых не должно быть в корректном коде

Попробуйте сами

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int age = scanner.nextInt();
        
        // TODO: Протестируйте метод с проверяемым исключением
        // Выведите: "=== Checked Exception Method ==="
        // Вызовите AgeVerifier.verifyAge(age) в блоке try-catch
        // - Перехватите InvalidAgeException: выведите "Checked exception caught: [message]"
        // - Перехватите NegativeAgeException: выведите "Unchecked exception caught: [message]"
        
        
        // TODO: Выведите пустую строку, затем "=== Unchecked Exception Method ==="
        // Вызовите AgeVerifier.verifyAgeUncheckedOnly(age) в блоке try-catch
        // - Перехватите NegativeAgeException: выведите "Runtime exception caught: [message]"
        // - Перехватите IllegalArgumentException: выведите "Illegal argument caught: [message]"
        
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование