Что такое исключения java
Перейти к содержимому

Что такое исключения java

  • автор:

Исключения Java

Защита от дурака — стандартная конструкция для любого приложения. Рассмотрим, как организовать её в Java.

15 сентября 2017 3 минуты 25410

Автор статьи
Илья Бубнов

Автор статьи
Илья Бубнов
https://gbcdn.mrgcdn.ru/uploads/post/1217/og_cover_image/cf0ec3cbe152ff0e4cb3936d72d59bea

Одно из стандартных требований ТЗ на разработку ПО – отсутствие ошибок и конфликтов, препятствующих нормальной работе. Путей реализации два – ограничить функциональность и возможности пользователя или создать код, который будет учитывать возможные неприятности.

В java исключением называется любая ошибка, которая возникает в ходе выполнения программы. Это может быть несоответствие типов данных, деление на ноль, обрыв связи с сервером и многое другое. Операции по их поиску и предотвращению называются обработкой исключений.

Иерархия

Прежде чем мы перейдём к практике, давайте познакомимся с видами исключений Джава и их иерархией. В основе всего лежит класс Throwable. Все возможные конфликты кода с машиной и пользователем описаны здесь. Для удобства обработки и чтения класс Throwable имеет подклассы Error и Exception. Error – критические ошибки, которые не обязательно происходят по вине пользователя, обработать их невозможно. Exception – собственно конфликты нашей программы, которые необходимо отлавливать.

Взгляните на упрощённую схему иерархии исключений java:

Как видно, блоки делятся на «два лагеря» по цветам — проверяемые и непроверяемые java исключения. Данная классификация показывает, как их воспринимает компилятор: проверяемые – учитывает, непроверяемые – игнорирует. К первому относится Exception в полном составе, кроме RuntimeException. Все остальные классы исключений – непроверяемые компилятором.

Иерархия классов исключений важна и для правильной организации кода. Допустим, у вас есть несколько блоков обработки. Тогда в начале необходимо указать низшие уровни, а в конце – высшие. В противном случае, будет запущен только первый блок, а остальные – проигнорированы.

Создание обработчика

Для обработки исключений java используются следующие операторы: try, catch, finally, throw, throws. Первые три — стандартная структура вашего блока. По шагам:

  1. Оператор или часть кода, в которой вам надо отыскать ошибку, помещается в блок try.
  2. Далее в блоке catch вы указываете, что за исключение надо ловить и как его обрабатывать.
  3. В блоке finally набор обязательных действий при возникновении ошибки. Обычно это запись данных, закрытие ресурсов и пр. Блок исполняется всегда, вне зависимости от срабатывания catch.

Рассмотрим структуру на примере Джава исключения:

try // код, где мы хотим отследить ошибку
>
catch (тип_исключения объект_исключения) // код обработки
>
finally // что нужно выполнить после завершения блока try
>

Если вы хотите обработать несколько исключений – просто создайте ещё один блок catch.

try // код, где мы хотим отследить ошибку
>
catch (тип_исключения_1 объект_исключения_1) // код обработки
>
catch (тип_исключения_2 объект_исключения_2) // код обработки
>
finally // что нужно выполнить после завершения блока try
>

С помощью оператора throw вы можете создавать исключения:

На практике это выглядит так:

Student stud1;
public void onClick(View view) if(stud1 == null) throw new NullPointerException(«Студента не существует»);
>
>

Включим оператор throw в наш стандартный пример с try-catch:

public void onClick(View view) if (stud1 == null) try throw new NullPointerException(«Студента не существует»);
> catch (NullPointerException e) Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
>
>
>

Как только обработка дойдёт до оператора throw, дальнейшее выполнение кода будет прекращено. Обработчик рассмотрит ближайший блок try-catch на требуемое исключение, потом следующий и так до конца кода. В случае, если вызвать ява исключение неоткуда – обработчик остановит программу.

Оператор throws используется для методов, которые содержат исключения, но их не обрабатывают.

тип имя_метода(список_параметров) throws список_исключений // код метода
>

Несколько исключений в списке необходимо перечислить через запятую. Воспользовавшись такой конструкцией, вы сообщите всем вызывающим методам о необходимости учитывать исключения.

Операторы try можно вкладывать друг в друга. При этом если вложенный обработчик не имеет своего блока catch, он осуществляет его поиск в родительском операторе. Если и там нет – блок обрабатывается системой.

Готовые и новые исключения

Далее приведём список java исключений, которые вам потребуются в работе чаще других:

  • ArithmeticException — ошибки вычислений.
  • NullPointerException — ссылка на пустое место.
  • NegativeArraySizeException — массив отрицательной размерности.
  • ArrayStoreException — присвоение элементу массива неправильного типа.
  • NumberFormatException — невозможно преобразовать строку в число.
  • IllegalArgumentException — неправильный аргумент при вызове метода.
  • UnsupportedOperationException — указанной операции не существует.
  • TypeNotPresentException — указанного типа не существует.

Все указанные типы java исключений содержатся в классе RuntimeException, а значит, их не надо указывать в блоке throws.

Естественно, система не может содержать всевозможные исключения. Некоторые придётся создавать самостоятельно. Для того, чтобы создать собственное java исключение, вам необходимо унаследовать собственный класс от Exception и переопределить требуемые методы класса Throwable. Или унаследоваться от наиболее близкого по смыслу типа. Рассмотрим на примере программы под android создание java исключения:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity

@Override
protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
>

public void testMethod() throws StudentException System.out.println(«Возбуждаем StudentException из метода testMethod()»);
throw new StudentException(); // конструктор по умолчанию
>

public void testMethod2() throws StudentException System.out.println(«Возбуждаем StudentException из метода testMethod2()»);
throw new StudentException(«Создано во втором методе»);
>

public void onClick(View view) try testMethod();
> catch (StudentException e) e.printStackTrace();
System.out.println(«Исключение перехвачено»);
>

try testMethod2();
> catch (StudentException e) e.printStackTrace();
>
>

class StudentException extends Exception StudentException() >

StudentException(String msg) super(msg);
>
>
>

Обработка исключений – основа безопасного и качественного кода. С их помощью вы можете управлять действиями пользователя, ходом выполнения программы или добавить вариативности коду.

Исключения

Исключение — это нештатная ситуация, ошибка во время выполнения программы. Самый простой пример — деление на ноль. Можно вручную отслеживать возникновение подобных ошибок, а можно воспользоваться специальным механизмом исключений, который упрощает создание больших надёжных программ, уменьшает объём необходимого кода и повышает уверенность в том, что в приложении не будет необработанной ошибки.

В методе, в котором происходит ошибка, создаётся и передаётся специальный объект. Метод может либо обработать исключение самостоятельно, либо пропустить его. В любом случае исключение ловится и обрабатывается. Исключение может появиться благодаря самой системе, либо вы сами можете создать его вручную. Системные исключения возникают при неправильном использовании языка Java или запрещённых приёмов доступа к системе. Ваши собственные исключения обрабатывают специфические ошибки вашей программы.

Вернёмся к примеру с делением. Деление на нуль может предотвратить проверкой соответствующего условия. Но что делать, если знаменатель оказался нулём? Возможно, в контексте вашей задачи известно, как следует поступить в такой ситуации. Но, если нулевой знаменатель возник неожиданно, деление в принципе невозможно, и тогда необходимо возбудить исключение, а не продолжать исполнение программы.

Существует пять ключевых слов, используемых в исключениях: try, catch, throw, throws, finally. Порядок обработки исключений следующий.

Операторы программы, которые вы хотите отслеживать, помещаются в блок try. Если исключение произошло, то оно создаётся и передаётся дальше. Ваш код может перехватить исключение при помощи блока catch и обработать его. Системные исключения автоматически передаются самой системой. Чтобы передать исключение вручную, используется throw. Любое исключение, созданное и передаваемое внутри метода, должно быть указано в его интерфейсе ключевым словом throws. Любой код, который следует выполнить обязательно после завершения блока try, помещается в блок finally

Схематически код выглядит так:

 try < // блок кода, где отслеживаются ошибки >catch (тип_исключения_1 exceptionObject) < // обрабатываем ошибку >catch (тип_исключения_2 exceptionObject) < // обрабатываем ошибку >finally < // код, который нужно выполнить после завершения блока try >

Существует специальный класс для исключений Trowable. В него входят два класса Exception и Error.

Класс Exception используется для обработки исключений вашей программой. Вы можете наследоваться от него для создания собственных типов исключений. Для распространённых ошибок уже существует класс RuntimeException, который может обрабатывать деление на ноль или определять ошибочную индексацию массива.

Класс Error служит для обработки ошибок в самом языке Java и на практике вам не придётся иметь с ним дело.

Прежде чем научиться обрабатывать исключения, нам (как и нормальному любопытному коту) хочется посмотреть, а что происходит, если ошибку не обработать. Давайте разделим число котов в вашей квартире на ноль, хотя мы и знаем, что котов на ноль делить нельзя!

 int catNumber; int zero; catNumber = 1; // у меня один кот zero = 0; // ноль, он и в Африке ноль int result = catNumber / zero; 

Я поместил код в обработчик щелчка кнопки. Когда система времени выполнения Java обнаруживает попытку деления на ноль, она создаёт объект исключения и передаёт его. Да вот незадача, никто не перехватывает его, хотя это должны были сделать вы. Видя вашу бездеятельность, объект перехватывает стандартный системный обработчик Java, который отличается вредных характером. Он останавливает вашу программу и выводит сообщение об ошибке, которое можно увидеть в журнале LogCat:

Caused by: java.lang.ArithmeticException: divide by zero at ru.alexanderklimov.test.MainActivity.onClick(MainActivity.java:79)

Как видно, созданный объект исключения принадлежит к классу ArithmeticException, далее системный обработчик любезно вывел краткое описание ошибки и место возникновения.

Вряд ли пользователи вашей программы будут довольны, если вы так и оставите обработку ошибки системе. Если программа будет завершаться с такой ошибкой, то скорее всего вашу программу просто удалят. Посмотрим, как мы можем исправить ситуацию.

Поместим проблемный код в блок try, а в блоке catch обработаем исключение.

 int catNumber; int zero; try < // мониторим код catNumber = 1; // у меня один кот zero = 0; // ноль, он и в Африке ноль int result = 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.

Если вы хотите увидеть описание ошибки, то параметр e и поможет увидеть ёго.

 catch (ArithmeticException e)

По умолчанию, класс Trowable, к которому относится ArithmeticException возвращает строку, содержащую описание исключения. Но вы можете и явно указать метод e.toString.

Несколько исключений

Фрагмент кода может содержать несколько проблемных мест. Например, кроме деления на ноль, возможна ошибка индексации массива. В таком случае вам нужно создать два или более операторов catch для каждого типа исключения. Причём они проверяются по порядку. Если исключение будет обнаружено у первого блока обработки, то он будет выполнен, а остальные проверки пропускаются и выполнение программы продолжается с места, который следует за блоком try/catch.

 int catNumber; int zero; try < // мониторим код catNumber = 1; // у меня один кот zero = 1; // ноль, он и в Африке ноль int result = 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 равным нулю, то сработает обработка первого исключения деления на ноль, и мы даже не узнаем о существовании второй ошибки. Но допустим, что в результате каких-то вычислений значение переменной стало равно единице. Тогда наше исключение ArithmeticException не сработает. Но сработает новое добавленное исключение ArrayIndexOutOfBoundsException. А дальше всё пойдёт как раньше.

Тут всегда нужно помнить одну особенность. При использовании множественных операторов catch обработчики подклассов исключений должные находиться выше, чем обработчики их суперклассов. Иначе, суперкласс будет перехватывать все исключения, имея большую область перехвата. Иными словами, Exception не должен находиться выше ArithmeticException и ArrayIndexOutOfBoundsException. К счастью, среда разработки сама замечает непорядок и предупреждает вас, что такой порядок не годится. Увидев такую ошибку, попробуйте перенести блок обработки исключений ниже.

Вложенные операторы try

Операторы try могут быть вложенными. Если вложенный оператор try не имеет своего обработчика catch для определения исключения, то идёт поиск обработчика catch у внешнего блока try и т.д. Если подходящий catch не будет найден, то исключение обработает сама система (что никуда не годится).

Оператор throw

Часть исключений может обрабатывать сама система. Но можно создать собственные исключения при помощи оператора throw. Код выглядит так:

 throw экземпляр_Throwable 

Вам нужно создать экземпляр класса Throwable или его наследников. Получить объект класса Throwable можно в операторе catch или стандартным способом через оператор new.

Мы могли бы написать такой код для кнопки:

 Cat cat; public void onClick(View view) < if(cat == null)< throw new NullPointerException("Котик не инициализирован"); >> 

Мы объявили объект класса Cat, но забыли его проинициализировать, например, в onCreate(). Теперь нажатие кнопки вызовет исключение, которое обработает система, а в логах мы можем прочитать сообщение об ошибке. Возможно, вы захотите использовать другое исключение, например, throw new UnsupportedOperationException(«Котик не инициализирован»);.

В любом случае мы передали обработку ошибки системе. В реальном приложении вам нужно обработать ошибку самостоятельно.

Поток выполнения останавливается непосредственно после оператора throw и другие операторы не выполняются. При этом ищется ближайший блок try/catch соответствующего исключению типа.

Перепишем пример с обработкой ошибки.

 public void onClick(View view) < if (cat == null) < try < throw new NullPointerException("Кота не существует"); >catch (NullPointerException e) < Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); >> > 

Мы создали новый объект класса NullPointerException. Многие классы исключений кроме стандартного конструктора по умолчанию с пустыми скобками имеют второй конструктор с строковым параметром, в котором можно разместить подходящую информацию об исключении. Получить текст из него можно через метод getMessage(), что мы и сделали в блоке catch.

Теперь программа не закроется аварийно, а будет просто выводить сообщения в всплывающих Toast.

Оператор throws

Если метод может породить исключение, которое он сам не обрабатывает, он должен задать это поведение так, чтобы вызывающий его код мог позаботиться об этом исключении. Для этого к объявлению метода добавляется конструкция throws, которая перечисляет типы исключений (кроме исключений Error и RuntimeException и их подклассов).

Общая форма объявления метода с оператором throws:

 тип имя_метода(список_параметров) throws список_исключений < // код внутри метода >

В фрагменте список_исключений можно указать список исключений через запятую.

Создадим метод, который может породить исключение, но не обрабатывает его. А в щелчке кнопки вызовем его.

 // Метод без обработки исключения public void createCat() < Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show(); throw new NullPointerException("Кота не существует"); >// Щелчок кнопки public void onClick(View v)

Если вы запустите пример, то получите ошибку. Исправим код.

 // Без изменений public void createCat() throws NullPointerException < Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show(); throw new NullPointerException("Кота не существует"); >// Щелчок кнопки public void onClick(View v) < try < createCat(); >catch (NullPointerException e) < // TODO: handle exception Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); >> 

Мы поместили вызов метода в блок try и вызвали блок catch с нужным типом исключения. Теперь ошибки не будет.

Оператор finally

Когда исключение передано, выполнение метода направляется по нелинейному пути. Это может стать источником проблем. Например, при входе метод открывает файл и закрывает при выходе. Чтобы закрытие файла не было пропущено из-за обработки исключения, был предложен механизм finally.

Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним. Блок будет выполнен, независимо от того, передано исключение или нет. Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally.

Встроенные исключения Java

Существуют несколько готовых системных исключений. Большинство из них являются подклассами типа RuntimeException и их не нужно включать в список throws. Вот небольшой список непроверяемых исключений.

  • ArithmeticException — арифметическая ошибка, например, деление на нуль
  • ArrayIndexOutOfBoundsException — выход индекса за границу массива
  • ArrayStoreException — присваивание элементу массива объекта несовместимого типа
  • ClassCastException — неверное приведение
  • EnumConstantNotPresentException — попытка использования неопределённого значения перечисления
  • IllegalArgumentException — неверный аргумент при вызове метода
  • IllegalMonitorStateException — неверная операция мониторинга
  • IllegalStateException — некорректное состояние приложения
  • IllegalThreadStateException — запрашиваемая операция несовместима с текущим потоком
  • IndexOutofBoundsException — тип индекса вышел за допустимые пределы
  • NegativeArraySizeException — создан массив отрицательного размера
  • NullPointerException — неверное использование пустой ссылки
  • NumberFormatException — неверное преобразование строки в числовой формат
  • SecurityException — попытка нарушения безопасности
  • StringIndexOutOfBounds — попытка использования индекса за пределами строки
  • TypeNotPresentException — тип не найден
  • UnsupportedOperationException — обнаружена неподдерживаемая операция

Список проверяемых системных исключений, которые можно включать в список throws.

  • ClassNotFoundException — класс не найден
  • CloneNotSupportedException — попытка клонировать объект, который не реализует интерфейс Cloneable
  • IllegalAccessException — запрещен доступ к классу
  • InstantiationException — попытка создать объект абстрактного класса или интерфейса
  • InterruptedException — поток прерван другим потоком
  • NoSuchFieldException — запрашиваемое поле не существует
  • NoSuchMethodException — запрашиваемый метод не существует
  • ReflectiveOperationException — исключение, связанное с рефлексией

Создание собственных классов исключений

Система не может предусмотреть все исключения, иногда вам придётся создать собственный тип исключения для вашего приложения. Вам нужно наследоваться от Exception (напомню, что этот класс наследуется от Trowable) и переопределить нужные методы класса Throwable. Либо вы можете наследоваться от уже существующего типа, который наиболее близок по логике с вашим исключением.

  • final void addSuppressed(Throwable exception) — добавляет исключение в список подавляемых исключений (JDK 7)
  • Throwable fillInStackTrace() — возвращает объект класса Throwable, содержащий полную трассировку стека.
  • Throwable getCause() — возвращает исключение, лежащее под текущим исключение или null
  • String getLocalizedMessage() — возвращает локализованное описание исключения
  • String getMessage() — возвращает описание исключения
  • StackTraceElement[] getStackTrace() — возвращает массив, содержащий трассировку стека и состояний из элементов класса StackTraceElement
  • final Throwable[] getSuppressed() — получает подавленные исключения (JDK 7)
  • Throwable initCause(Throwable exception) — ассоциирует исключение с вызывающим исключением. Возвращает ссылку на исключение.
  • void printStackTrace() — отображает трассировку стека
  • void printStackTrace(PrintStream stream) — посылает трассировку стека в заданный поток
  • void printStackTrace(PrintWriter stream) — посылает трассировку стека в заданный поток
  • void setStackTrace(StackTraceElement elements[]) — устанавливает трассировку стека для элементов (для специализированных приложений)
  • String toString() — возвращает объект класса String, содержащий описание исключения.

Самый простой способ — создать класс с конструктором по умолчанию.

 // Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.exception; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >public void testMethod() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod()"); throw new HungryCatException(); // конструктор по умолчанию >public void onClick(View view) < try < testMethod(); >catch (HungryCatException e) < e.printStackTrace(); System.out.println("Наше исключение перехвачено"); >> class HungryCatException extends Exception < >> 

Мы создали собственный класс HungryCatException, в методе testMethod() его возбуждаем, а по нажатию кнопки вызываем этот метод. В результате наше исключение сработает.

Создать класс исключения с конструктором, который получает аргумент-строку, также просто.

 // Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.exception; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); >public void testMethod() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod()"); throw new HungryCatException(); // конструктор по умолчанию >public void testMethod2() throws HungryCatException < System.out.println("Возбуждаем HungryCatException из метода testMethod2()"); throw new HungryCatException("Создано во втором методе"); >public void onClick(View view) < try < testMethod(); >catch (HungryCatException e) < e.printStackTrace(); System.out.println("Наше исключение перехвачено"); >try < testMethod2(); >catch (HungryCatException e) < e.printStackTrace(); >> class HungryCatException extends Exception < HungryCatException() < >HungryCatException(String msg) < super(msg); >> > 

Ещё вариант. Добавим также метод toString().

 class CustomException extends Exception < String message; CustomException(String str) < message = str; >public String toString() < return ("Custom Exception Occurred: " + message); >> // где-то вызываем try < throw new CustomException("This is a custom message"); >catch (CustomException e)

Теперь класс содержит два конструктора. Во втором конструкторе используется конструктор родительского класса с аргументом String, вызываемый ключевым словом super.

Перехват произвольных исключений

Можно создать универсальный обработчик, перехватывающий любые типы исключения. Осуществляется это перехватом базового класса всех исключений Exception:

cacth(Exception e)

Подобная конструкция не упустит ни одного исключения, поэтому её следует размещать в самом конце списка обработчиков, во избежание блокировки следующих за ней обработчиков исключений.

Основные правила обработки исключений

Используйте исключения для того, чтобы:

  • обработать ошибку на текущем уровне (избегайте перехватывать исключения, если не знаете, как с ними поступить)
  • исправить проблему и снова вызвать метод, возбудивший исключение
  • предпринять все необходимые действия и продолжить выполнение без повторного вызова действия
  • попытаться найти альтернативный результат вместо того, который должен был бы произвести вызванный метод
  • сделать все возможное в текущем контексте и заново возбудить это же исключение, перенаправив его на более высокий уровень
  • сделать все, что можно в текущем контексте, и возбудить новое исключение, перенаправив его на более высокий уровень
  • завершить работу программы
  • упростить программу (если используемая схема обработки исключений делает все только сложнее, значит, она никуда не годится)
  • добавить вашей библиотеке и программе безопасности

Исключения

Исключения в программировании (exceptions) — это механизм, который позволяет программе обрабатывать нетипичную ситуацию и при этом не прекращать работу. Благодаря этому механизму разработчик может описать в коде реакцию программы на такие ситуации.

Освойте профессию «Java-разработчик»

Простой пример: в программе-калькуляторе исключением может стать ситуация, когда пользователь решит поделить на ноль. Это не должно стать ошибкой, из-за которой рушится вся программа, но чтобы ситуация не застопорила исполнение остального кода, нужно ее правильно обработать. Для этого необходимы обработчики исключений. Они позволяют «сказать» программе, что ей делать, если такое случится.

Механизм обработки исключений существует в большинстве языков программирования. Он может быть реализован немного по-разному, но общая суть схожа: это всегда какие-то особые случаи, которые надо обработать отдельно. Мы при описании будем отталкиваться от особенностей исключений в Java, но встретить их можно и в других языках: JavaScript, PHP, Python, C++ и так далее.

Профессия / 14 месяцев
Java-разработчик

Освойте востребованный язык

Group 1321314345 (4)

Зачем нужны исключения

Механизм обработки исключений может понадобиться любому разработчику. Если не отслеживать исключительные ситуации, может возникнуть незаметная ошибка, которая нарушит работу всего кода, или программа может «зависнуть» либо «упасть» — потому что сложный момент не был обработан как надо.

Исключения нужны, чтобы программа продолжала относительно корректно работать, даже если что-то пошло не так.

Какими бывают исключения

Исключения делятся на две большие группы, которые пересекаются друг с другом: синхронные и асинхронные. Синхронные могут возникнуть только в конкретном месте программы или при выполнении определенной операции: открытие файла, деление и так далее. Асинхронные могут возникнуть когда и где угодно. Их «ловят» по-разному, чтобы успешно отслеживать и те, и другие.

Мы сказали, что эти группы пересекаются друг с другом, хотя по логике они противоположны. Пересечение происходит потому, что при выполнении операций асинхронным может стать даже формально синхронное исключение, и наоборот.

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке

Как происходит работа с исключениями

  • Разработчик пишет код и понимает, что в какой-то момент в том или ином месте может возникнуть нештатная ситуация. Бывает, что исключения добавляют в уже написанный код — например, нештатную ситуацию обнаружили при тестировании.
  • В этом месте пишется особый блок кода — обработчик. Он говорит программе: здесь может возникнуть особая ситуация, если она случится, выполни вот это.
  • Внутри обработчика — функция, которая выполнится, если программа столкнется с описанной ситуацией. Она или исправит ситуацию, или скорректирует дальнейшее выполнение программы.

Бывают исключения, которые нельзя предусмотреть. Разработчики обрабатывают не все возможные нештатные ситуации, а только самые очевидные, чтобы не перегружать код. Это справедливо для большинства сфер разработки, кроме тех, где слишком высока цена ошибки.

Как устроена обработка исключений

Существуют разные виды обработки: структурная и неструктурная, с возвратом и без возврата. Они различаются механизмом действия, но общая суть одна: это функция, которая запускается, если в коде случилась та или иная исключительная ситуация. Тут можно использовать условный оператор if или специальные синтаксические конструкции.

В примере с делением на ноль обработчик может отменить попытку деления и сказать пользователю, что на ноль делить нельзя, — но это самый простой пример. В реальности все сложнее.

Обработка с возвратом и без возврата. Эти виды обработки различаются реакцией на случившееся исключение. Версия с возвратом предполагает, что обработчик попытается разрешить проблему, а когда ему это удастся, вернет программу к исходному поведению. В итоге она будет работать так, как если бы исключения не возникало.

Вот пример: не запустился скрипт, необходимый для работы следующего скрипта. Следующий скрипт заметил это, зафиксировал исключение и обратился к обработчику, который запустил нужный скрипт «вручную». После этого все может работать, как и было задумано.

Обработка без возврата — вид обработки, когда проблема не ликвидируется, а участок кода, который не получается выполнить, пропускается. В примере со скриптами обработка «переключила» бы выполнение кода на момент, где уже не понадобится незаработавший скрипт.

Структурная и неструктурная обработка. Это два способа подключить обработчики. В первом случае они встраиваются в код, а когда генерируется исключение, для него выбирается тот или иной обработчик в зависимости от ситуации. Во втором случае обработчики существуют отдельно и «подключаются» к конкретным видам исключений с помощью специальных команд. Способ выбирается в зависимости от вида исключения, особенностей кода и языка.

Обычно асинхронные исключения обрабатывают неструктурно, а синхронные — структурно.

Гарантированное завершение. Так называется отдельный вид функции, которая обычно пишется после обработчика. Она описывает действия, которые должны произойти в этой части кода вне зависимости от того, произошло исключение или нет.

Исключения и ошибки: разница

Кроме исключений, в языках программирования существует механизм обработки ошибок. Их часто путают, особенно новички. И то, и другое подразумевает нетипичную ситуацию, в которой работу программы нельзя продолжить корректно. Но есть и различия:

  • ошибка означает, что программа «упала», что ее работу нельзя продолжить и она должна быть завершена. Ошибку невозможно исправить — только сообщить о ней пользователю, записать в лог и прекратить исполнение кода;
  • исключение — это нештатная ситуация, которую тем не менее можно попробовать починить «на ходу», не закрывая программу. В этом есть смысл, в отличие от ситуации с ошибкой.

Это действительно похожие понятия. В Java, например, сущности исключений и ошибок наследуются от общего предка — интерфейса Throwable. Но ошибка — это явление, когда что-то сделать принципиально не получается. А исключение — ситуация, когда программа просто не знает, что делать, если не указать на это дополнительно.

Можно провести аналогию. Мама послала дочь в магазин за покупками и сказала ей купить батон хлеба. Если хлеба в магазине не оказалось, девочка не сможет его купить. Это ошибка. А если в магазине есть три вида батонов, или все батоны вчерашние, а девочка не знает, нужен ли маме только свежий хлеб, или батон есть, но только из ржаной муки, — это исключения.

В первом случае дочь просто вернется домой и ничего не купит. Из-за ошибки программа не выполняется. Во втором случае девочка позвонит маме и спросит, что ей делать. Программа передаст управление обработчику, чтобы тот разрешил сложную ситуацию.

Когда пользоваться исключениями, а когда — ошибками

В некоторых случаях разработчики описывают все нештатные ситуации как исключения. Например, при создании новых библиотек, которые должны быть очень гибкими и подразумевать многие ситуации — то, что критично для одной задачи, окажется поправимым в другой. Но это редкие случаи, и чаще приходится выбирать между обработкой ошибки и исключения.

Обработчики ошибок советуют использовать тогда, когда проблема не решаема изнутри программы. Например, у приложения нет связи с сервером — оно не может продолжать работу без этого. Или какие-то критичные файлы оказались повреждены, и из-за этого код просто нельзя исполнить. Или в системе закончилась свободная память. Это никак не поправить программными способами.

Исключениями стоит пользоваться, если возникла нештатная, неправильная ситуация, которую не подразумевает логика работы программы. Но программу при этом не нужно выключать и завершать — надо исправить или «перескочить» проблемный момент и сохранить все остальное.

Как начать пользоваться исключениями

В большинстве языков механизм обработки исключений есть по умолчанию — это популярная функция. Но приступать к работе с ними рекомендуют после изучения базовых возможностей языка. Мы советуем идти от простого к сложному: начать с основ и затем переходить к комплексным темам. Конкретно обработка исключений обычно изучается перед тем, как человек переходит к практическим проектам, потому что любая более-менее сложная программа может столкнуться с исключениями в ходе работы.

Java-разработчик

Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.

Исключения в Java

Исключения представляют собой объекты, которые позволяют обработать исключительную ситуацию. Исключительная ситуация— это ошибка, происходящая во время выполнения программы (так называемая ошибка времени выполнения). Основные типы исключений (Exception) в java.

Исключительные ситуации можно обрабатывать с помощью блока try/catch/finally.

Рассмотрим, как он работает. В блок try помещается код, в котором может содержаться исключительная ситуация. Он работает либо до возникновения исключительной ситуации, либо при успешном выполнении до конца блока try.

В блоке catch выполняются некие операции при возникновении исключительной ситуации определенного типа (в параметрах блока — в круглых скобках — указывается тип возможного исключения — т.е. класс, который обрабатывает исключительную ситуацию).

Блок catch может делать все, что угодно: вызывать необходимый класс-исключение при помощи создания его объектов с использованием ключевого слова throw (это фактически искусственная передача выполнения нужному нам блоку catch), а может и сам производить какие-либо действия.

В блоке finally выполняются определенные восстановительные операции. Он обычно предназначен для освобождения памяти, выделенной под объект. Восстановительные операции будут выполняться независимо от того, возникло исключение (теперь мы будем именно так называть исключительные ситуации) в блоке try или нет.

Блок finally может отсутствовать.
Конструкция try/catch/finally может быть вложенной. Покажем использование этой конструкции на примере (листинг 5.1).

Листинг 5.1.
Пример использования конструкции try/catch/finally

class MyException extends Exception < public void MyException() < System.out.println("Возникло исключение. "); >> class Toys < public static short numbers = 0; public static short newNumber() < return ++numbers; >public short number; public String name, size; public void setParam(String name, String size) < try < this.name = name; this.size = size; if (this.number != 0) < throw new MyException(); // Вызов метода создания объекта MyException >this.number = Toys.newNumber(); > catch (MyException e) < System.err.println("Error!"); new MyException().MyException(); >catch (Exception e) < // Блок обработки всех остальных исключений блока try. >finally < System.out.println("Итого: \nИмя игрушки: " + this.name + "\nРазмер игрушки: " + this.size + "\nНомер игрушки: " + this.number); >> public static void main(String[] args) < Toys car = new Toys(); try < car.setParam("Car", "Large"); // Какие-то действия над объектом car. car.setParam("Car", "Small"); // Вот это приведет к возникновению исключительной ситуации. >catch (Exception e) < // . >> >

Запомните важное правило: если необходимо, чтобы были отдельные обработчики исключения для базового класса и его производных, всегда в начале нужно помещать обработчик для производных классов. Если же сделать наоборот, то всегда будет выполняться обработчик для базового класса, так как он обрабатывает исключения своего типа и производных классов, а это противоречит желанию создать отдельный обработчик для производного класса.
Естественно, вы заметили, что мы создали класс-исключение как производный от класса Exception.

Подробнее создание классов-исключений мы рассмотрим позднее, а пока давайте рассмотрим использование стандартного класса ArithmeticException (листинг 5.2).

Листинг 5.2.
Пример использования стандартного класса ArithmeticException

class Program < public static void main(String[] args) < int a = 10; try < a = a / Integer.parseInt(args[0]); System.out.println("10 / " + args[0] + " = " + a); >catch (ArithmeticException e) < System.out.println("Error!"); >catch (ArrayIndexOutOfBoundsException e) < System.out.println("Выходза пределы массива"); // А если пользователь не ввел значения в коммандной строке? >finally < System.out.println("That\'s all. "); >> >

Классы-исключения в большинстве своем наследуются от производного класса Exception. Можно использовать явное (прямое) обращение к объекту исключений, чтобы вызвать необходимые действия. Напомним о классе из первого примера, который позволяет обрабатывать исключение.

class MyException extends Exception < public void MyException() < System.out.println ("Возникло исключение. "); >>

Если необходимо, чтобы с помощью некоего класса обрабатывались какие-либо ошибки в методе, надо после круглых скобок, где указываются параметры, написать ключевое слово throws, а после него— имя класса, который должен обрабатывать какую-либо ошибку.

Если же нужно использовать несколько классов, лучше всего создать базовый класс — и тогда метод будет обрабатывать ошибки с помощью базового класса и всех его производных. Искусственно создать исключения можно с помощью оператора throw. Приведем пример использования класса MyException (листинг 5.3).

Листинг 5.3.
Пример использования класса MyException

class Toys < public static short numbers = 0; public static short newNumber() < return ++numbers; >public short number; public String name, size; public void setParam(String name, String size) < try < this.name = name; this.size = size; if (this.number != 0) < throw new MyException(); // Вызов метода создания объекта MyException >this.number = Toys.newNumber(); > catch (MyException e) < System.err.println("Error!"); new MyException().MyException(); >catch (Exception e) < // Блок обработки всех остальных исключений блока try. >finally < System.out.println("Итого: \nИмя игрушки: " + this.name + "\nРазмер игрушки: " + this.size + "\nНомер игрушки: " + this.number); >> >

Так как мы пытаемся вторично вызвать статический метод создания номера данного объекта, вызовем класс-исключение, который выдает несколько строк и продолжает выполнение программы, не выполнив код приращения номера.

Хотя в языке Java предусмотрен стандартный класс-исключение для ошибок при попытке деления на 0, сейчас мы создадим собственный класс-исключение, который будет обрабатывать подобные ошибки (листинг 5.4).

Листинг 5.4.
Создание класса для обработки ошибок

class MyDivByZero extends Exception < public void MyDivByZero() < System.out.println("Что такое? Вы решили делить на 0?"); >> class Arithmetic < public static double myDelete(double a, double b) throws MyDivByZero < double c; try < if (b == 0.0) < throw new MyDivByZero(); >c = a / b; > catch (MyDivByZero e) < c = 0.0; System.exit(0); >return c; > public static void main(String[] args) < try < System.out.println("5.1, разделить на 1.2 будет " + myDelete(5.1, 1.2)); System.out.println("3.6, разделить на 0 будет " + myDelete(3.6, 0)); System.out.println("7.8, разделить на 3.9 будет " + myDelete(7.8, 3.9)); >catch (MyDivByZero e) < // . >> >

В данном случае мы сами разделили на 0, и проблемы с обработкой исключения не возникло (это всего лишь ошибка программиста — он ее исправит). А вдруг необходимо попросить пользователя ввести число и он введет 0? Тогда-то и будет проблема.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *