throw и свои исключения
В уроке о проверяемых исключениях метод load впервые бросил исключение сам, оператором throw. В здании это кнопка пожарной тревоги на стене: нажать её может любой, кто заметил дым, не дожидаясь датчика. Разберём, когда нажимать её самому и как сделать тревогу со своей надписью.
Пусть метод withdraw снимает деньги со счёта. Что ему делать, если просят больше, чем есть? Можно вернуть -1, но вызывающий может забыть это проверить и напечатать -1 как остаток. Исключение забыть нельзя: если на него никто не ответит, программа остановится.
Свой класс исключения
Для случая «не хватает денег» в Java нет готового класса, но его можно написать. Исключение — обычный класс, наследник Exception:
public class Main {
public static void main(String[] args) {
var error = new InsufficientFundsException("need 50 more");
System.out.println(error.getMessage()); // need 50 more
}
}
class InsufficientFundsException extends Exception {
InsufficientFundsException(String message) {
super(message);
}
}Давайте разберём по частям:
class InsufficientFundsException extends Exception— новый класс исключения. Его родительException, значит, исключение проверяемое. Имя по традиции кончается наException.- Конструктор принимает сообщение и передаёт его родителю через
super(message). - Метод
getMessage()объявлен в классеThrowable, и наш класс получает его по наследству. Он возвращает сообщение, которое передали в конструктор. - В
mainобъект создаётся черезnew, как любой другой. Ничего не случается: пока исключение не бросили, это обычный объект.
Если унаследовать класс от RuntimeException, исключение будет непроверяемым. Проверяемое выбирают для ситуации, к которой вызывающий должен быть готов, как к нехватке денег. Непроверяемое — для ошибки в коде вызывающего.
Бросить своё исключение
Теперь withdraw может нажать кнопку тревоги сам:
static int withdraw(int balance, int amount)
throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
if (amount > balance) {
int lack = amount - balance;
throw new InsufficientFundsException("need " + lack + " more");
}
return balance - amount;
}Метод проверяет две разные беды:
- Сумма не больше нуля — это ошибка в коде того, кто вызвал метод. Для неё в Java есть готовый непроверяемый класс
IllegalArgumentException, «неверный аргумент».NumberFormatException, который бросаетparseInt, — его наследник. - Денег не хватает — это обычная жизненная ситуация, и вызывающий должен быть к ней готов. О ней сообщает наше проверяемое исключение, поэтому в заголовке стоит
throws InsufficientFundsException.
throw завершает метод сразу, как исключение от JVM: до return дело не доходит. Вызов withdraw теперь пишут в try:
try {
System.out.println(withdraw(100, 30)); // 70
System.out.println(withdraw(100, 150));
} catch (InsufficientFundsException e) {
System.out.println("Refused: " + e.getMessage()); // Refused: need 50 more
}Разберём по строкам:
withdraw(100, 30)возвращает70, и программа его печатает.withdraw(100, 150)бросает исключение с сообщениемneed 50 more, и второйprintlnне выполняется.catchловит его и печатаетRefused: need 50 more.
А вызов withdraw(100, -5) в том же try бросил бы IllegalArgumentException. Этот catch его не поймает, потому что класс другой, и программа остановится:
Exception in thread "main" java.lang.IllegalArgumentException: amount must be positiveТак и задумано: ошибку в коде не прячут, а исправляют.
throw и throws
Эти два слова путают постоянно, и их различие часто спрашивают на собеседовании:
| Слово | Где стоит | Что делает |
|---|---|---|
throw | в теле метода | бросает один объект исключения сейчас |
throws | в заголовке метода, после скобок | объявляет, какие исключения могут выйти из метода |
throw — действие: нажать кнопку тревоги. throws — табличка на этаже «передаём наверх». Для проверяемого исключения табличка обязательна, для непроверяемого — нет.
Здесь картинка с кнопкой неточна. Настоящая сирена звучит одинаково, кто бы её ни включил. А исключение несёт свой класс и сообщение, и по классу catch решает, его это тревога или нет.
Частая ошибка
В своём классе исключения забыли передать сообщение родителю:
class InsufficientFundsException extends Exception {
InsufficientFundsException(String message) { // ✗
}
}Программа компилируется, но вместо Refused: need 50 more печатает Refused: null. Конструктор не вызвал super(message), поэтому компилятор вставил super() без аргументов. Родитель так и не узнал сообщение, и getMessage() возвращает null. Передайте сообщение родителю: первой строкой конструктора напишите super(message);.
Главное
throw new …бросает исключение сразу и завершает метод; для неверного аргумента есть готовыйIllegalArgumentException.- Своё исключение — класс-наследник
Exception(проверяемое) илиRuntimeException(непроверяемое), который передаёт сообщение черезsuper(message). getMessage()возвращает сообщение, переданное в конструктор.throwбросает исключение в теле метода, аthrowsобъявляет его в заголовке.