Проверяемые и непроверяемые исключения
В Java исключения делятся на два вида. Проверяемые (checked) компилятор заставляет обработать заранее, иначе программа не скомпилируется. Непроверяемые (unchecked) можно не обрабатывать — компилятор о них молчит. Это особенность именно Java: в большинстве других языков такого деления нет.
Какие исключения какие
Правило простое:
- Непроверяемые —
RuntimeException, все его наследники и ещёError. Обычно это ошибки в логике программы:NullPointerException,ArithmeticException,IllegalArgumentException. - Проверяемые — все остальные наследники
Exception. Это ситуации, которые от программиста не зависят: файла нет, сеть недоступна. Самый известный пример —IOException, ошибка ввода-вывода.
Метод Files.readString читает файл и может выбросить IOException. Поэтому без обработки компилятор его вызвать не даст:
void main() {
String text = Files.readString(Path.of("notes.txt"));
IO.println(text);
}Результат:
Main.java:2: error: unreported exception IOException; must be caught or declared to be thrown
String text = Files.readString(Path.of("notes.txt"));
^
1 error
error: compilation failedВариант 1: перехватить
Первый способ выполнить требование компилятора — обернуть вызов в try-catch:
void main() {
try {
String text = Files.readString(Path.of("notes.txt"));
IO.println(text);
} catch (IOException e) {
IO.println("Не удалось прочитать файл: " + e.getMessage());
}
}Результат:
Не удалось прочитать файл: notes.txtФайла notes.txt нет, поэтому сработал catch. Сообщение IOException здесь — просто имя файла.
Вариант 2: объявить throws
Второй способ — честно сказать в объявлении метода, что он может выбросить исключение. Для этого служит слово throws. Тогда обрабатывать ошибку будет тот, кто вызвал метод:
String readNotes() throws IOException {
return Files.readString(Path.of("notes.txt"));
}
void main() {
try {
IO.println(readNotes());
} catch (NoSuchFileException e) {
IO.println("Файла нет: " + e.getMessage());
} catch (IOException e) {
IO.println("Другая ошибка ввода-вывода");
}
}Результат:
Файла нет: notes.txtNoSuchFileException — наследник IOException, поэтому его можно перехватить отдельно. Слово throws можно написать и у main — тогда при ошибке программа просто завершится с трассировкой стека.
Непроверяемые исключения
Для RuntimeException и его наследников компилятор ничего не требует. Метод может выбросить их без всяких throws:
int percent(int part, int total) {
if (total <= 0) {
throw new IllegalArgumentException("total должен быть больше нуля");
}
return part * 100 / total;
}
void main() {
IO.println(percent(3, 4) + "%");
}Результат:
75%Непроверяемые исключения обычно не перехватывают, а исправляют код, чтобы они не возникали: проверяют null, границы массива, входные данные.
Обернуть в непроверяемое
Иногда проверяемое исключение нельзя пробросить дальше — например, внутри лямбды, которой throws не положен. Тогда его заворачивают в непроверяемое. Для IOException есть готовый класс UncheckedIOException:
void main() {
List<String> names = List.of("a.txt", "b.txt");
try {
names.forEach(name -> {
try {
IO.println(Files.readString(Path.of(name)));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (UncheckedIOException e) {
IO.println("Ошибка: " + e.getCause().getClass().getSimpleName());
}
}Результат:
Ошибка: NoSuchFileExceptionИсходное исключение сохраняется как причина (cause) и доступно через getCause().
Частая ошибка
Пустой catch, написанный лишь для того, чтобы «замолчал» компилятор, прячет ошибку:
void main() {
String text = "";
try {
text = Files.readString(Path.of("config.txt"));
} catch (IOException e) {
}
IO.println("Настройки: [" + text + "]");
}Результат:
Настройки: []Программа продолжила работу с пустыми настройками, и никто не узнал почему. Если обработать ошибку на месте нечем, добавьте throws и пусть разбирается вызывающий код, а в catch хотя бы выводите сообщение.