Исключения в Java
Исключение в Java – это нештатная ситуация, ошибка, случившееся во время выполнения программы. Например, деление на ноль. Возникновение подобных ошибок можно отслеживать вручную или использовать специальный механизм исключений, упрощающий создание более надежных программ, а также уменьшающий объем кода. Так вы будете уверены, что программа не пострадает от необработанной ошибки.
В том методе, где произошла ошибка, создается и передается специальный объект. При этом метод может либо обработать исключение сам, либо пропустить его. Как бы там ни было исключение ловится и обрабатывается. Оно может появиться благодаря системе или вы можете создать его самостоятельно. Системные исключения появляются при использовании запрещенных приемов доступа к системе или неправильном использовании Java. Что касается ваших собственных исключений, то они обрабатывают специфические ошибки программы.
Рассмотрим пример с делением. Проверка соответствующего условия может предотвратить деление на ноль. Но что, если знаменатель является нулем? Если же такой нулевой знаменатель появился неожиданно, то деление невозможно и нужно возбудить исключение, и ни в коем случае не продолжать исполнение программы.
В исключениях используется пять ключевых слов: try, catch, throw, throws, finally. Схема обработки исключений следующая.
В блок try помещаются операторы программы, которые нужно отслеживать. Если произошло исключение, то оно создается и передается дальше. С помощью блока catch код может перехватить исключения и обработать его.
За передачу системных исключений отвечает сама система. Чтобы сделать это вручную, применяется throw. Созданное и передаваемое исключение внутри метода, указывается ключевым словом throws. Код, который обязательно выполнить после завершения блока try, заключается в блок finally.
Пример:
try {
// блок кода, где отслеживаются ошибки
}
catch (тип_исключения_1 exceptionObject) {
// обрабатываем ошибку
}
catch (тип_исключения_2 exceptionObject) {
// обрабатываем ошибку
}
finally {
// код, который нужно выполнить после завершения блока try
}
Для исключений существует специальный класс Trowable, в который входят 2 класса — Exception и Error. Первый используется для обработки исключений вашим приложением. Вы можете наследоваться от него, чтобы создавать собственные исключения. Класс Runtime Exception существует для распространения ошибок, который определяет ошибочную индексацию массива или обрабатывает деление на ноль.
Класс Error применяется для обработки ошибок в языке Java. Поэтому вам не придется с ним работать.
Перед тем, как научиться обрабатывать исключения, любому любопытному программисту захочется посмотреть, что же происходит, если не обработать ошибку. Давайте рассмотрим пример с делением котов на ноль:
intcatNumber;
intzero;
catNumber = 1; // у меня один кот
zero = 0; // ноль, он и в Африке ноль
intresult = catNumber / zero;
Если разместить команду в обработчик щелчка кнопки, то система времени выполнения Java выявит попытку деления на ноль, создаст объект исключения и передаст его. Но никто не перехватывает его, так как это должны быть сделать именно вы. Когда система видит вашу бездеятельность, вступает стандартный системный обработчик с вредным характером. Он остановит программу и покажет сообщение об ошибке. Его можно увидеть в журнале LogCat:
Causedby: java.lang.ArithmeticException: dividebyzeroat ru.alexanderklimov.test.MainActivity.onClick(MainActivity.java:79)
Созданный объект исключений относится к классу Arithmetic Exception. Затем системный обработчик вывел краткое описание ошибки, а также место его возникновения.
Если вы так и оставите процесс обработки ошибки системы, то вызовите недовольство пользователей. Если программа завершится с такой ошибкой, то вероятней всего ее просто удалят. Посмотрим, как разрешить эту ситуацию.
Разместите проблемный код в блок try и обработайте исключение в блоке catch.
intcatNumber;
intzero;try{ // мониторим код
catNumber = 1; // у меня один кот
zero = 0; // ноль, он и в Африке ноль
intresult = catNumber / zero;
Toast.makeText(this, «Не увидите это сообщение!», Toast.LENGTH_LONG).show();
} catch (ArithmeticException e) {
Toast.makeText(this, «Нельзя котов делить на ноль!», Toast.LENGTH_LONG).show();
}
Toast.makeText(this, «Жизнь продолжается», Toast.LENGTH_LONG).show();
В таком случае программа не закроется аварийно, поскольку мы обрабатываем ситуацию с делением на ноль.
В таком случае мы уже знали, к какому именно классу принадлежит эта ошибка, так как в блоке catch указали конкретный тип. Учтите, что последний оператор не срабатывает в блоке try, поскольку ошибка происходит строчкой выше. Затем выполнения передается в catch и в обычном порядке выполняются следующие операторы.
Операторы try и catch действуют в паре. Возможны ситуации, когда catch обрабатывает несколько вложений try.
Параметр е поможет вам увидеть описание ошибки:
catch (ArithmeticException e) {
Toast.makeText(this, e + «: Нельзя котов делить на ноль!», Toast.LENGTH_LONG).show();
}
Класс Trowable, к которому относится Arithmetic Exception, по умолчанию возвращает строку, содержащую описание исключения. Однако вы можете явно указать метод e.toString.
Несколько исключений
Код может содержать ни одно, а несколько проблемных мест. К примеру, помимо деления на ноль, может возникнуть ошибка индексации массива. В этом случае вам необходимо создавать или больше операторов catch для всех типов исключения. При этом они проверяются по порядке. Если будет обнаружено исключение у первого блока, то он будет выполнен, тогда как другие проверки пропускаются и выполнения приложения продолжается с места, следующего за блоком try/catch.
intcatNumber;
intzero;try{ // мониторим код
catNumber = 1; // у меня один кот
zero = 1; // ноль, он и в Африке ноль
intresult = catNumber / zero;
// Создадим массив из трёх котов
String[] catNames = {«Васька», «Барсик», «Мурзик»};
catNames[3] = «Рыжик»;
Toast.makeText(this, «Не увидите это сообщение!», Toast.LENGTH_LONG).show();
} catch (ArithmeticException e) {
Toast.makeText(this, e.toString() + «: Нельзя котов делить на ноль!», Toast.LENGTH_LONG).show();
}
catch (ArrayIndexOutOfBoundsException e) {
Toast.makeText(this, «Ошибка: » + e.toString(), Toast.LENGTH_LONG).show();
}
Toast.makeText(this, «Жизнь продолжается», Toast.LENGTH_LONG).show();
В рассматриваемом примере, мы использовали массив с тремя элементами, но обращались к четвертому, поскольку забыли, что отсчет у массива начинается с нуля. Если значение переменной zero оставить равным нулю, то сработает обработка первого исключения деления на ноль. При этом мы даже не будем знать о существовании второй ошибки. Но представьте, если в результате определенных вычислений значений переменной = 1. В таком случае Arithmetic Exception не сработает, но вступит новое добавленное Array Index Out Of Bounds Exception. Дальше процесс будет таким же.
Здесь важно знать одну особенность. Используя множественные операторы catch обработки подклассов исключений должны располагаться выше по отношению обработчиков их суперклассов. В противном случае, суперкласс будет перехватывать все исключения, так как имеет большую область перехвата. Другими словами, Exception не должен располагаться выше Arithmetic Exception и Array Index Out Of Bounds Exception. Хорошо, что среда разработки сама обнаружит непорядок и обязательно вас предупредит об этом. Заметив такую ошибку, перенесите блок обработки исключений ниже.
Вложенные операторы try
Операторы try бывают вложенными. Если такой оператор не имеет собственного обработчика catch для определения исключения, то происходит поиск обработчика catch у внешнего блока try и так далее. Если не найдется подходящий catch, то сама система обработает исключения, что, конечно же, недопустимо.
Оператор throw
Система может обрабатывать часть исключений. Но с помощью оператора throw можно создать собственные исключения. Когда выглядит следующим образом:
throw экземпляр_Throwable
Вам необходимо создать экземпляр класса Throwable или же его наследников. Объект класса Throwable можно получить в операторе catch или традиционным способом посредством оператора new.
Пример кода для кнопки:
Catcat;
publicvoidonClick(Viewview) {
if(cat == null){
thrownewNullPointerException(«Котик не инициализирован»);
}
}
В примере был объявлен объект класса Cat, ноне проинициализированный в onCreate(). Нажатие кнопки вызовет исключение, обрабатываемое системой, а в логах вы можете увидеть сообщение об ошибке. Также можно использовать другое исключение по типу thrownewUnsupportedOperationException(«Котик не инициализирован»).
Как бы там ни было, мы передали системе обработку ошибки. В реальной программе вам необходимо самостоятельно обработать ошибку.
При этом поток выполнения останавливается после оператора throw и остальные операторы не выполняются. Ищется ближайший блок try/catch соответствующего исключению типа.
Пример кода с обработкой ошибки:
publicvoidonClick(Viewview) {
if (cat == null) {
try {
thrownewNullPointerException(«Кота не существует»);
} catch (NullPointerException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
Был создан новый объект класса NullPointerException. По умолчанию многие классы исключений (кроме стандартного конструктора) с пустыми скобками имеют второй конструктор с строковым параметром, где можно разместить информацию об исключении. Текст из него можно получить через метод getMessage(), что собственно и было сделано в блоке catch.
Теперь программа не будет закрыта аварийно, а выведет сообщение в сплывающих Toast.
Оператор throws
В том случае, если метод может продлить исключение, которое он не обрабатывает, он должен задать данное поведение так, чтобы вызывающий его код смог бы позаботиться об этом исключении. Для этой цели к объявлению метода добавляется конструкция throws, перечисляющая типы исключений (кроме Error и Runtime Exception, а также их подклассов).
Общая форма объявления метода с использованием оператора throws:
тип имя_метода(список_параметров) throwsсписок_исключений {
// код внутри метода
}
В фрагменте список_исключений можно через запятую указать список исключений. Создадим метод, который продлит исключение, но не обрабатывает его. И вызовем его при нажатии на кнопку.
// Метод без обработки исключения
publicvoidcreateCat(){
Toast.makeText(this, «Вы создали котёнка», Toast.LENGTH_LONG).show();
thrownewNullPointerException(«Кота не существует»);
}// Щелчок кнопки
publicvoidonClick(View v) {
createCat();
}
Если вы запустите пример, то получите ошибку. Исправим код.
// Без изменений
publicvoidcreateCat() throwsNullPointerException {
Toast.makeText(this, «Вы создали котёнка», Toast.LENGTH_LONG).show();
thrownewNullPointerException(«Кота не существует»);
}// Щелчок кнопки
publicvoidonClick(View v) {
try {
createCat();
} catch (NullPointerException e) {
// TODO: handleexception
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
Вызов метода помещен в блок try. Блок catch с необходимым типом исключения. В данном случае ошибка не произойдет.
Оператор finally
После передачи исключения, выполнения метода направляется по нелинейному пути. Это может спровоцировать проблемы. К примеру, при входе метод открывает файл, а при выходе закрывает. Чтобы из-за обработки исключения не было пропущено закрытие файла, нужно воспользоваться оператором finally.
Оператор finally создаёт блок кода, который будет выполнен после блока try/catch, но перед кодом, идущим за ним. Блок будет выполнен, невзирая на то, будет передано исключение или нет. Оператор finally не обязателен, но каждый оператор try требует наличия либо finally, либо catch.
Встроенные исключения Java
Java поддерживает несколько готовых системных исключений, большая часть из которых является подклассами типа Runtime Exception. При этом их не нужно добавлять в список throws. Приведем наиболее распространенные неповторяемые исключения:
Illegal Monitor State Exception | Неверная операция мониторинга |
Index Out of Bounds Exception | Тип индекса вышел за допустимые пределы |
Array Store Exception | Присваивание элементу массива объекта несовместимого типа |
Type Not Present Exception | Тип не найден |
Enum Constant Not Present Exception | Попытка использования неопределённого значения перечисления |
Number Format Exception | Неверное преобразование строки в числовой формат |
Unsupported Operation Exception | Обнаружена неподдерживаемая операция |
Arithmetic Exception | Арифметическая ошибка, к примеру, деление на ноль |
Illegal Thread State Exception | Запрашиваемая операция несовместима с текущим потоком |
String Index Out Of Bounds | Попытка использования индекса за пределами строки |
Class Cast Exception | Неверное приведение |
Security Exception | Попытка нарушения безопасности |
Illegal Argument Exception | Неверный аргумент при вызове метода |
Null Pointer Exception | Неверное использование пустой ссылки |
Array Index Out Of Bounds Exception | Выход индекса за границу массива |
Unsupported Operation Exception | Обнаружена неподдерживаемая операция |
Illegal State Exception | Некорректное состояние приложения |
Negative Array Size Exception | Создан массив отрицательного размера |
Проверяемые системные исключения, которые можно включать в список throws.
Interrupted Exception | Поток прерван другим потоком |
Class Not Found Exception | Класс не найден |
No Such Field Exception | Запрашиваемое поле не существует |
Illegal Access Exception | Запрещен доступ к классу |
Reflective Operation Exception | Исключение, связанное с рефлексией |
Instantiation Exception | Попытка создать объект абстрактного класса или интерфейса |
CloneNotSupportedException | Попытка клонировать объект, который не реализует интерфейс Cloneable |
No Such Method Exception | Запрашиваемый метод не существует |
Создание собственных классов исключений
Система не в состоянии предусмотреть все исключения. В некоторых случаях придется создавать свой тип исключения для собственного приложения. Вам необходимо наследоваться от Exception (данный класс наследуется от Trowable) и переопределить необходимые методы класса Throwable. Также вы можете наследоваться уже от существующего типа, наиболее близкого по логике с исключением.
Stringget Localized Message() | Возвращает локализованное описание исключения |
Final void add Suppressed (Throwable exception) | Добавляет исключение в список подавляемых исключений (JDK 7) |
Void print Stack Trace (Print Writer stream) | Посылает трассировку стека в заданный поток |
String get Message() | Возвращает описание исключения |
Throwable fill InStack Trace() | Возвращает объект класса Throwable, содержащий полную трассировку стека |
String to String() | Возвращает объект класса String, содержащий описание исключения |
Void print Stack Trace() | Отображает трассировку стека |
Throwable get Cause() | Возвращает исключение, лежащее под текущим исключение или null |
Void set Stack Trace (Stack Trace Elementelements[]) | Устанавливает трассировку стека для элементов (для специализированных приложений) |
Final Throwable[] get Suppressed() | Получает подавленные исключения (JDK 7) |
Stack Trace Element[] get Stack Trace() | Возвращает массив, содержащий трассировку стека и состояний из элементов класса StackTraceElement |
Throwable init Cause(Throwable exception) | Ассоциирует исключение с вызывающим исключением. Возвращает ссылку на исключение |
Наиболее простой способ — создать класс с конструктором по умолчанию.
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.packageru.alexanderklimov.exception;
importandroid.os.Bundle;
import android.support.v7.app.AppCompatActivity;
importandroid.view.View;publicclassMainActivityextendsAppCompatActivity {
@Override
protectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}publicvoidtestMethod() throwsHungryCatException{
System.out.println(«Возбуждаем HungryCatException из метода testMethod()»);
thrownewHungryCatException(); // конструктор по умолчанию
}publicvoidonClick(Viewview) {
try {
testMethod();
} catch (HungryCatException e) {
e.printStackTrace();
System.out.println(«Наше исключение перехвачено»);
}
}classHungryCatExceptionextendsException{
}
}
В примере создан собственный класс Hungry Cat Exception, который возбужден в методе test Method(). По нажатию кнопки вызываем данный метод. Наше исключение сработает.
Несложно будет и создать класс исключения с конструктором, получающим аргумент-строку.
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.packageru.alexanderklimov.exception;
importandroid.os.Bundle;
import android.support.v7.app.AppCompatActivity;
importandroid.view.View;publicclassMainActivityextendsAppCompatActivity {
@Override
protectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}publicvoidtestMethod() throwsHungryCatException {
System.out.println(«Возбуждаем HungryCatException из метода testMethod()»);
thrownewHungryCatException(); // конструктор по умолчанию
}publicvoid testMethod2() throwsHungryCatException {
System.out.println(«Возбуждаем HungryCatException из метода testMethod2()»);
thrownewHungryCatException(«Создано во втором методе»);
}publicvoidonClick(Viewview) {
try {
testMethod();
} catch (HungryCatException e) {
e.printStackTrace();
System.out.println(«Наше исключение перехвачено»);
}try {
testMethod2();
} catch (HungryCatException e) {
e.printStackTrace();
}
}classHungryCatExceptionextendsException {
HungryCatException() {
}HungryCatException(Stringmsg) {
super(msg);
}
}
}
Ещё вариант. Также добавим метод toString().
classCustomExceptionextendsException {
Stringmessage;CustomException(Stringstr) {
message = str;
}publicStringtoString() {
return («CustomExceptionOccurred: » + message);
}
}// где-то вызываем
try {
thrownewCustomException(«Thisis a custommessage»);
} catch (CustomException e) {
System.out.println(e);
}
Теперь наш класс содержит два конструктора. Во втором применяется конструктор родительского класса с аргументом String, который вызывается ключевым словом super.
Перехват произвольных исключений
Создадим универсальный обработчик, который будет перехватывать любые типы исключений. Это осуществляется перехватом базового класса всех исключений Exception:
cacth(Exception e) {
Log.w(«Log», «Перехвачено исключение»);
}
Данная конструкция не упустит исключения, поэтому ее нужно разместить в конце списка обработчиков, чтобы избежать блокировка обработчиков исключения, следующих за ней.
Правила обработки исключений
Исключения нужно использовать для того, чтобы:
- Исправить проблему и повторно вызвать метод, возбудивший исключение.
- Обработать ошибку на текущем уровне (если не знаете как поступить с исключением, избегайте его перехватывания).
- Предпринять все действия и продолжить выполнения без необходимости повторно вызова действий.
- Завершить работу программы.
- Попытаться найти альтернативное решение (результат вместо того который должен был осуществить вызванный метод).
- Упростить программу (если схема обработки исключений делает все сложнее, значит она не эффективная).
- Добавить программе и библиотеке безопасности.
Освоили ли вы этот материал? Какие сложности у вас возникли? Напишите ответ в комментариях.