Обычно система сама генерирует исключения при определенных ситуациях, например, при делении числа на ноль. Но язык Java также позволяет генерировать исключения вручную с помощью оператора throw. Например, в нашей программе пользователь должен вводить имя, но имя имеет свои ограничения. Например, очевидно, что имя из одного символа - это некорректно имя. Возможно, мы предъявляем еще какие-то ограничения к имени. И в этом случае мы можем при вводе некорректных данных генерировать исключение.
Общая форма оператора:
throw объект_исключения;
То есть после оператора указывается объект исключения. Как его создать? Прежде всего мы можем использовать встроенные классы исключений, в том числе базовые - Throwable и Exception.
Так, один из конструкторов типа Throwable принимает сообщение об ошибке:
Throwable(String message)
По сути это то сообщение, которое возвращается методом getMessage() и которое при возникновении ошибки отображается на консоли. Ну и соответственно все другие типы исключений также имеют подобный конструктор.
Рассмотрим генерацию исключения на примере:
import java.util.Scanner;
class Program{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Введите имя: ");
String name = in.nextLine(); // считываем введенную строку
// если строка имеет имеет меньше 2-х символов, генерируем исключение
if(name.length() < 2) throw new Throwable("Малобукоф");
System.out.println("Привет, " + name);
}
}
Здесь с помощью класса Scanner и метода in.nextLine() считываем с консоли строку - условное имя пользователя. Затем, используя метол length() у строки смотрим на длину -
если в имени меньше 2 символов, то генерируем исключение:
if(name.length() < 2) throw new Throwable("Малобукоф");
В конструктор класса Throwable передаем сообщение об ошибке.
Но если мы попробуем скомпилировать программу, нас постигнет неудача:
Program.java:12: error: unreported exception Throwable; must be caught or declared to be thrown
if(name.length() < 2) throw new Throwable("Малобукоф");
^
1 error
Компилятор нам говорит, что нам надо либо обработать исключение, либо указать, что метод main генерирует данное исключение.
КАк говорилось в прошлой статье, исключения в Java делятся условно на две группы: проверяемые (checked exceptions) и непроверяемые
(unchecked exceptions). К непроверяемым исключениям относят классы Error и RuntimeException и унаследованные от них классы. А проверяемые исключения по сути все остальные.
И в контексте работы с оператором throw подобное разделение имеет большое значение. При генерации проверяемых исключений нам надо либо указать, что метод генерирует исключение, либо обработать это исключение. Если же генерируются
непроверяемые исключения, то ничего этого делать не надо.
В нашем примере выше генерируемое исключение типа Throwable рассматривается компилятором как проверяемое исключение (хотя классы Error и RuntimeException
унаследованы от Throwable). Поэтому и при компиляции возникает ошибка.
Для решения проблемы изменим код и обработаем исключение с помощью рассмотренной в прошлых статьях конструкции try..catch:
import java.util.Scanner;
class Program{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Введите имя: ");
String name = in.nextLine(); // считываем введенную строку
try{
// если строка имеет имеет меньше 2-х символов, генерируем исключение
if(name.length() < 2) throw new Throwable("Малобукоф");
System.out.println("Привет, " + name);
}
catch(Throwable ex){
System.out.println("Возникла ошибка: " + ex.getMessage());
}
}
}
Здесь та, часть кода, которая генерирует исключения и которая зависит от проверки переменной name, помещается в блок try.
Если будет сгенерировано исключение, то управление перейдет к блоку catch, где мы перехватим исключение и выведем сообщение об ошибке.
Пример работы программы:
Введите имя: e Возникла ошибка: Малобукоф
Подобным образом мы можем генерировать исключения в любом месте программы. Кроме того, мы можем использовать и другие встроенные типы исключений. Например, встроенный тип
IllegalArgumentException предназначен для генерации исключения при некорректном параметре метода. Используем данный тип:
class Program{
public static void main(String[] args) {
var tom = new Person("Tom", -20);
tom.print();
}
}
class Person{
private String name;
private int age;
Person(String name, int age){
if(age < 1 || age > 110) throw new IllegalArgumentException("Некорректный возраст: " + age);
this.name = name;
this.age = age;
}
void print(){ System.out.printf("Name: %s; Age: %d\n", name, age); }
}
Здесь в конструктор класса Person через параметр age передается возраст человека. Однако не все значения типа int могут быть разумными значениями для возраста. Например,
возраст не может быть отрицательным. И в конструкторе класса Person проверяем значение параметра age на соответствие некоторому диапазону. Ив случае несоответствия, генерируем исключение
типа IllegalArgumentException
if(age < 1 || age > 110)
throw new IllegalArgumentException("Некорректный возраст: " + age);
В методе main передаем в конструктор Person заведомо некорректное значение:
var tom = new Person("Tom", -20);
В итоге при запуске программы и создании этого объекта будет сгенерировано исключение
Exception in thread "main" java.lang.IllegalArgumentException: Некорректный возраст: -20 at Person.<init>(Program.java:20) at Program.main(Program.java:7)
Обратите внимание, что при компиляции программы компилятор не выдал ошибку, как при компиляции предыдущего примера. Потому что в данном случае мы обрабатываем исключение типа
IllegalArgumentException, которое является непроверяемым. Поэтому в принципе его можно не обрабатывать, если нас устраивает подобное завершение программы с ошибкой. Тем не менее обработаем исключением:
class Program{
public static void main(String[] args) {
try{
var tom = new Person("Tom", -20);
tom.print();
}
catch(IllegalArgumentException ex){
System.out.println(ex.getMessage());
}
}
}
class Person{
private String name;
private int age;
Person(String name, int age){
if(age < 1 || age > 110) throw new IllegalArgumentException("Некорректный возраст: " + age);
this.name = name;
this.age = age;
}
void print(){ System.out.printf("Name: %s; Age: %d\n", name, age); }
}
Если метод генерирует проверяемое исключение, то нам надо либо обработать это исключение, либо сообщить компилятору, что метод генерирует данное исключение. Поэтому обработка исключения непосредственно в методе - не единственный способ работы с проверяемыми исключениями. Нередко метод сам не обрабатывает исключение, а предоставляет возможность обработки внешнему коду, где этот метод вызывается. В этом случае в объявлении метода необходимо указать тип исключения, который генерирует метод. Для этого используется оператор throws в следующем виде:
возвращаемый_тип имя_метода(параметры) throws тип_исключения_1, тип_исключения_2,... тип_исключения_N {
}
Оператор throws указывается после списка параметров метода, но до открывающей фигурной скобки. А после оператора идет перечисление типов генерируемых исключений. Например:
import java.util.Scanner;
class Program{
public static void main(String[] args) {
try{
printName(); // здесь генерируется исключение
}
catch(Throwable ex){
System.out.println(ex.getMessage());
}
}
static void printName() throws Throwable {
Scanner in = new Scanner(System.in);
System.out.print("Введите имя: ");
String name = in.nextLine(); // считываем введенную строку
// если строка имеет имеет меньше 2-х символов, генерируем исключение
if(name.length() < 2) throw new Throwable("Недостаточная длина имени: " + name.length());
System.out.println("Привет, " + name);
}
}
Здесь метод printName() считывает через консоль имя пользователя и с помощью оператора throw генерирует исключение типа Throwable, если длина введенного имени меньше 2 символов.
При этом, поскольку этот метод сам не обрабатывает исключение, то в объявлении метода указываем, что он генерирует исключение типа Throwable:
static void printName() throws Throwable {
А внешний метод - main(), в котором вызывается printName(), обрабатывает возможно исключение с помощью конструкции try..catch.
ПРи этом следует учитывать, что если вызываемый метод (printName()) сам не обрабатывает проверяемое исключение, то внешний - вызывающий метод (main()) должен
либо обработать исключение (как в примере выше), либо также указать компилятору с помощью оператора throws, что он генерирует данный тип исключений.
Выше был пример, где внешний метод main() обрабаотывал исключение, возникшее в printName(). Теперь же используем другую стратегию:
import java.util.Scanner;
class Program{
public static void main(String[] args) throws Throwable {
printName(); // здесь возникает исключение, но его никто не обрабатывает
}
static void printName() throws Throwable{
Scanner in = new Scanner(System.in);
System.out.print("Введите имя: ");
String name = in.nextLine(); // считываем введенную строку
// если строка имеет имеет меньше 2-х символов, генерируем исключение
if(name.length() < 2) throw new Throwable("Недостаточная длина имени: " + name.length());
System.out.println("Привет, " + name);
}
}
Данная программа успешно скомпилируется, так как в заголовке метода main() мы указали выражение throws Throwable:
public static void main(String[] args) throws Throwable {
Однако при выполнении при некорректном вводе, поскольку исключение не обрабатывается, программа упадет с ошибкой:
Введите имя: r Exception in thread "main" java.lang.Throwable: Недостаточная длина имени: 1 at Program.printName(Program.java:22) at Program.main(Program.java:7)
И в конце стоит отметить, что метод может генерировать несколько типов исключений. В этом случае они указываются после оператора throws через запятую:
public static void main(String[] args) throws FileNotFoundException, EOFException {
Однако если метод генерирует непроверяемые исключения типа IllegalArgumentException, то нет смысла их указывать, так как компилятор их все равно не проверяет.