Иерархия исключений, try-catch-finally, создание собственных исключений.
Исключение (Exception) — это событие, которое происходит во время выполнения программы и нарушает нормальный поток инструкций.
Аналогия
Представь, что ты готовишь по рецепту. Внезапно обнаруживаешь, что закончились яйца. У тебя есть варианты:
Исключения — это именно такой механизм. Они позволяют отделить «нормальную» логику от обработки ошибок.
// ❌ БЕЗ исключений — код превращается в лапшу
public int divide(int a, int b) {
if (b == 0) {
System.out.println("Ошибка: деление на ноль");
return -1; // а что, если -1 — это валидный результат?
}
return a / b;
}
// ✅ С исключениями — чистая логика
public int divide(int a, int b) {
return a / b; // Java сама бросит ArithmeticException при b == 0
}Все исключения в Java — это объекты, унаследованные от класса Throwable. Давай посмотрим на иерархию:
Throwable
/ \
Error Exception
|
-----------------
| |
RuntimeException Checked Exceptions
(unchecked) (IOException, SQLException...)Базовый класс для всего, что можно «бросить» с помощью throw.
Ошибки, которые не нужно ловить — они означают, что с JVM что-то не так.
| Пример | Что означает |
|---|---|
| OutOfMemoryError | Закончилась память |
| StackOverflowError | Бесконечная рекурсия |
| NoClassDefFoundError | Класс не найден |
💡 Правило: Error мы не обрабатываем. Если JVM «умирает» — программа должна упасть.
Делятся на два типа:
Компилятор требует их обработать: либо поймать в try-catch, либо объявить в throws.
Примеры:
// ❌ Компилятор не скомпилирует!
public void readFile() {
FileReader fr = new FileReader("file.txt"); // FileNotFoundException
}
// ✅ Нужно обработать
public void readFile() throws IOException {
FileReader fr = new FileReader("file.txt");
}Компилятор не требует их обрабатывать, но они могут возникнуть в рантайме. Обычно это ошибки программиста.
| Исключение | Когда возникает |
|---|---|
| NullPointerException | Обращение к null |
| ArrayIndexOutOfBoundsException | Выход за границы массива |
| ArithmeticException | Деление на ноль |
| ClassCastException | Неверное приведение типов |
| IllegalArgumentException | Некорректный аргумент метода |
| NumberFormatException | Невозможно преобразовать строку в число |
// Компилятор не ругается, но упадёт в рантайме
int[] arr = {1, 2, 3};
int x = arr[10]; // 💥 ArrayIndexOutOfBoundsException| Тип | Проверка компилятором | Пример | Обрабатывать обязательно? |
|---|---|---|---|
| Error | Нет | OutOfMemoryError | ❌ Не стоит |
| Checked Exception | ✅ Да | IOException | ✅ Обязательно |
| RuntimeException | ❌ Нет | NullPointerException | По ситуации |
try {
// Код, который МОЖЕТ бросить исключение
} catch (ТипИсключения e) {
// Что делать, если произошло это исключение
} finally {
// Выполнится ВСЕГДА, было исключение или нет
}public class DivisionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("Введите первое число: ");
int a = Integer.parseInt(scanner.nextLine());
System.out.print("Введите второе число: ");
int b = Integer.parseInt(scanner.nextLine());
int result = a / b;
System.out.println("Результат: " + result);
} catch (NumberFormatException e) {
System.out.println("Ошибка: введено не число!");
} catch (ArithmeticException e) {
System.out.println("Ошибка: деление на ноль!");
} catch (Exception e) {
// Ловим всё остальное (должен быть ПОСЛЕДНИМ!)
System.out.println("Неизвестная ошибка: " + e.getMessage());
} finally {
System.out.println("Спасибо за использование программы!");
}
}
}try {
// ...
} catch (FileNotFoundException e) { // сначала конкретное
// ...
} catch (IOException e) { // потом его родитель
// ...
} catch (Exception e) { // в конце — самое общее
// ...
}Если несколько исключений обрабатываются одинаково:
try {
// ...
} catch (NumberFormatException | ArithmeticException e) {
System.out.println("Ошибка вычислений: " + e.getMessage());
}try {
int[] arr = {1, 2, 3};
System.out.println(arr[10]);
} catch (Exception e) {
System.out.println(e.getMessage()); // Короткое описание: "Index 10 out of bounds..."
System.out.println(e.toString()); // Тип + сообщение
e.printStackTrace(); // Полный стек вызовов (в консоль ошибок)
}|Ситуация|finally выполнится?| |Исключения не было|✅ Да| |Исключение было и поймано|✅ Да| |Исключение было и НЕ поймано|✅ Да (перед падением)| |В try был return|✅ Да (перед выходом)| |Вызван System.exit(0)|❌ Нет| |Убита JVM|❌ Нет|
public static int test() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.println("finally"); // выведется ПЕРЕД возвратом 1
}
}💡 Когда использовать finally: для освобождения ресурсов — закрытия файлов, соединений с БД, сетевых сокетов. Но в современном Java для этого лучше использовать try-with-resources 👇
Если ресурс реализует интерфейс AutoCloseable, его можно объявить прямо в try, и он автоматически закроется.
FileReader reader = null;
try {
reader = new FileReader("file.txt");
// читаем файл...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // может бросить своё исключение!
} catch (IOException e) {
e.printStackTrace();
}
}
}try (FileReader reader = new FileReader("file.txt");
BufferedReader br = new BufferedReader(reader)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// reader и br ЗАКРОЮТСЯ АВТОМАТИЧЕСКИ, даже если было исключение💡 Правило: всегда используй try-with-resources для работы с файлами, соединениями, потоками. Это короче, чище и безопаснее.
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Некорректный возраст: " + age);
}
this.age = age;
}// Checked exception — ОБЯЗАТЕЛЬНО объявляем в сигнатуре
public void readFile(String path) throws IOException {
FileReader fr = new FileReader(path);
// ...
}
// Unchecked — можно не объявлять, но можно (для документации)
public void divide(int a, int b) throws ArithmeticException {
// ...
}| Ключевое слово | Где используется | Что делает |
|---|---|---|
| throw | В теле метода | Бросает объект исключения |
| throws | В сигнатуре метода | Объявляет, какие исключения может бросить |
public void processFile(String path) throws IOException { // throws — объявление
if (path == null) {
throw new IllegalArgumentException("Path is null"); // throw — бросок
}
FileReader fr = new FileReader(path); // сам бросает IOException
}Иногда стандартных исключений не хватает. Например, тебе нужно специальное исключение InsufficientFundsException для банковского аккаунта.
// CHECKED — наследуемся от Exception
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(String message, double amount) {
super(message);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
// UNCHECKED — наследуемся от RuntimeException
public class InvalidUserInputException extends RuntimeException {
public InvalidUserInputException(String message) {
super(message);
}
}Тип Когда использовать Checked (extends Exception) Когда вызывающий код обязан отреагировать (например, «недостаточно средств», «файл не найден») Unchecked (extends RuntimeException) Когда это ошибка программиста или ситуация, которую нельзя предсказать (например, «неверный аргумент»)
💡 Современный тренд: в Java всё чаще используют RuntimeException. Checked-исключения считаются многими разработчиками «тяжёлыми» — они загроможждают код.
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Недостаточно средств. Не хватает: " + (amount - balance),
amount - balance
);
}
balance -= amount;
}
public static void main(String[] args) {
BankAccount account = new BankAccount(1000);
try {
account.withdraw(1500);
} catch (InsufficientFundsException e) {
System.out.println("Ошибка: " + e.getMessage());
System.out.println("Не хватает: " + e.getAmount());
}
}
}Иногда одно исключение вызывает другое. Чтобы не потерять информацию об оригинале, используй цепочку:
public class UserRepository {
public User getUser(int id) throws UserNotFoundException {
try {
// ... запрос к БД
} catch (SQLException e) {
// Упаковываем SQLException в наше исключение
throw new UserNotFoundException("Пользователь " + id + " не найден", e);
}
}
}
public class UserNotFoundException extends Exception {
public UserNotFoundException(String message, Throwable cause) {
super(message, cause); // cause — оригинальное исключение
}
}Теперь в стеке будет видно и UserNotFoundException, и его причину — SQLException.
public class EmailValidator {
public static void validate(String email) throws InvalidEmailException {
if (email == null) {
throw new InvalidEmailException("Email не может быть null");
}
if (!email.contains("@")) {
throw new InvalidEmailException("Email должен содержать @");
}
if (!email.contains(".")) {
throw new InvalidEmailException("Email должен содержать домен");
}
}
}
public class InvalidEmailException extends Exception {
public InvalidEmailException(String message) {
super(message);
}
}public class Calculator {
public static double calculate(String expression) {
try {
String[] parts = expression.split(" ");
if (parts.length != 3) {
throw new IllegalArgumentException("Формат: число операция число");
}
double a = Double.parseDouble(parts[0]);
String op = parts[1];
double b = Double.parseDouble(parts[2]);
return switch (op) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> {
if (b == 0) throw new ArithmeticException("Деление на ноль");
yield a / b;
}
default -> throw new UnsupportedOperationException("Операция: " + op);
};
} catch (NumberFormatException e) {
System.out.println("Ошибка: некорректные числа");
} catch (ArithmeticException e) {
System.out.println("Ошибка: " + e.getMessage());
} catch (Exception e) {
System.out.println("Неизвестная ошибка: " + e.getMessage());
}
return Double.NaN;
}
public static void main(String[] args) {
System.out.println(calculate("10 + 5")); // 15.0
System.out.println(calculate("10 / 0")); // Ошибка + NaN
System.out.println(calculate("abc + 5")); // Ошибка + NaN
}
}public class FileProcessor {
public static List<String> readLines(String path) throws IOException {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} catch (FileNotFoundException e) {
System.out.println("Файл не найден: " + path);
throw e; // пробрасываем дальше
} catch (IOException e) {
System.out.println("Ошибка чтения: " + e.getMessage());
throw e;
}
return lines;
}
}// ❌ ПЛОХО
try {
doSomething();
} catch (Exception e) {
// ничего не делаем — ошибка исчезает бесследно!
}
// ✅ ХОРОШО
try {
doSomething();
} catch (SpecificException e) {
log.error("Ошибка при ...", e);
// или: переопределить, уведомить пользователя, retry и т.д.
}Exception — это проблема, которая может возникнуть в runtime. Это исключительная ситуация, которая возникает из-за каких-то причин.
Диаграмма наследования исключений выглядит так (нужно знать ее назубок ;) ):

На диаграмме видно, что в целом все исключения делятся на две группы — exceptions и error.
Error — используются JVM для отображения ошибок, после которых работа приложения уже не имеет смысла. Например StackOverFlowError, которая говорит, что стек заполнен и программа уже не может работать.
Exception — исключения, которые генерируются программно в коде. Есть разные исключения, проверяемые и непроверяемые, но главное, что они есть, и их можно перехватить и продолжить работу приложения.
Exceptions, в свою очередь, еще делятся на тех, кто наследуется от RuntimeException и других наследников Exception.
В рамках этого вопроса информации достаточно. О том, что такое проверяемые / непроверяемые исключения, поговорим ниже.
Как это работает? Как только где-то создается исключение, runtime создает Exception Object (обозначим как ExcObj). В нем хранится вся необходимая для работы информация — само исключение, которое вызывалось и место, где это произошло.
Создание ExcObj и передача в runtime есть ничто иное как “выбрасывание исключения”.
ExcObj содержит методы, по которым можно дойти до место создания исключения. Это множество методов называется Call Stack.
Далее, runtime система ищет метод в Call Stack, который сможет обработать наше исключение.
Если он таки находит соответствующий обработчик, то есть тип исключения совпадает с типом в обработчике, все хорошо.
Если не находит, то runtime передает всё в default exception handler, который подготавливает ответ и завершает работу.
Как это выглядит наглядно:
/**
* Пример, в котором показываются две опции — когда находится обработчик для исключения и когда нет.
*/
class ThrowerExceptionExample {
public static void main(String[] args) throws IllegalAccessException {
ThrowerExceptionExample example = new ThrowerExceptionExample();
System.out.println(example.populateString());
}
/**
* Здесь происходит перехват одного из возможных исключений — {@link IOException}.
* А вот второй будет пробрасываться дальше вверх по вызову.
*/
private String populateString() throws IllegalAccessException {
try {
return randomThrower();
} catch (IOException e) {
return "Caught IOException";
}
}
/**
* Здесь две опции: или бросается {@link IOException} или {@link IllegalAccessException}.
* Выбирается случайным образом.
*/
private String randomThrower() throws IOException, IllegalAccessException {
if (new Random().nextBoolean()) {
throw new IOException();
} else {
throw new IllegalAccessException();
}
}
}В нашем случае CallStack схематично будет иметь вид:
randomThrower() => populateString() => main(String[] args)
Есть две опции: случайным образом будет выброшено одно или другое исключение. Для IOException все ок, если будет сгенерировано оно, то результатом работы будет: "Caught IOException".
А вот если будет второе исключение, обработчика на которого нет, программа будет остановлена с таким выводом:
Exception in thread "main" java.lang.IllegalAccessException
at ThrowerExceptionExample.randomThrower(CollectionExample.java:38)
at ThrowerExceptionExample.populateString(CollectionExample.java:24)
at ThrowerExceptionExample.main(CollectionExample.java:15)В вопросах выше уже использовались те или иные ключевые слова для работы с исключениями, теперь нужно поговорить о них более подробно.
Какие есть ключевые слова?
Важно отметить, что catch, throw и throws можно использовать только с java.lang.Throwable. С другими типами работать это не будет.
Сейчас обсудим именно try, catch и finally.
Вот как это выглядит:
try {
// сюда передают тот код, который может вызвать исключение.
} catch (IOException e) {
// первый catch блок, который принимает IOException и все его подтипы(потомки).
// Например, нет файла при чтении, выпадает FileNotFoundException, и мы уже соответствующе
// обрабатываем это.
} catch (IllegalAccessException e) {
// если нужно, можно добавить больше одного catch блока, в этом нет проблем.
} catch (OneException | TwoException e) {
// можно даже объединять несколько в один блок
} catch (Throwable t) {
// а можно сказать, что мы принимаем ВСЁ))))
} finally {
// этот блок может быть, а может и не быть.
// и он точно выполнится.
}throw используют в случае, когда нужно явно создать новое исключение. Применяют его для создания и выбрасывания пользовательских исключений.
Например, исключения, связанные с валидацией. Обычно для валидации наследуются от RuntimeException.
Пример:
// пример пробрасывания исключения
throw new RuntimeException("because I can :D");Важно, что использовать эту конструкцию можно только тем, что наследуется от Throwable. То есть, нельзя сказать так:
throw new String("как тебе такое, Илон Маск?");Далее, работа потока обрывается и начинается поиск обработчика, который смог бы обработать его. Когда не находит, идет к методу, который вызвал его, и так поиск будет идти наверх по строке вызовов пока либо не найдет соответствующий обработчик, либо оставит работу приложения.
Смотрим:
// Пример, который демонстрирует работу throw
class ThrowExample {
void willThrow() throws IOException {
throw new IOException("Because I Can!");
}
void doSomething() {
System.out.println("Doing something");
try {
willThrow();
} catch (IOException e) {
System.out.println("IOException was successfully handled.");
}
}
public static void main(String args[]) {
ThrowExample throwExample = new ThrowExample();
throwExample.doSomething();
}
}Если запустить программу, получим такой результат:
Doing something
IOException was successfully handled.throws — механизм, при помощи которого метод может выбрасывать одно или более исключений. Добавляются они через запятую.
Смотрим как это легко и просто:
private Object willThrow() throws RuntimeException, IOException, FileNotFoundExceptionПричем важно отметить, что могут быть как проверяемые таки непроверяемые исключения.
Разумеется, что непроверяемые исключения можно и не добавлять в throws, но правила хорошего тона говорят об обратном.
Если это проверяемые, то используя метод, который их генерирует, нужно как-то его обработать. Есть два варианта:
B Java есть два типа исключений — checked и unchecked.
Это исключения, которые проверяются во время компиляции. Если какой-то код в методе во время исключения выдает checked исключение, метод обязан либо обработать его при помощи try-catch, либо пробросить его дальше
На примере, который считывает картинку из пути "/users/romankh3/image.png", обновляет ее каким-то образом(для нас это не важно) и сохраняет ее обратно.
class CheckedImageExample {
public static void main(String[] args) {
File imageFile = new File("/users/romankh3/image.png");
BufferedImage image = ImageIO.read(imageFile);
updateAndSaveImage(image, imageFile);
}
private static void updateAndSaveImage(BufferedImage image, File imageFile) {
ImageIO.write(image, "png", imageFile);
}
}Такой код компилироваться не будет, так как статические методы ImageIO.read() и ImageIO.write() выбрасывают исключение IOException, которое является checked (проверяемым) и должно соответственно быть обработанным.
Здесь две опции, которые мы уже обсудили выше: или использовать try-catch, или пробросить дальше. Для лучшего усвоения сделаем и так, и эдак.
То есть в методе updateAndSave просто пробросим, а уже в главном методе воспользуемся try-catch:
class CheckedImageExample {
public static void main(String[] args) {
File imageFile = new File("/users/romankh3/image.png");
try {
BufferedImage image = ImageIO.read(imageFile);
updateAndSaveImage(image, imageFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void updateAndSaveImage(BufferedImage image, File imageFile) throws IOException {
ImageIO.write(image, "png", imageFile);
}
}Это те исключения, которые на этапе компиляции не проверяются. То есть метод может генерировать RuntimeException, а компилятор не напомнит каким-то образом это обработать.
Как показано ниже, у нас все, кто наследуются от RuntimeException и Error являются непроверяемыми.

Рассмотрим следующую Java-программу. Код прекрасно компилируется, но при запуске выдает исключение ArrayIndexOutOfBoundsException. Компилятор позволяет его компилировать, потому что ArrayIndexOutOfBoundsException является непроверенным исключением.
Обычная ситуация с массивом, которая может быть:
class CheckedImageExample {
public static void main(String[] args) {
int[] array = new int[3];
array[5] = 12;
}
}Результатом будет:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at main(CheckedImageExample.java:12)Кстати, вы уже заметили, что в Java короткие имена никто не дает? Чем больше, тем лучше. Он, Spring Framework, в этом при успел очень сильно: возьми только какой-нибудь BeanFactoryPostProcessor класс)))
Это механизм, при помощи которого нужно правильно закрывать все ресурсы.
Как-то не понятно, да?)
Для начала, что такое ресурс...
Ресурс — это объект, после работы с которым нужно закрыть его, то есть вызвать метод close(). Ресурсом называются все объекты, которые реализуют интерфейс AutoClosable, который, в свою очередь реализует интерфейс Closeable.
Для нас важно понять, что все InputStream, OutpuStream являются ресурсами и их нужно правильно и успешно высвобождать. Вот как раз для этого и нужно нам использовать try-with-resource конструкцию.
Вот как она выглядит:
private void unzipFile(File zipFile) throws IOException {
try(ZipInputStream zipOutputStream = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry zipEntry = zipOutputStream.getNextEntry();
while (zipEntry != null) {
}
}
}
private void saveZipEntry(ZipEntry zipEntry) {
// логика сохранения
}Вот этом примере ресурс — это ZipInputStream, после работы с которым нужно будет закрыть его. И чтоб не думать о том, что нужно вызвать метод close(), мы просто определяем эту переменную в блоке try, как показано в примере и в рамках этого блока выполняем все необходимое. Что делает пример? Он разархивирует zip архив. Для этого нужно воспользоваться InputStream’ом.
Определять можно больше одной переменной, разделяют их точкой с запятой.
Как правило при использовании try-catch-finally блок final применяли для закрытия ресурсов. В Java 7 появился новый вид оператора try-with-resources, аналог try-catch-finally для освобождения ресурсов, но более компактный и удобочитаемый.
Давайте вспомним, как выглядит try-catch-finally:
String text = "some text......";
BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new FileWriter("someFileName"));
bufferedWriter.write(text);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}А теперь давайте перепишем этот код, но с использованием try-with-resources:
String text = "some text......";
try(BufferedWriter bufferedWriter =new BufferedWriter(new FileWriter("someFileName"))) {
bufferedWriter.write(text);
} catch (IOException e) {
e.printStackTrace();
}Как-то проще стало, не находите? Помимо упрощения, ещё есть пара моментов:
Метод close выполняется в неявном finally блоке, иначе как программа поймет, как именно данный закрывать ресурс?
Но, скорее всего, вы редко будете писать свои имплементации ресурсов и их метод закрытия.
Последовательность выполнения блоков:
Как правило исключения, которые выпали ниже по списку, перебивают те, что выпали выше.
Представьте ситуацию, что при использовании try-catch-finally у вас в try падает исключение. Соответственно, сразу начинает исполняться определенный блок catch, в котором вы прописываете другое исключение (например, с сообщением, которое более подробно описывает ошибку), и вы хотите, чтобы метод прокидывал это исключение дальше. Следом идет выполнение блока finally, и в нём тоже падает исключение. Но уже другое.
Какое же из этих двух исключений в итоге выбросит данный метод? Исключение, которое выбросил блок finally!
Но и тут есть один момент с try-with-resources.
Теперь рассмотрим поведение try-with-resources в этой же ситуации. У нас падает исключение в блоке try при попытке закрытия ресурсов в методе close(), то есть в неявном finally.
Какое же из этих исключений отловит catch? То, которое выбросил блок try! Исключение из неявного finally (из метода close()) будет игнорироваться. Это игнорирование еще называют подавлением исключений.
Как исключения, так и ошибки являются подклассами класса Throwable. Тем не менее, они имеют свои различия.
Ошибка указывает на проблему, которая в основном возникает из-за нехватки системных ресурсов. И наше приложение не должно обнаруживать эти типы проблем. Некоторые из примеров ошибок — сбой системы и ошибка нехватки памяти. Ошибки в основном возникают во время выполнения, так как они относятся к непроверенному типу.
Исключения — это проблемы, которые могут возникнуть во время выполнения и во время компиляции. Как правило это происходит в коде, написанном разработчиками. То есть exception более предсказуемые и более зависящие от нас как от разработчиков. В это же время errors более случайны и более независимы от нас, а скорее зависимы от неполадок самой системы, в которой работает наше приложение.
Как я и сказал ранее, exception — это ошибка во время выполнения программы и во время компиляции, которая произошла в написанном разработчике коде (из-за некой нештатной ситуации).
Checked — вид исключений, которые нужно всегда обрабатывать посредством использования механизма — try — catch или пробрасывать в методы выше.
Throws используется в заголовке метода для обозначения возможных брошенных исключений данным методом. То есть это и есть механизм “проброски” исключений в методы выше.
Unchecked — вид исключений, которые не нужно обрабатывать, они, как правило, менее предсказуемы и менее вероятны. Тем не менее, по желанию их тоже можно обработать.
Throw используется, при ручном броске исключения, например:
throw new Exception();
Иерархия исключений весьма велика обширна, даже слишком, чтобы о ней вот тут так все рассказать. Поэтому, мы рассмотрим лишь ключевые ее звенья:

Тут на самом верху иерархии мы видим класс — Throwable — общий класс, предок иерархии исключений, который в свою очередь делится на:
Exception делится на различные непроверяемые runtime исключения и разнообразные проверяемые исключения.
Как я раньше и говорил:
Checked — исключения, которые вы обязаны как-то обработать, то есть либо обработать в блоке try — catch, либо “пробросить“ в метод выше. Для этого в сигнатуре метода после перечисления аргументов метода нужно использовать ключевое слово trows <тип исключения>, которое указывает для пользователей метода, что метод может кинуть данное исключение (что-то вроде предупреждения) и передаст обязанность обрабатывать исключение уже для пользователей сего метода.
Unchecked — исключения, которые обрабатывать не нужно, так как они не проверяются во время компиляции и, как правило, более непредсказуемые. То есть, основная разница с checked в том, что для них эти механизмы try — catch или пробрасывания работают также, но они не являются обязательными.
try{ // начало блока перехвата
throw new Exception(); // ручной бросок исключения
} catch (Exception e) { // данное исключение и его потомки будут перехватываться
System.out.println("Упс, что-то пошло не так =("); // вывод некоторого исключения в консоль
}Сперва напишем свой класс исключения, который наследуется от Exception и переопределим ему конструктор с сообщением ошибки:
public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
}Ну а далее бросим его вручную и перехватим как и в предыдущем вопросе:
try{
throw new CustomException("Упс, что-то пошло не так =(");
} catch (CustomException e) {
System.out.println(e.getMessage());
}И опять же при запуске вы получите вывод в консоль:
Упс, что-то пошло не так =(Если я правильно понимаю вопрос, тут спрашивают про правила работы с исключениями при наследовании, и они таковы:
Animal с методом, бросающим IOException:public interface Animal {
void voice() throws IOException;
}В реализации данного интерфейса мы не можем выставить более общее бросаемое исключение (например, Exception, Throwable), но можем заменить его на исключение наследника, как, например, на FileNotFoundException:
public class Cat implements Animal {
@Override
public void voice() throws FileNotFoundException {
// некоторая реализация
}
}throws все классы исключений пробрасываемым конструктором суперкласса, к которому идет обращение при создании объекта.Предположим, что конструктор класса Animal бросает множество исключений:
public class Animal {
public Animal() throws ArithmeticException, NullPointerException, IOException {
}Тогда наследник класса в конструкторе также обязан их указать:
public class Cat extends Animal {
public Cat() throws ArithmeticException, NullPointerException, IOException {
super();
}Либо, как и в случае с методами — можно указать не те же исключения, а более общие.
В нашем случае достаточно будет указать более общее исключение — Exception, так как это общий предок всех рассмотренных трех исключений:
public class Cat extends Animal {
public Cat() throws Exception {
super();
}Для начала вспомним, что же такое finally. Ранее мы рассматривали механизм перехвата исключений: блок try очерчивает зону перехвата, в то же время блок (блоки) catch — это код, который сработает при падении определенного исключения.
Finally — третий блок кода после слова finally, который является взаимозаменяемым с catch, но не взаимоисключающим. Суть данного блока в том, что код в нем срабатывает всегда, независимо от результата срабатывания try или catch (независимо от того упало ли исключение или нет).
Случаи его несрабатывания весьма редки и они являются нештатными.
Самый простой случай несрабатывания — это когда в коде выше будет вызван метод System.exit(0), который завершает программу (тушит ее):
try {
throw new IOException();
} catch (IOException e) {
System.exit(0);
} finally {
System.out.println("Данное сообщение не будет выведенно в консоль");
}Также есть некоторые другие ситуации, в которых finally не сработает:
try…finally проходит deamon-поток и параллельно с этим программа завершается. Ведь deamon-поток — это поток для фоновых действий, то есть он не является приоритетным и обязательным и приложение не будет ждать окончания его работы.try или catch, попав в который поток там останется навсегда:try {
while (true) {
}
} finally {
System.out.println("Данное сообщение не будет выведенно в консоль");
}Насколько я понимаю в данном вопросе имеется в виду множество catch для одного блока try:
try {
throw new FileNotFoundException();
} catch (FileNotFoundException e) {
System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
System.out.print("Упс, у вас упало исключение - " + e);
}Если в блоке try падает исключение, то блоки catch поочередно сверху вниз пытаются его перехватить, если у некоторого блока catch это получается, он получает право на обработку исключения, в то время когда остальные блоки ниже уже не смогут попытаться перехватить и по своему обработать его.
Поэтому, более узкие исключения ставятся в цепочке catch блоков выше, а более широкие — пониже. К примеру, если у нас в первом catch блоке отлавливается исключение класса Exception, то в остальные блоки проверяемые исключения попасть никак не смогут (остальные блоки с наследниками Exception будут абсолютно бесполезны).
В таком случае наша обработка будет иметь вид вроде следующего:
try {
throw new NullPointerException();
} catch (Exception e) {
if (e instanceof FileNotFoundException) {
// некоторая обработка с сужением типа (FileNotFoundException)e
} else if (e instanceof ArithmeticException) {
// некоторая обработка с сужением типа (ArithmeticException)e
} else if(e instanceof NullPointerException) {
// некоторая обработка с сужением типа (NullPointerException)e
}Словив исключение через catch, мы пытаемся выяснить его конкретный тип через метод instanceof, который используется для проверки — принадлежит ли объект определенному типу, чтобы потом без негативных последствий можно было сузить его до этого типа.
Оба рассмотренных подхода могут применяться в одной и той же ситуации, но про некорректность вопроса я сказал из-за того, что второй вариант я бы не назвал хорошим и в своей практике ни разу не встречал, в то же время первый способ с мультикетчами получил широкое распространение.
Выше я уже неоднократно использовал, тем не менее повторю это ключевое слово — throw.
Пример использования (принудительная выброска исключения):
throw new NullPointerException();
В первую очередь хочу отметить, то что main это не более чем обычный метод, и да, он вызывается виртуальной машиной для начала выполнения программы, но кроме этого он может быть вызван и из любого другого кода.
То есть на него также распространяются обычные правила указания выбрасываемых проверяемых исключений (checked exceptions) после throws:
public static void main(String[] args) throws IOException {
Соответственно в нем тоже могут падать исключения.
Если main не был вызван в каком-то методе, а был запущен как точка запуска программы, то выброшенное им исключение будет обрабатываться перехватчиком UncaughtExceptionHandler.
Данный обработчик один на свой поток (то есть по одному обработчику в каждом потоке). При надобности можно создать свой обработчик и задать его с помощью метода setDefaultUncaughtExceptionHandler, вызванного у объекта Thread.