Проверяемые и непроверяемые ошибки
Часть раздела Объектно-ориентированное программирование путешествия по 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) для программных ошибок, которые не должны возникать в корректном коде.
Задание
ЛегкоДавайте создадим систему проверки возраста, которая демонстрирует ключевое различие между проверяемыми (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]"
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка10Обработка исключений в ООП
Иерархия классов исключенийПользовательские исключенияПроверяемые и непроверяемые ошибкиПаттерн Try With ResourcesПовторение — Validated User2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»