Тема 24. Исключения - BelyiZ/JavaCourses GitHub Wiki
Содержание:
- Зачем нужны исключения?
- Типы исключений
- Генерация исключений
- Обработка исключений
- Список литературы/курсов
Зачем нужны исключения?
В повседневной жизни иногда возникают ситуации, которые мы совсем не планировали. Встаешь утром, опаздываешь, знаешь точно, что определенная вещь лежит в конкретном месте, а ее там нет. Включаешь воду – а ее отключили. Пытаешься попасть на работу, а машина не заводится. Но каждый раз мы справляемся с непредвиденными ситуациями. А как с ними справляются Java-программы, сейчас мы с вами узнаем. В программировании возникновение ошибок и непредвиденных ситуаций при выполнении программы называют исключением. Ошибки могут возникать по причине неправильных действий пользователя, отсутствии необходимого ресурса на диске, или потери соединения. Причинами исключений при выполнении программы также могут быть ошибки программирования или неправильное использование API. В таких случаях программа должна четко знать, как поступать в такой ситуации. Для этого в Java предусмотрен механизм исключений.
Исключения в Java
Возможность предупреждения и разрешения исключительной ситуации в программе для ее продолжения – одна из причин использования исключений в Java.
Механизм исключений также позволяет защитить написанный вами код от неправильного использования пользователем за счет валидации (проверки) входящих данных.
На стадии разработки программы мы «ограждаем» опасные участки кода в отношении исключений с помощью блока try{}
,
предусматриваем «запасные» пути с помощью блока catch{}
, а в блоке finally{}
мы пишем код, который выполняется в программе при любом исходе.
В случаях, когда мы не можем предусмотреть «запасной путь» или намеренно хотим предоставить право его выбора пользователю,
мы должны, по крайней мере, предупредить его об опасности. Почему? А вы только вообразите негодование водителя,
доехавшего до аварийного моста, по которому нельзя проехать, не встретив по дороге ни одного предупреждающего знака!
В программировании при написании своих классов и методов мы не всегда можем предвидеть контекст их использования другими разработчиками в своих программах,
поэтому не можем предвидеть на 100% правильный путь для разрешения исключительной ситуации.
В то же время, правило хорошего тона — предупредить пользователей нашего кода о возможности исключительной ситуации.
Механизм исключений Java позволяет нам сделать это с помощью throws
– по сути, объявления общего поведения нашего метода,
заключающееся в выбрасывании исключения, и предоставляя, таким образом, написание кода по обработке исключения в Java пользователю метода.
Типы исключений
При возникновении ошибки в процессе выполнения программы исполняющая среда JVM создает объект нужного типа из иерархии исключений Java –
множества возможных исключительных ситуаций, унаследованных от общего класса-родителя Throwable
.
В Java исключения являются иерархическими, а наследование используется для категоризации различных типов исключений.
Throwable
— родительский класс в иерархии Java исключений. Он имеет два дочерних класса — Error
и Exception
.
Исключительные ситуации, возникающие в программе, можно разделить на две группы:
ситуации, при которых восстановление дальнейшей нормальной работы программы невозможно и те,
где приложение может продолжать корректную работу.
images/exceptions/exceptions-classes.webp
Непроверяемые исключения
К первой группе относят ситуации, когда возникают исключения, унаследованные из класса Error
.
Error
— это тип ошибок, которые выходят за рамки вашей программы, их невозможно предвидеть или обработать.
Это может быть аппаратный сбой, «поломка» JVM или ошибка памяти. Именно для таких необычных ситуаций есть отдельная иерархия ошибок.
Мы должны просто знать, что такие ошибки есть и не можем справиться с такими ситуациями.
Примеры Error: OutOfMemoryError
и StackOverflowError
.
Это ошибки обычно свидетельствуют о серьезных проблемах, устранить которые, зачастую, программными средствами невозможно.
Такой вид исключений в Java относится к неконтролируемым или непроверяемым (unchecked) на стадии компиляции.
К этой группе также относят RuntimeException
– исключения, наследники класса Exception
, генерируемые JVM во время выполнения программы.
Часто причиной возникновения их являются ошибки программирования. Эти исключения — это ошибки программиста,
и они также являются непроверяемыми на стадии компиляции, поэтому написание кода по их обработке не является обязательным.
Исключения. Например, пытаясь получить элемент из массива, мы должны проверить длину массива,
прежде чем пытаться получить элемент — в противном случае это может быть брошен ArrayIndexOutOfBoundException
.
RuntimeException
— родительский класс для всех непроверяемых исключений генерируемых во время исполнения приложения.
Если мы сами бросаем RuntimeException
в методе, то необязательно указывать в сигнатуре метода ключевое слово throws
.
Проверяемые исключения
Ко второй группе относят исключительные ситуации, предвидимые еще на стадии написания программы,
и для которых должен быть написан код обработки, например, FileNotFoundException
. Такие исключения являются проверяемыми (checked).
Мы должны отлавливать каждую такую исключительную ситуацию и обрабатывать ее, например, написав внятное и полезное сообщение пользователю о том, что произошло.
Exception
— родительский класс всех проверяемых исключений, создавая свои классы исключения, их нужно наследовать от него.
Если мы бросили проверяемое исключение, то должны поймать его в том же методе или должны пробросить его с помощью ключевого слова throws
.
Основная часть работы разработчика на Java при работе с исключениями – обработка таких ситуаций.
Генерация исключений
Создание (генерация) исключения
При исполнении программы исключение генерируется JVM или вручную, с помощью оператора throw
.
При этом в памяти создается объект исключения и выполнение основного кода программы прерывается,
а обработчик исключений JVM пытается найти способ обработать исключение.
Создание собственных классов-исключений
Существует возможность написать собственные исключения для обработки тех или иных ошибок,
если нам недостаточно уже существующих. Для этого мы просто создаем класс наследуемый от
класса-родителя Exception
или RuntimeException
.
public class ProjectNotFoundException extends Exception {
public ProjectNotFoundException (String projectName) {
super("Project with name [" + projectName + "] not found");
}
}
При создании собственных исключений следует учитывать два правила:
- Название нашего класса должно оканчиваться на «Exception»
- Класс должен содержать конструктор со строковой переменной, описывающей детали проблемы исключения. В конструкторе вызывается супер-конструктор с передачей сообщения.
Пример использования созданного исключения:
public class ProjectsManager {
public Project find(String projectName) throws ProjectNotFoundException {
Project project = findProjectByName(projectName);
if (project == null) {
throw new ProjectNotFoundException(projectName);
}
return project;
}
}
Отлавливаем это исключение кодом:
public class ExceptionsLesson {
public static void main(String[] args) {
ProjectsManager manager = new ProjectsManager();
try {
Project project = manager.find("The best project");
} catch (ProjectNotFoundException e) {
System.err.print(e);
}
}
}
Результатом выполнения программы будет:
ProjectNotFoundException: Project with name [The best project] not found
.
Обработка исключений
Конструкция try-catch-finally
Обработка исключений в Java основана на использовании в программе следующих ключевых слов:
try
– определяет блок кода, в котором может произойти исключение.catch
– определяет блок кода, в котором происходит обработка исключения.finally
– определяет блок кода, который является необязательным, но при его наличии выполняется в любом случае независимо от результатов выполнения блокаtry
.
Эти ключевые слова используются для создания в программном коде специальных обрабатывающих конструкций:
try-catch
, try-catch-finally
, try-finally
.
throw
– используется для генерации исключения.
throws
– используется в сигнатуре методов для предупреждения, о том что метод может выбросить исключение.
Общий вид конструкции для обработки исключительной ситуации выглядит следующим образом:
try{
//здесь код, который потенциально может привести к ошибке
} catch(SomeException e ){ //в скобках указывается класс конкретной ожидаемой ошибки
//здесь описываются действия, направленные на обработку исключений
} finally{
//выполняется в любом случае ( блок finally не обязателен)
}
Одному блоку try может соответствовать сразу несколько блоков catch с разными классами исключений.
class Main {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
} catch (FileNotFoundException ex) {
System.out.println("File not found! Check name or path");
} catch (IOException e) {
System.out.println(e);
}
}
}
Здесь если файл не найден, то будет выведено на экран "File not found! Check name or path" и программа завершится,
если же файл найден, но, например, он не доступен для записи то на экран будет выведены данные брошенного исключения.
Важная особенность, последовательность блоков catch
должна идти от частного к более общему. В противном случае будет ошибка компиляции.
try{
fis = new FileInputStream(fileName);
} catch (Exception ex){
//...
} catch (IOException e) { // <--- Ошибка
//...
}
В Java 7 стала доступна новая конструкция, с помощью которой можно пере отлавливать несколько исключений одни блоком catch:
try { ...
} catch( IOException | SQLException ex ) {
logger.log(ex);
throw ex;
}
Это удобно, если обработка ошибок не отличается.
Конструкция try-with-resources
В Java 7 ввели ещё одну конструкцию, призванную упростить жизнь программисту. Если раньше приходилось писать так:
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException{
BufferedReader br = new BufferedReader(new FileReader(path));
try{
return br.readLine();
} finally{
try {
if(br!=null)br.close();
} catch(Exception e) {
}
}
}
То теперь это можно сделать так:
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Но, где же закрытие потока br?
Дело в том, что в Java SE 7 поток BufferedReader
реализует интерфейс java.lang.AutoCloseable
и теперь в конструкции try-with-resources
нет необходимости закрывать его.
Наследование методов бросающих исключения
Рассмотрим следующий код:
public class SuperClass {
public void start() throws IOException{
throw new IOException("Not able to open file");
}
}
public class SubClass extends SuperClass{
public void start() throws Exception{
throw new Exception("Not able to start");
}
}
Он не скомпилируется.
Дело в том, что метод start()
был переопределен в дочернем классе и в сигнатуре был указан более общий класс исключения.
Согласно правилам переопределения методов, это действие не допустимо.
Можно лишь сужать класс исключения:
import java.io.FileNotFoundException;
import java.io.IOException;
public class SuperClass {
public void start() throws IOException{
throw new IOException("Not able to open file");
}
}
class SubClass extends SuperClass{
public void start() throws FileNotFoundException{
// FileNotFoundException - наследник IOExceptio
throw new FileNotFoundException("Not able to start");
}
}
Проброс исключений без обработки
Когда вы не планируете обрабатывать исключение в своем методе, но хотите предупредить пользователей метода
о возможных исключительных ситуациях — используйте ключевое слово throws
.
Это ключевое слово в сигнатуре метода означает, что при определенных условиях метод, может выбросить исключение.
Такое предупреждение является частью интерфейса метода и предоставляет право пользователю на собственный вариант
реализации обработчика исключения. После throws мы указываем тип выбрасываемого исключения. Обычно это наследники класса Exception
.
Поскольку Java является объектно-ориентированным языком, все исключения в Java представляют собой объекты.
Список литературы/курсов
- https://javarush.ru/groups/posts/isklyucheniya-java
- https://www.programcreek.com/2009/02/diagram-for-hierarchy-of-exception-classes/
- https://www.codejava.net/java-core/exception/java-checked-and-unchecked-exceptions (список проверяемых и непроверяемых исключений с библиотеками)
Тема 23. Spliterator | Оглавление | Тема 25. Работа с файлами