try, catch и finally
Исключение не обязательно должно ронять программу. Его можно перехватить (catch) и решить, что делать дальше: показать понятное сообщение, взять значение по умолчанию, попробовать ещё раз. Для этого служит конструкция try-catch.
try и catch
Код, который может выбросить исключение, помещают в блок try. Следом идёт блок catch с типом исключения, которое нужно перехватить:
void main() {
try {
int result = 10 / 0;
IO.println(result);
} catch (ArithmeticException e) {
IO.println("Делить на ноль нельзя");
}
IO.println("Программа продолжает работу");
}Результат:
Делить на ноль нельзя
Программа продолжает работуКогда в try возникает исключение, остаток блока пропускается и управление переходит в подходящий catch. После него программа идёт дальше как обычно. Если исключения не было, catch не выполняется вовсе.
Объект исключения
В скобках catch объявляется переменная — обычно её называют e. Через неё доступно сообщение об ошибке:
void main() {
String input = "12a";
try {
int number = Integer.parseInt(input);
IO.println(number * 2);
} catch (NumberFormatException e) {
IO.println("Ошибка: " + e.getMessage());
}
}Результат:
Ошибка: For input string: "12a"Несколько catch
Один try может иметь несколько блоков catch — для разных ошибок разная реакция. Java проверяет их сверху вниз и выполняет первый подходящий. Если реакция одинаковая, типы можно перечислить через | в одном блоке: catch (NumberFormatException | ArithmeticException e) — это называется multi-catch.
void check(String text, int index) {
try {
int value = Integer.parseInt(text);
int[] data = new int[3];
data[index] = value;
IO.println("Записано: " + value);
} catch (NumberFormatException e) {
IO.println("Не число: " + text);
} catch (ArrayIndexOutOfBoundsException e) {
IO.println("Плохой индекс: " + index);
}
}
void main() {
check("7", 1);
check("семь", 1);
check("7", 5);
}Результат:
Записано: 7
Не число: семь
Плохой индекс: 5finally
Блок finally выполняется всегда: было исключение или нет, перехватили его или нет. В нём обычно освобождают ресурсы — закрывают файлы и соединения:
void main() {
try {
IO.println("Открываем соединение");
int x = 5 / 0;
} catch (ArithmeticException e) {
IO.println("Ошибка: " + e.getMessage());
} finally {
IO.println("Закрываем соединение");
}
}Результат:
Открываем соединение
Ошибка: / by zero
Закрываем соединениеДля закрытия ресурсов есть и более удобный способ — try-with-resources, ему посвящён отдельный урок.
throw
Исключения можно выбрасывать и самому — оператором throw. Так метод сообщает, что получил недопустимые данные:
void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Возраст не может быть отрицательным: " + age);
}
IO.println("Возраст: " + age);
}
void main() {
setAge(30);
try {
setAge(-5);
} catch (IllegalArgumentException e) {
IO.println(e.getMessage());
}
}Результат:
Возраст: 30
Возраст не может быть отрицательным: -5Частая ошибка
Блоки catch проверяются по порядку, поэтому общий тип нельзя ставить раньше частного. Exception перехватывает и ArithmeticException, и второй блок никогда бы не выполнился — компилятор считает это ошибкой:
void main() {
try {
int x = 10 / 0;
} catch (Exception e) {
IO.println("Что-то пошло не так");
} catch (ArithmeticException e) {
IO.println("Деление на ноль");
}
}Результат:
Main.java:6: error: exception ArithmeticException has already been caught
} catch (ArithmeticException e) {
^
1 error
error: compilation failedСтавьте частные исключения выше общих. И не злоупотребляйте catch (Exception e): лучше перехватывать только те ошибки, которые вы действительно умеете обработать.