Перейти к содержимому

Как игнорировать исключения в java

  • автор:

Обработка исключений в Java: Полное руководство с лучшими и наихудшими практиками

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

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

Что такое Обработка исключений?

Мы ежедневно сталкиваемся с обработкой исключений в реальной жизни.

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

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

Зачем использовать обработку исключений?

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

К сожалению, на самом деле окружающая среда далека от идеала – файл не может быть найден, время от времени прерывается подключение к Интернету, а JVM не может предоставить достаточно памяти, и мы остаемся с пугающей Ошибкой StackOverflow .

Если мы не справимся с такими условиями, все приложение окажется в руинах, а весь остальной код устареет. Поэтому мы должны уметь писать код, который может адаптироваться к таким ситуациям.

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

Иерархия исключений

Все это просто напрашивается вопрос – каковы эти исключения в глазах Java и JVM?

В конце концов, исключения-это просто объекты Java, которые расширяют интерфейс Throwable :

---> Throwable Exception Error | (checked) (unchecked) | RuntimeException (unchecked)

Когда мы говорим об исключительных условиях, мы обычно имеем в виду одно из трех:

  • Проверенные Исключения
  • Непроверенные Исключения/Исключения во Время Выполнения
  • Ошибки

Примечание : Термины “Время выполнения” и “Непроверенный” часто используются взаимозаменяемо и относятся к одному и тому же типу исключений.

Проверенные Исключения

Проверенные исключения-это исключения, которые мы обычно можем предвидеть и планировать заранее в нашем приложении. Это также исключения, которые компилятор Java требует, чтобы мы либо обрабатывали, либо объявляли при написании кода.

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

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

Непроверенные Исключения

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

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

Ошибки

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

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

Как обрабатывать исключения

бросок и броски

Самый простой способ устранить ошибку компилятора при работе с проверенным исключением – просто выбросить ее.

public File getFile(String url) throws FileNotFoundException < // some code throw new FileNotFoundException(); >

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

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

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

попробуйте-поймайте блоки

Более распространенным подходом было бы использовать try – catch блок для перехвата и обработки возникающего исключения:

public String readFirstLine(String url) throws FileNotFoundException < try < Scanner scanner = new Scanner(new File(url)); return scanner.nextLine(); >catch(FileNotFoundException ex) < throw ex; >>

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

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

Запуск этого фрагмента кода без допустимого URL-адреса приведет к появлению исключения:

Exception in thread "main" java.io.FileNotFoundException: some_file (The system cannot find the file specified) (FileInputStream.java:138) at java.util.Scanner.(Scanner.java:611) at Exceptions.ExceptionHandling.readFirstLine(ExceptionHandling.java:15)  

В качестве альтернативы, мы можем попытаться оправиться от этого состояния вместо того, чтобы бросать:

public static String readFirstLine(String url) < try < Scanner scanner = new Scanner(new File(url)); return scanner.nextLine(); >catch(FileNotFoundException ex) < System.out.println("File not found."); return null; >>

Запуск этого фрагмента кода без действительного URL-адреса приведет к:

File not found.
наконец, Блоки

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

Это часто использовалось для закрытия ресурсов, открытых в блоке try , поскольку возникающее исключение пропускало бы код, закрывающий их:

public String readFirstLine(String path) throws IOException < BufferedReader br = new BufferedReader(new FileReader(path)); try < return br.readLine(); >finally < if(br != null) br.close(); >>

Однако этот подход был осужден после выпуска Java 7, которая представила лучший и более чистый способ закрытия ресурсов, и в настоящее время рассматривается как плохая практика.

заявление о попытке использования ресурсов

Ранее сложный и подробный блок может быть заменен:

static String readFirstLineFromFile(String path) throws IOException < try(BufferedReader br = new BufferedReader(new FileReader(path))) < return br.readLine(); >>

Это намного чище и, очевидно, упрощено, если включить объявление в круглые скобки блока try .

Кроме того, вы можете включить в этот блок несколько ресурсов, один за другим:

static String multipleResources(String path) throws IOException < try(BufferedReader br = new BufferedReader(new FileReader(path)); BufferedWriter writer = new BufferedWriter(path, charset)) < // some code >>

Таким образом, вам не нужно беспокоиться о закрытии ресурсов самостоятельно, так как блок try-with-resources гарантирует, что ресурсы будут закрыты в конце инструкции.

Несколько блоков захвата

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

public void parseFile(String filePath) < try < // some code >catch (IOException ex) < // handle >catch (NumberFormatException ex) < // handle >>

Когда блок try вызывает исключение, JVM проверяет, является ли первое пойманное исключение подходящим, и если нет, продолжает, пока не найдет его.

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

Перехват FileNotFound исключения в этом примере не требуется, поскольку он распространяется из Исключение IOException , но если возникнет необходимость, мы сможем поймать его до Исключения IOException :

public void parseFile(String filePath) < try < // some code >catch(FileNotFoundException ex) < // handle >catch (IOException ex) < // handle >catch (NumberFormatException ex) < // handle >>

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

Примечание : При перехвате нескольких исключений компилятор Java требует, чтобы мы помещали более конкретные исключения перед более общими, иначе они были бы недоступны и привели бы к ошибке компилятора.

Блоки захвата соединения

Чтобы уменьшить шаблонный код, в Java 7 также были введены блоки union catch/|. Они позволяют нам одинаково обрабатывать несколько исключений и обрабатывать их исключения в одном блоке:

public void parseFile(String filePath) < try < // some code >catch (IOException | NumberFormatException ex) < // handle >>

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

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

Создание проверенного исключения

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

public void countUsers() throws TooManyUsersException < int numberOfUsers = 0; while(numberOfUsers < 500) < // some code numberOfUsers++; >throw new TooManyUsersException("The number of users exceeds our maximum recommended amount."); > >

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

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

Определить подобное исключение так же просто, как написать следующее:

public class TooManyUsersException extends Exception < public TooManyUsersException(String message) < super(message); >>
Создание непроверенного исключения

Создание исключений во время выполнения обычно сводится к проверке входных данных, поскольку они чаще всего возникают из – за неправильного ввода-либо в виде исключения IllegalArgumentException , NumberFormatException , ArrayIndexOutOfBoundsException , либо NullPointerException :

public void authenticateUser(String username) throws UserNotAuthenticatedException < if(!isAuthenticated(username)) < throw new UserNotAuthenticatedException("User is not authenticated!"); >>

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

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

public class UserNotAuthenticatedException extends RuntimeException < public UserNotAuthenticatedException(String message) < super(message); >>
Бросание

Создание исключения упоминалось ранее, поэтому вот краткий раздел для уточнения:

public String readFirstLine(String url) throws FileNotFoundException < try < Scanner scanner = new Scanner(new File(url)); return scanner.nextLine(); >catch(FileNotFoundException ex) < throw ex; >>

Повторное выбрасывание относится к процессу выбрасывания уже пойманного исключения, а не к выбрасыванию нового.

Обертывание

Обертывание, с другой стороны, относится к процессу обертывания уже пойманного исключения в другое исключение:

public String readFirstLine(String url) throws FileNotFoundException < try < Scanner scanner = new Scanner(new File(url)); return scanner.nextLine(); >catch(FileNotFoundException ex) < throw new SomeOtherException(ex); >>
Бросание бросаемого или _Exception*?

Эти классы верхнего уровня могут быть пойманы и переосмыслены, но как это сделать, может варьироваться:

public void parseFile(String filePath) < try < throw new NumberFormatException(); >catch (Throwable t) < throw t; >>

В этом случае метод создает исключение NumberFormatException , которое является исключением во время выполнения. Из-за этого нам не нужно отмечать подпись метода с помощью NumberFormatException или Throwable .

Однако, если мы создадим проверенное исключение в методе:

public void parseFile(String filePath) throws Throwable < try < throw new IOException(); >catch (Throwable t) < throw t; >>

Теперь мы должны объявить, что метод выбрасывает Бросаемый . Почему это может быть полезно-это широкая тема, которая выходит за рамки данного блога, но для этого конкретного случая есть способы использования.

Наследование исключений

Подклассы, наследующие метод, могут создавать только меньшее количество проверенных исключений, чем их суперкласс:

public class SomeClass < public void doSomething() throws SomeException < // some code >>

При таком определении следующий метод вызовет ошибку компилятора:

public class OtherClass extends SomeClass < @Override public void doSomething() throws OtherException < // some code >>

Лучшие и худшие методы обработки исключений

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

Лучшие Методы Обработки Исключений

Избегайте Исключительных Условий

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

public Employee getEmployee(int i) < Employee[] employeeArray = ; if(i >= employeeArray.length) < System.out.println("Index is too high!"); return null; >else < System.out.println("Employee found: " + employeeArray[i].name); return employeeArray[i]; >> >

Вызов этого метода с допустимым индексом приведет к:

Employee found: Scott

Но вызов этого метода с индексом, который выходит за рамки, приведет к:

Index is too high!

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

Используйте попытку с ресурсами

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

Закройте ресурсы в try-catch-наконец-то

Если вы по какой-либо причине не используете предыдущий совет, по крайней мере, не забудьте закрыть ресурсы вручную в блоке “Наконец”.

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

Наихудшие Методы Обработки Исключений

Глотание Исключений

Если вы намерены просто удовлетворить компилятор, вы можете легко сделать это, проглотив исключение :

public void parseFile(String filePath) < try < // some code that forms an exception >catch (Exception ex) <> >

Проглатывание исключения относится к акту перехвата исключения и не устранения проблемы.

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

Еще одна очень распространенная практика-просто распечатать трассировку стека исключения:

public void parseFile(String filePath) < try < // some code that forms an exception >catch(Exception ex) < ex.printStackTrace(); >>

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

Вернитесь в последний блок

Если выполнение блока try завершается внезапно по какой-либо другой причине R, то наконец блок выполняется, и тогда есть выбор.

Итак, в терминологии документации, если блок наконец завершается нормально, то оператор try завершается внезапно по причине R.

Если блок finally завершается внезапно по причинам, то оператор try завершается внезапно по причинам (и причина R отбрасывается).

По сути, при резком возврате из блока finally JVM удалит исключение из блока try , и все ценные данные из него будут потеряны:

public String doSomething() < String name = "David"; try < throw new IOException(); >finally < return name; >>

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

Бросаю в последний блок

Очень похоже на предыдущий пример, использование throw в наконец блоке приведет к удалению исключения из try-catch блока:

public static String doSomething() < try < // some code that forms an exception >catch(IOException io) < throw io; >finally < throw new MyException(); >>

В этом примере исключение MyException , созданное внутри блока finally , затмит исключение, созданное блоком catch , и вся ценная информация будет удалена.

Имитация оператора goto

Критическое мышление и творческие способы поиска решения проблемы-хорошая черта, но некоторые решения, какими бы творческими они ни были, неэффективны и избыточны.

Java не имеет оператора goto , как некоторые другие языки, а скорее использует метки для перехода по коду:

public void jumpForward() < label: < someMethod(); if (condition) break label; otherMethod(); >>

И все же некоторые люди используют исключения для их имитации:

public void jumpForward() < try < // some code 1 throw new MyException(); // some code 2 >catch(MyException ex) < // some code 3 >>

Использование исключений для этой цели неэффективно и медленно. Исключения предназначены для исключительного кода и должны использоваться для исключительного кода.

Лесозаготовки и метания

При попытке отладить фрагмент кода и выяснить, что происходит, не регистрируйте и не создавайте исключение:

public static String readFirstLine(String url) throws FileNotFoundException < try < Scanner scanner = new Scanner(new File(url)); return scanner.nextLine(); >catch(FileNotFoundException ex) < LOGGER.error("FileNotFoundException: ", ex); throw ex; >>

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

Перехват исключения или Выбрасывание

Почему бы нам просто не перехватить исключение или Выбрасываемое, если оно улавливает все подклассы?

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

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

Ловить Бросаемый поймает все . Это включает в себя все ошибки, которые на самом деле никоим образом не должны быть пойманы.

Вывод

В этой статье мы рассмотрели исключения и обработку исключений с нуля. После этого мы рассмотрели лучшие и худшие методы обработки исключений в Java.

Надеюсь, вы нашли этот блог информативным и образовательным, счастливого кодирования!

Читайте ещё по теме:

  • Подавленные исключения Java
  • Руководство по ключевому слову Java finally
  • Обработка исключений в Java
  • Java Улавливает Несколько Исключений, Перестраивает Исключение
  • Вопросы и ответы для интервью с исключениями Java
  • Java обеспечивает надежную структуру для обработки исключений. Узнайте об обработке исключений в java с примерами программ и рекомендациями.
  • Обработка исключений весной
  • Метки block, exception, handling

Полное руководство по обработке исключений в Java

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

Обработка исключений в Java. Краткий обзор

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

Java — объектно-ориентированный язык программирования, поэтому всякий раз, когда происходит ошибка при выполнении инструкции, создается объект-исключение, а затем нормальный ход выполнения программы останавливается и JRE пытается найти кого-то, кто может справиться (обработать) это исключение. Объект-исключение содержит много информации об отладке, а именно номер строки, где произошло исключение, тип исключения и т.д.

Что и как происходит, когда появляется ошибка

Когда в методе происходит исключение, то процесс создания объекта-исключения и передачи его в Runtime Environment называется «бросать исключение».

После создания исключения, Java Runtime Environment пытается найти обработчик исключения.

Обработчик исключения — блок кода, который может обрабатывать объект-исключение.

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

Пример

У нас есть 3 метода, каждый из которых вызывает друг-друга: А -> В -> С (А вызывает В, а В вызывает С). Если исключение появляется в методе C, то поиск соответствующего обработчика будет происходить в обратном порядке: С -> В -> А (сначала там, где было исключение — в С, если там нет обработчика, то идем в метод В — если тут тоже нет, то идем в А).

Если соответствующий обработчик исключений будет найден, то объект-исключение передаётся обработчику.

Обработать исключение — значит «поймать исключение».

Если обработчик исключений не был найден, то программа завершает работу и печатает информации об исключении.

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

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

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

  • Бросить исключение ( throw ) — ключевое слово, которое используется для того, чтобы бросить исключение во время выполнения. Мы знаем, что Java Runtime начинает поиск обработчика исключений как только оно будет брошено, но часто нам самим нужно генерировать исключение в нашем коде, например, в программе авторизации, если какое-то поле null . Именно для таких случаем и существует возможность бросить исключение.
  • throws — когда мы бросаем исключение в методе и не обрабатываем его, то мы должны использовать ключевое слово throws в сигнатуре метода для того, чтобы пробросить исключение для обработки в другом методе. Вызывающий метод может обработать это исключение или пробросить его еще дальше с помощью throws в сигнатуре метода. Следует отметить, что пробрасывать можно сразу несколько исключений.
  • Блок try-catch используется для обработки исключений в коде. Слово try — это начало блока обработки, catch — конец блока для обработки исключений. Мы можем использовать сразу несколько блоков catch при одном try . catch в качестве параметра принимает тип исключения для обработки.
  • finally — необязательная завершающая конструкция блока try-catch . Как только исключение остановило процесс исполнения программы, в finally мы можем безопасно освободить какие-то открытые ресурсы. Следует отметить, что finally блок выполняется всегда — не смотря на появление исключительной ситуации.

Давайте посмотрим простую программу обработки исключений в Java.

package ua . com . prologistic ;
import java . io . FileNotFoundException ;
import java . io . IOException ;
public class ExceptionHandling < // в методе main() пробрасывается сразу несколько исключений public static void main ( String [ ] args ) throws FileNotFoundException , IOException < // в блоке try-catch перехватываются сразу несколько исключений вызовом дополнительного catch(. ) testException ( - 5 ) ; testException ( - 10 ) ; > catch ( FileNotFoundException e ) < e . printStackTrace ( ) ; > catch ( IOException e ) < e . printStackTrace ( ) ;

System . out . println ( «Необязательный блок, но раз уже написан, то выполнятся будет не зависимо от того было исключение или нет» ) ;

testException ( 15 ) ;
// тестовый метод создания, обработки и пробрасывания исключения
public static void testException ( int i ) throws FileNotFoundException , IOException < FileNotFoundException myException = new FileNotFoundException ( "число меньше 0: " + i ) ; throw myException ; > else if ( i > 10 ) < throw new IOException ( "Число должно быть в пределах от 0 до 10" ) ;

А в консоле эта программа напишет такое:

java . io . FileNotFoundException : число меньше 0 : — 5
at ua . com . prologistic . ExceptionHandling . testException ( ExceptionHandling . java : 24 )
at ua . com . prologistic . ExceptionHandling . main ( ExceptionHandling . java : 10 )

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

Exception in thread «main» java . io . IOException : Число должно быть в пределах от 0 до 10
at ua . com . prologistic . ExceptionHandling . testException ( ExceptionHandling . java : 27 )
at ua . com . prologistic . ExceptionHandling . main ( ExceptionHandling . java : 19 )

Обратите внимание, что метод testException() бросает исключение, используя ключевое слово throw , а в сигнатуре метода используется ключевое слово throws , чтобы дать понять вызывающему методу тип исключений, которые может бросить testException() .

Важные моменты в обработке исключений:

  • Нельзя использовать блоки catch или finally без блока try .
  • Блок try также может быть использован только с catch блоком, или только с finally блоком, или с тем и другим блоком.
  • Мы можем использовать несколько блоков catch только с одним try .
  • try-catch блоки могут быть вложенными — этим они очень похожи на if-else конструкции.
  • Мы можем использовать только один, блок finally в одном try-catch .

Иерархия исключений в Java

Java исключения являются иерархическими, а наследование используется для категоризации различных типов исключений. Throwable — родительский класс в иерархии Java исключений. Он имеет два дочерних объекта — Error и Exception . Исключения далее разделены на проверяемые исключения и исключения времени выполнения.

  1. Error — это тип ошибок, которые выходят за рамки вашей программы, их невозможно предвидеть или обработать. Это может быть аппаратный сбой, «поломка» JVM или ошибка памяти. Именно для таких необычных ситуаций есть отдельная иерархия ошибок. Мы должны просто знать, что такие ошибки есть и не можем справиться с такими ситуациями. Примеры Error : OutOfMemoryError и StackOverflowError .
  2. Проверяемые исключения (Checked Exceptions) — тип исключений, которые мы можем предвидеть в программе и попытаться обработать, например, FileNotFoundException . Мы должны поймать это исключение и написать внятное и полезное сообщение пользователю о том, что произошло (также желательно логировать ошибки). Exception — родительский класс всех проверяемых исключений (Checked Exceptions). Если мы бросили проверяемое исключение, то должны поймать его в том же методе или должны пробросить его с помощью ключевого слова throws .
  3. Runtime Exception — это ошибки программиста. Например, пытаясь получить элемент из массива, мы должны проверить длину массива, прежде чем пытаться получить элемент — в противном случае это может быть брошен ArrayIndexOutOfBoundException . RuntimeException — родительский класс для всех Runtime исключений. Если мы сами бросаем Runtime Exception в методе, то не обязательно указывать в сигнатуре метода ключевое слово throws .

На рисунке 1 представлена иерархия исключений в Java:

иерархия исключений в Java

Рисунок 1 — Иерархия исключений в Java

Полезные методы в обработке исключений

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

Полезные методы класса Throwable :

  1. public String getMessage() — этот метод возвращает сообщение, которое было создано при создании исключения через конструктор.
  2. public String getLocalizedMessage() — метод, который переопределяют подклассы для локализации конкретное сообщение об исключении. В реализации Throwable класса этот метод просто использует метод g etMessage() , чтобы вернуть сообщение об исключении ( Throwable на вершине иерархии — ему нечего локализировать, поэтому он вызывает getMessage()) .
  3. public synchronized Throwable getCause() — этот метод возвращает причину исключения или идентификатор в виде null , если причина неизвестна.
  4. public String toString() — этот метод возвращает информацию о Throwable в формате String .
  5. public void printStackTrace() — этот метод выводит информацию трассировки стека в стандартный поток ошибок, этот метод перегружен и мы можем передать PrintStream или PrintWriter в качестве аргумента, чтобы написать информацию трассировки стека в файл или поток.

Автоматическое управление ресурсами и улучшения блока перехвата ошибок в Java 7

Если вам нужно перехватывать много исключений в одном блоке try-catch , то блок перехвата будет выглядеть очень некрасиво и в основном будет состоять из избыточного кода. Именно поэтому в Java 7 это было значительно улучшено и теперь мы можем перехватывать несколько исключений в одном блоке catch .

Это выглядит следующим образом:

Исключения в Java, Часть I (try-catch-finally)

Это первая часть статьи, посвященной такому языковому механизму Java как исключения (вторая (checked/unchecked) вот). Она имеет вводный характер и рассчитана на начинающих разработчиков или тех, кто только приступает к изучению языка.

Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).

1. Ключевые слова: try, catch, finally, throw, throws
  • try
  • catch
  • finally
  • throw
  • throws

«Магия» (т.е. некоторое поведение никак не отраженное в исходном коде и потому неповторяемое пользователем) исключений #1 заключается в том, что catch, throw, throws можно использовать исключительно с java.lang.Throwable или его потомками.

throws:
Годится

public class App < public static void main(String[] args) throws Throwable <>> 
public class App < public static void main(String[] args) throws String <>> >> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String' 

catch:
Годится

public class App < public static void main(String[] args) < try < >catch (Throwable t) <> > > 
public class App < public static void main(String[] args) < try < >catch (String s) <> > > >> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String' 

throw:
Годится

public class App < public static void main(String[] args) < // Error - потомок Throwable throw new Error(); >> 
public class App < public static void main(String[] args) < throw new String("Hello!"); >> >> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String' 

Кроме того, throw требуется не-null аргумент, иначе NullPointerException в момент выполнения

public class App < public static void main(String[] args) < throw null; >> >> RUNTIME ERROR: Exception in thread "main" java.lang.NullPointerException 

throw и new — это две независимых операции. В следующем коде мы независимо создаем объект исключения и «бросаем» его

public class App < public static void main(String[] args) < Error ref = new Error(); // создаем экземпляр throw ref; // "бросаем" его >> >> RUNTIME ERROR: Exception in thread "main" java.lang.Error 

Однако, попробуйте проанализировать вот это

public class App < public static void main(String[] args) < f(null); >public static void f(NullPointerException e) < try < throw e; >catch (NullPointerException npe) < f(npe); >> > >> RUNTIME ERROR: Exception in thread "main" java.lang.StackOverflowError 
2. Почему используем System.err, а не System.out

System.out — buffered-поток вывода, а System.err — нет. Таким образом вывод может быть как таким

public class App < public static void main(String[] args) < System.out.println("sout"); throw new Error(); >> >> RUNTIME ERROR: Exception in thread "main" java.lang.Error >> sout 

Так и вот таким (err обогнало out при выводе в консоль)

public class App < public static void main(String[] args) < System.out.println("sout"); throw new Error(); >> >> sout >> RUNTIME ERROR: Exception in thread "main" java.lang.Error 

Давайте это нарисуем

 буфер сообщений +----------------+ +->| msg2 msg1 msg0 | --> out / +----------------+ \ / +-> +--------+ ВАШЕ ПРИЛОЖЕНИЕ | КОНСОЛЬ| \ +-> +--------+ \ / +------------------------> err нет буфера, сразу печатаем 

когда Вы пишете в System.err — ваше сообщение тут же выводится на консоль, но когда пишете в System.out, то оно может на какое-то время быть буферизированно. Stacktrace необработанного исключение выводится через System.err, что позволяет им обгонять «обычные» сообщения.

3. Компилятор требует вернуть результат (или требует молчать)

Если в объявлении метода сказано, что он возвращает НЕ void, то компилятор зорко следит, что бы мы вернули экземпляр требуемого типа или экземпляр типа, который можно неявно привести к требуемому

public class App < public double sqr(double arg) < // надо double return arg * arg; // double * double - это double >> 
public class App < public double sqr(double arg) < // надо double int k = 1; // есть int return k; // можно неявно преобразовать int в double >> 
// на самом деле, компилятор сгенерирует байт-код для следующих исходников public class App < public double sqr(double arg) < // надо double int k = 1; // есть int return (double) k; // явное преобразование int в double >> 

вот так не пройдет (другой тип)

public class App < public static double sqr(double arg) < return "hello!"; >> >> COMPILATION ERROR: Incompatible types. Required: double. Found: java.lang.String 

Вот так не выйдет — нет возврата

public class App < public static double sqr(double arg) < >> >> COMPILATION ERROR: Missing return statement 

и вот так не пройдет (компилятор не может удостовериться, что возврат будет)

public class App < public static double sqr(double arg) < if (System.currentTimeMillis() % 2 == 0) < return arg * arg; // если currentTimeMillis() - четное число, то все ОК >// а если нечетное, что нам возвращать? > > >> COMPILATION ERROR: Missing return statement 

Компилятор отслеживает, что бы мы что-то вернули, так как иначе непонятно, что должна была бы напечатать данная программа

public class App < public static void main(String[] args) < double d = sqr(10.0); // ну, и чему равно d? System.out.println(d); >public static double sqr(double arg) < // nothing >> >> COMPILATION ERROR: Missing return statement 

Из-забавного, можно ничего не возвращать, а «повесить метод»

public class App < public static double sqr(double arg) < while (true); // Удивительно, но КОМПИЛИРУЕТСЯ! >> 

Тут в d никогда ничего не будет присвоено, так как метод sqr повисает

public class App < public static void main(String[] args) < double d = sqr(10.0); // sqr - навсегда "повиснет", и System.out.println(d); // d - НИКОГДА НИЧЕГО НЕ БУДЕТ ПРИСВОЕНО! >public static double sqr(double arg) < while (true); // Вот тут мы на века "повисли" >> 

Компилятор пропустит «вилку» (таки берем в квадрат ИЛИ висим)

public class App < public static double sqr(double arg) < if (System.currentTimeMillis() % 2 == 0) < return arg * arg; // ну ладно, вот твой double >else < while (true); // а тут "виснем" навсегда >> > 

Но механизм исключений позволяет НИЧЕГО НЕ ВОЗВРАЩАТЬ!

public class App < public static double sqr(double arg) < throw new RuntimeException(); >> 

Итак, у нас есть ТРИ варианта для компилятора

public class App < public static double sqr(double arg) else if (time % 2 == 1) < < while (true); // не, я решил "повиснуть" >else < throw new RuntimeException(); // или бросить исключение >> > 

Но КАКОЙ ЖЕ double вернет функция, бросающая RuntimeException?
А НИКАКОЙ!

public class App < public static void main(String[] args) < // sqr - "сломается" (из него "выскочит" исключение), double d = sqr(10.0); // выполнение метода main() прервется в этой строчке и // d - НИКОГДА НИЧЕГО НЕ БУДЕТ ПРИСВОЕНО! System.out.println(d); // и печатать нам ничего не придется! >public static double sqr(double arg) < throw new RuntimeException(); // "бросаем" исключение >> >> RUNTIME ERROR: Exception in thread "main" java.lang.RuntimeException 

Подытожим: бросаемое исключение — это дополнительный возвращаемый тип. Если ваш метод объявил, что возвращает double, но у вас нет double — можете бросить исключение. Если ваш метод объявил, что ничего не возвращает (void), но у вам таки есть что сказать — можете бросить исключение.

Давайте рассмотрим некоторый пример из практики.

Задача: реализовать функцию, вычисляющую площадь прямоугольника

public static int area(int width, int height)

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

public static int area(int width, int height) < return width * height; // тут просто перемножаем >

Вопрос: что делать, если мы обнаружили, что хотя бы один из аргументов — отрицательное число?
Если просто умножить, то мы пропустили ошибочные данные дальше. Что еще хуже, возможно, мы «исправили ситуацию» — сказали что площадь прямоугольника с двумя отрицательными сторонами -10 и -20 = 200.

Мы не можем ничего не вернуть

public static int area(int width, int height) < if (width < 0 || height < 0) < // у вас плохие аргументы, извините >else < return width * height; >> >> COMPILATION ERROR: Missing return statement 

Можно, конечно, отписаться в консоль, но кто ее будет читать и как определить где была поломка. При чем, вычисление то продолжится с неправильными данными

public static int area(int width, int height) < if (width < 0 || height < 0) < System.out.println("Bad . "); >return width * height; > 

Можно вернуть специальное значение, показывающее, что что-то не так (error code), но кто гарантирует, что его прочитают, а не просто воспользуются им?

public static int area(int width, int height) < if (width < 0 || height < 0) < return -1; // специальное "неправильное" значение площади >return width * height; > 

Можем, конечно, целиком остановить виртуальную машину

public static int area(int width, int height) < if (width < 0 || height < 0) < System.exit(0); >return width * height; > 

Но «правильный путь» таков: если обнаружили возможное некорректное поведение, то
1. Вычисления остановить, сгенерировать сообщение-поломку, которое трудно игнорировать, предоставить пользователю информацию о причине, предоставить пользователю возможность все починить (загрузить белье назад и повторно нажать кнопку старт)

public static int area(int width, int height) 
4. Нелокальная передача управления (nonlocal control transfer)

Механизм исключительных ситуация (исключений) — это механизм НЕЛОКАЛЬНОЙ ПЕРЕДАЧИ УПРАВЛЕНИЯ.
Что под этим имеется в виду?
Программа, в ходе своего выполнения (точнее исполнения инструкций в рамках отдельного потока), оперирует стеком («стопкой») фреймов. Передача управления осуществляется либо в рамках одного фрейма
public class App < public static void main(String[] args) < // Пример: ОПЕРАТОР ПОСЛЕДОВАТЕЛЬНОСТИ int x = 42; // первый шаг int y = x * x; // второй шаг x = x * y; // третий шаг . >> 
public class App < public static void main(String[] args) < // Пример: ОПЕРАТОР ВЕТВЛЕНИЯ if (args.length >2) < первый шаг // второй шаг или тут . >else < // или тут . >// третий шаг . > > 
public class App < public static void main(String[] args) < // Пример: ОПЕРАТОР ЦИКЛА do..while int x = 1; do < . >while (x++ < 10); . >> 

и другие операторы.

  • вызов метода: создаем новый фрейм, помещаем его на верхушку стека и переходим в него
  • выход из метода: возвращаемся к предыдущему фрейму (через return или просто кончились инструкции в методе)

return — выходим из ОДНОГО фрейма (из фрейма #4(метод h()))

public class App < public static void main(String[] args) < System.err.println("#1.in"); f(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println("#1.out"); // вернулись >// выходим из текущего фрейма, кончились инструкции public static void f() < System.err.println(". #2.in"); g(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". #2.out"); //вернулись >// выходим из текущего фрейма, кончились инструкции public static void g() < System.err.println(". . #3.in"); h(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". . #3.out"); // вернулись >// выходим из текущего фрейма, кончились инструкции public static void h() < System.err.println(". . . #4.in"); if (true) < System.err.println(". . . #4.RETURN"); return; // выходим из текущего фрейма по 'return' >System.err.println(". . . #4.out"); // ПРОПУСКАЕМ > > >> #1.in >> . #2.in >> . . #3.in >> . . . #4.in >> . . . #4.RETURN >> . . #3.out >> . #2.out >> #1.out 

throw — выходим из ВСЕХ фреймов

public class App < public static void main(String[] args) < System.err.println("#1.in"); f(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println("#1.out"); // ПРОПУСТИЛИ! >public static void f() < System.err.println(". #2.in"); g(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". #2.out"); // ПРОПУСТИЛИ! >public static void g() < System.err.println(". . #3.in"); h(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". . #3.out"); // ПРОПУСТИЛИ! >public static void h() < System.err.println(". . . #4.in"); if (true) < System.err.println(". . . #4.THROW"); throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw' >System.err.println(". . . #4.out"); // ПРОПУСТИЛИ! > > >> #1.in >> . #2.in >> . . #3.in >> . . . #4.in >> . . . #4.THROW >> RUNTIME ERROR: Exception in thread "main" java.lang.Error 

При помощи catch мы можем остановить летящее исключение (причина, по которой мы автоматически покидаем фреймы).
Останавливаем через 3 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g()) + фрейм #2(метод f())

public class App < public static void main(String[] args) < System.err.println("#1.in"); try < f(); // создаем фрейм, помещаем в стек, передаем в него управление >catch (Error e) < // "перехватили" "летящее" исключение System.err.println("#1.CATCH"); // и работаем >System.err.println("#1.out"); // работаем дальше > public static void f() < System.err.println(". #2.in"); g(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". #2.out"); // ПРОПУСТИЛИ! >public static void g() < System.err.println(". . #3.in"); h(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". . #3.out"); // ПРОПУСТИЛИ! >public static void h() < System.err.println(". . . #4.in"); if (true) < System.err.println(". . . #4.THROW"); throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw' >System.err.println(". . . #4.out"); // ПРОПУСТИЛИ! > > >> #1.in >> . #2.in >> . . #3.in >> . . . #4.in >> . . . #4.THROW >> #1.CATCH >> #1.out 

Обратите внимание, стандартный сценарий работы был восстановлен в методе main() (фрейм #1)

Останавливаем через 2 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g())

public class App < public static void main(String[] args) < System.err.println("#1.in"); f(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println("#1.out"); // вернулись и работаем >public static void f() < System.err.println(". #2.in"); try < g(); // создаем фрейм, помещаем в стек, передаем в него управление >catch (Error e) < // "перехватили" "летящее" исключение System.err.println(". #2.CATCH"); // и работаем >System.err.println(". #2.out"); // работаем дальше > public static void g() < System.err.println(". . #3.in"); h(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". . #3.out"); // ПРОПУСТИЛИ! >public static void h() < System.err.println(". . . #4.in"); if (true) < System.err.println(". . . #4.THROW"); throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw' >System.err.println(". . . #4.out"); // ПРОПУСТИЛИ! > > >> #1.in >> . #2.in >> . . #3.in >> . . . #4.in >> . . . #4.THROW >> . #2.CATCH >> . #2.out >> #1.out 

Останавливаем через 1 фрейм (фактически аналог return, просто покинули фрейм «другим образом»)

public class App < public static void main(String[] args) < System.err.println("#1.in"); f(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println("#1.out"); // вернулись и работаем >public static void f() < System.err.println(". #2.in"); g(); // создаем фрейм, помещаем в стек, передаем в него управление System.err.println(". #2.out"); // вернулись и работаем >public static void g() < System.err.println(". . #3.in"); try < h(); // создаем фрейм, помещаем в стек, передаем в него управление >catch (Error e) < // "перехватили" "летящее" исключение System.err.println(". . #3.CATCH"); // и работаем >System.err.println(". . #3.out"); // работаем дальше > public static void h() < System.err.println(". . . #4.in"); if (true) < System.err.println(". . . #4.THROW"); throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw' >System.err.println(". . . #4.out"); // ПРОПУСТИЛИ! > > >> #1.in >> . #2.in >> . . #3.in >> . . . #4.in >> . . . #4.THROW >> . . #3.CATCH >> . . #3.out >> . #2.out >> #1.out 

Итак, давайте сведем все на одну картинку

// ---Используем RETURN--- // ---Используем THROW--- // Выход из 1-го фрейма // Выход из ВСЕХ (из 4) фреймов #1.in #1.in . #2.in . #2.in . . #3.in . . #3.in . . . #4.in . . . #4.in . . . #4.RETURN . . . #4.THROW . . #3.out RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error . #2.out #1.out // ---Используем THROW+CATCH--- // Выход из 3-х фреймов // Выход из 2-х фреймов // Выход из 1-го фрейма #1.in #1.in #1.in . #2.in . #2.in . #2.in . . #3.in . . #3.in . . #3.in . . . #4.in . . . #4.in . . . #4.in . . . #4.THROW . . . #4.THROW . . . #4.THROW #1.CATCH . #2.CATCH . . #3.CATCH #1.out . #2.out . . #3.out #1.out . #2.out #1.out 
5. try + catch (catch — полиморфен)

Напомним иерархию исключений

 Object | Throwable / \ Error Exception | RuntimeException 

То, что исключения являются объектами важно для нас в двух моментах
1. Они образуют иерархию с корнем java.lang.Throwable (java.lang.Object — предок java.lang.Throwable, но Object — уже не исключение)
2. Они могут иметь поля и методы (в этой статье это не будем использовать)

По первому пункту: catch — полиморфная конструкция, т.е. catch по типу Parent перехватывает летящие экземпляры любого типа, который является Parent-ом (т.е. экземпляры непосредственно Parent-а или любого потомка Parent-а)

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (Exception e) < // catch по Exception ПЕРЕХВАТЫВАЕТ RuntimeException System.err.print(" 2"); >System.err.println(" 3"); > > >> 0 2 3 

Даже так: в блоке catch мы будем иметь ссылку типа Exception на объект типа RuntimeException

public class App < public static void main(String[] args) < try < throw new RuntimeException(); >catch (Exception e) < if (e instanceof RuntimeException) < RuntimeException re = (RuntimeException) e; System.err.print("Это RuntimeException на самом деле. "); >else < System.err.print("В каком смысле не RuntimeException. "); >> > > >> Это RuntimeException на самом деле. 

catch по потомку не может поймать предка

public class App < public static void main(String[] args) throws Exception < // пока игнорируйте 'throws' try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (RuntimeException e) < System.err.print(" 2"); >System.err.print(" 3"); > > >> 0 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Exception 

catch по одному «брату» не может поймать другого «брата» (Error и Exception не находятся в отношении предок-потомок, они из параллельных веток наследования от Throwable)

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (Exception e) < System.err.print(" 2"); >System.err.print(" 3"); > > >> 0 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error 

По предыдущим примерам — надеюсь вы обратили внимание, что если исключение перехвачено, то JVM выполняет операторы идущие ПОСЛЕ последних скобок try+catch.
Но если не перехвачено, то мы
1. не заходим в блок catch
2. покидаем фрейм метода с летящим исключением

А что будет, если мы зашли в catch, и потом бросили исключение ИЗ catch?

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (RuntimeException e) < // перехватили RuntimeException System.err.print(" 2"); if (true) // но бросили Error > System.err.println(" 3"); // пропускаем - уже летит Error > > >> 0 2 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error 

В таком случае выполнение метода тоже прерывается (не печатаем «3»). Новое исключение не имеет никакого отношения к try-catch

Мы можем даже кинуть тот объект, что у нас есть «на руках»

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (RuntimeException e) < // перехватили RuntimeException System.err.print(" 2"); if (true) // и бросили ВТОРОЙ раз ЕГО ЖЕ > System.err.println(" 3"); // пропускаем - опять летит RuntimeException > > >> 0 2 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.RuntimeException 

И мы не попадем в другие секции catch, если они есть

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (RuntimeException e) < // перехватили RuntimeException System.err.print(" 2"); if (true) // и бросили новый Error > catch (Error e) < // хотя есть cath по Error "ниже", но мы в него не попадаем System.err.print(" 3"); >System.err.println(" 4"); > > >> 0 2 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error 

Обратите внимание, мы не напечатали «3», хотя у нас летит Error а «ниже» расположен catch по Error. Но важный момент в том, что catch имеет отношение исключительно к try-секции, но не к другим catch-секциям.

Как покажем ниже — можно строить вложенные конструкции, но вот пример, «исправляющий» эту ситуацию

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch (RuntimeException e) < // перехватили RuntimeException System.err.print(" 2.1"); try < System.err.print(" 2.2"); if (true) // и бросили новый Error System.err.print(" 2.3"); > catch (Throwable t) < // перехватили Error System.err.print(" 2.4"); >System.err.print(" 2.5"); > catch (Error e) < // хотя есть cath по Error "ниже", но мы в него не попадаем System.err.print(" 3"); >System.err.println(" 4"); > > >> 0 2.1 2.2 2.4 2.5 4 
6. try + catch + catch + .

Как вы видели, мы можем расположить несколько catch после одного try.

Но есть такое правило — нельзя ставить потомка после предка! (RuntimeException после Exception)

public class App < public static void main(String[] args) < try < >catch (Exception e) < >catch (RuntimeException e) < >> > >> COMPILATION ERROR: Exception 'java.lang.RuntimeException' has alredy been caught 

Ставить брата после брата — можно (RuntimeException после Error)

public class App < public static void main(String[] args) < try < >catch (Error e) < >catch (RuntimeException e) < >> > 

Как происходит выбор «правильного» catch? Да очень просто — JVM идет сверху-вниз до тех пор, пока не найдет такой catch что в нем указано ваше исключение или его предок — туда и заходит. Ниже — не идет.

public class App < public static void main(String[] args) < try < throw new Exception(); >catch (RuntimeException e) < System.err.println("catch RuntimeException"); >catch (Exception e) < System.err.println("catch Exception"); >catch (Throwable e) < System.err.println("catch Throwable"); >System.err.println("next statement"); > > >> catch Exception >> next statement 

Выбор catch осуществляется в runtime (а не в compile-time), значит учитывается не тип ССЫЛКИ (Throwable), а тип ССЫЛАЕМОГО (Exception)

public class App < public static void main(String[] args) < try < Throwable t = new Exception(); // ссылка типа Throwable указывает на объект типа Exception throw t; >catch (RuntimeException e) < System.err.println("catch RuntimeException"); >catch (Exception e) < System.err.println("catch Exception"); >catch (Throwable e) < System.err.println("catch Throwable"); >System.err.println("next statement"); > > >> catch Exception >> next statement 
7. try + finally

finally-секция получает управление, если try-блок завершился успешно

public class App < public static void main(String[] args) < try < System.err.println("try"); >finally < System.err.println("finally"); >> > >> try >> finally 

finally-секция получает управление, даже если try-блок завершился исключением

public class App < public static void main(String[] args) < try < throw new RuntimeException(); >finally < System.err.println("finally"); >> > >> finally >> Exception in thread "main" java.lang.RuntimeException 

finally-секция получает управление, даже если try-блок завершился директивой выхода из метода

public class App < public static void main(String[] args) < try < return; >finally < System.err.println("finally"); >> > >> finally 

finally-секция НЕ вызывается только если мы «прибили» JVM

public class App < public static void main(String[] args) < try < System.exit(42); >finally < System.err.println("finally"); >> > >> Process finished with exit code 42 

System.exit(42) и Runtime.getRuntime().exit(42) — это синонимы

public class App < public static void main(String[] args) < try < Runtime.getRuntime().exit(42); >finally < System.err.println("finally"); >> > >> Process finished with exit code 42 

И при Runtime.getRuntime().halt(42) — тоже не успевает зайти в finally

public class App < public static void main(String[] args) < try < Runtime.getRuntime().halt(42); >finally < System.err.println("finally"); >> > >> Process finished with exit code 42 

exit() vs halt()
javadoc: java.lang.Runtime#halt(int status)
… Unlike the exit method, this method does not cause shutdown hooks to be started and does not run uninvoked finalizers if finalization-on-exit has been enabled. If the shutdown sequence has already been initiated then this method does not wait for any running shutdown hooks or finalizers to finish their work.

Однако finally-секция не может «починить» try-блок завершившийся исключение (заметьте, «more» — не выводится в консоль)

public class App < public static void main(String[] args) < try < System.err.println("try"); if (true) > finally < System.err.println("finally"); >System.err.println("more"); > > >> try >> finally >> Exception in thread "main" java.lang.RuntimeException 

Трюк с «if (true) <. >» требуется, так как иначе компилятор обнаруживает недостижимый код (последняя строка) и отказывается его компилировать

public class App < public static void main(String[] args) < try < System.err.println("try"); throw new RuntimeException(); >finally < System.err.println("finally"); >System.err.println("more"); > > >> COMPILER ERROR: Unrechable statement 

И finally-секция не может «предотвратить» выход из метода, если try-блок вызвал return («more» — не выводится в консоль)

public class App < public static void main(String[] args) < try < System.err.println("try"); if (true) > finally < System.err.println("finally"); >System.err.println("more"); > > >> try >> finally 

Однако finally-секция может «перебить» throw/return при помощи другого throw/return

public class App < public static void main(String[] args) < System.err.println(f()); >public static int f() < try < return 0; >finally < return 1; >> > >> 1 
public class App < public static void main(String[] args) < System.err.println(f()); >public static int f() < try < throw new RuntimeException(); >finally < return 1; >> > >> 1 
public class App < public static void main(String[] args) < System.err.println(f()); >public static int f() < try < return 0; >finally < throw new RuntimeException(); >> > >> Exception in thread "main" java.lang.RuntimeException 
public class App < public static void main(String[] args) < System.err.println(f()); >public static int f() < try < throw new Error(); >finally < throw new RuntimeException(); >> > >> Exception in thread "main" java.lang.RuntimeException 

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

// open some resource try < // use resource >finally < // close resource >

Например для освобождения захваченной блокировки

Lock lock = new ReentrantLock(); . lock.lock(); try < // some code >finally

Или для закрытия открытого файлового потока

InputStream input = new FileInputStream(". "); try < // some code >finally

Специально для этих целей в Java 7 появилась конструкция try-with-resources, ее мы изучим позже.

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

public class App < public static void main(String[] args) < System.err.println(f()); >public static int f() < long rnd = System.currenttimeMillis(); boolean finished = false; try < if (rnd % 3 == 0) < throw new Error(); >else if (rnd % 3 == 1) < throw new RuntimeException(); >else < // nothing >finished = true; > finally < if (finished) < // не было исключений >else < // было исключение, но какое? >> > > 

Не рекомендуемые практики
— return из finally-секции (можем затереть исключение из try-блока)
— действия в finally-секции, которые могут бросить исключение (можем затереть исключение из try-блока)

8. try + catch + finally
public class App < public static void main(String[] args) < try < System.err.print(" 0"); // nothing System.err.print(" 1"); >catch(Error e) < System.err.print(" 2"); >finally < System.err.print(" 3"); >System.err.print(" 4"); > > >> 0 1 3 4 

Не заходим в catch, заходим в finally, продолжаем после оператора

Есть исключение и есть подходящий catch

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch(Error e) < System.err.print(" 2"); >finally < System.err.print(" 3"); >System.err.print(" 4"); > > >> 0 2 3 4 

Заходим в catch, заходим в finally, продолжаем после оператора

Есть исключение но нет подходящего catch

public class App < public static void main(String[] args) < try < System.err.print(" 0"); if (true) System.err.print(" 1"); > catch(Error e) < System.err.print(" 2"); >finally < System.err.print(" 3"); >System.err.print(" 4"); > > >> 0 3 >> RUNTIME ERROR: Exception in thread "main" java.lang.RuntimeException 

Не заходим в catch, заходим в finally, не продолжаем после оператора — вылетаем с неперехваченным исключением

9. Вложенные try + catch + finally

Операторы обычно допускают неограниченное вложение.
Пример с if

public class App < public static void main(String[] args) < if (args.length >1) < if (args.length >2) < if (args.length >3) < . >> > > > 
public class App < public static void main(String[] args) < for (int i = 0; i < 10; i++) < for (int j = 0; j < 10; i++) < for (int k = 0; k < 10; k++) < . >> > > > 

Суть в том, что try-cacth-finally тоже допускает неограниченное вложение.
Например вот так

public class App < public static void main(String[] args) < try < try < try < . >catch (Exception e) < >finally <> > catch (Exception e) < >finally <> > catch (Exception e) < >finally <> > > 

Или даже вот так

public class App < public static void main(String[] args) < try < try < . >catch (Exception e) < . >finally < . >> catch (Exception e) < try < . >catch (Exception e) < . >finally < . >> finally < try < . >catch (Exception e) < . >finally < . >> > > 

Ну что же, давайте исследуем как это работает.

Вложенный try-catch-finally без исключения

public class App < public static void main(String[] args) < try < System.err.print(" 0"); try < System.err.print(" 1"); // НИЧЕГО System.err.print(" 2"); >catch (RuntimeException e) < System.err.print(" 3"); // НЕ заходим - нет исключения >finally < System.err.print(" 4"); // заходим всегда >System.err.print(" 5"); // заходим - выполнение в норме > catch (Exception e) < System.err.print(" 6"); // НЕ заходим - нет исключения >finally < System.err.print(" 7"); // заходим всегда >System.err.print(" 8"); // заходим - выполнение в норме > > >> 0 1 2 4 5 7 8 

Мы НЕ заходим в обе catch-секции (нет исключения), заходим в обе finally-секции и выполняем обе строки ПОСЛЕ finally.

Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНУТРЕННИЙ catch

public class App < public static void main(String[] args) < try < System.err.print(" 0"); try < System.err.print(" 1"); if (true) System.err.print(" 2"); > catch (RuntimeException e) < System.err.print(" 3"); // ЗАХОДИМ - есть исключение >finally < System.err.print(" 4"); // заходим всегда >System.err.print(" 5"); // заходим - выполнение УЖЕ в норме > catch (Exception e) < System.err.print(" 6"); // не заходим - нет исключения, УЖЕ перехвачено >finally < System.err.print(" 7"); // заходим всегда >System.err.print(" 8"); // заходим - выполнение УЖЕ в норме > > >> 0 1 3 4 5 7 8 

Мы заходим в ПЕРВУЮ catch-секцию (печатаем «3»), но НЕ заходим во ВТОРУЮ catch-секцию (НЕ печатаем «6», так как исключение УЖЕ перехвачено первым catch), заходим в обе finally-секции (печатаем «4» и «7»), в обоих случаях выполняем код после finally (печатаем «5»и «8», так как исключение остановлено еще первым catch).

Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНЕШНИЙ catch

public class App < public static void main(String[] args) < try < System.err.print(" 0"); try < System.err.print(" 1"); if (true) System.err.print(" 2"); > catch (RuntimeException e) < System.err.print(" 3"); // НЕ заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА >finally < System.err.print(" 4"); // заходим всегда >System.err.print(" 5"); // не заходим - выполнение НЕ в норме > catch (Exception e) < System.err.print(" 6"); // ЗАХОДИМ - есть подходящее исключение >finally < System.err.print(" 7"); // заходим всегда >System.err.print(" 8"); // заходим - выполнение УЖЕ в норме > > >> 0 1 4 6 7 8 

Мы НЕ заходим в ПЕРВУЮ catch-секцию (не печатаем «3»), но заходим в ВТОРУЮ catch-секцию (печатаем «6»), заходим в обе finally-секции (печатаем «4» и «7»), в ПЕРВОМ случае НЕ выполняем код ПОСЛЕ finally (не печатаем «5», так как исключение НЕ остановлено), во ВТОРОМ случае выполняем код после finally (печатаем «8», так как исключение остановлено).

Вложенный try-catch-finally с исключением, которое НИКТО НЕ ПЕРЕХВАТИТ

public class App < public static void main(String[] args) < try < System.err.print(" 0"); try < System.err.print(" 1"); if (true) System.err.print(" 2"); > catch (RuntimeException e) < System.err.print(" 3"); // НЕ заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА >finally < System.err.print(" 4"); // заходим всегда >System.err.print(" 5"); // НЕ заходим - выполнение НЕ в норме > catch (Exception e) < System.err.print(" 6"); // не заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА >finally < System.err.print(" 7"); // заходим всегда >System.err.print(" 8"); // не заходим - выполнение НЕ в норме > > >> 0 1 4 7 >> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error 

Мы НЕ заходим в ОБЕ catch-секции (не печатаем «3» и «6»), заходим в обе finally-секции (печатаем «4» и «7») и в обоих случаях НЕ выполняем код ПОСЛЕ finally (не печатаем «5» и «8», так как исключение НЕ остановлено), выполнение метода прерывается по исключению.

Контакты

Я занимаюсь онлайн обучением Java (вот курсы программирования) и публикую часть учебных материалов в рамках переработки курса Java Core. Видеозаписи лекций в аудитории Вы можете увидеть на youtube-канале, возможно, видео канала лучше систематизировано в этой статье.

  1. показываю различные варианты применения
  2. строю усложняющуюся последовательность примеров по каждому варианту
  3. объясняю логику двигавшую авторами (по мере возможности)
  4. даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
  5. даю лабораторные для самостоятельной работы

skype: GolovachCourses
email: GolovachCourses@gmail.com

  • java
  • exception
  • исключения
  • курсы программирования

Как игнорировать исключения в java

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

Иван Уровень 23
29 мая 2023
Отличная статья! Большое Вам спасибо за разъяснение!
Tural Уровень 13
16 мая 2023
Отлавливают ли Error ? в OCS пишется что нельзя их отлавливать
Dok3R73 Уровень 42
26 марта 2023
Спасибо за труды, очень доступно написано��
Art Sch Уровень 13
11 января 2023

я читаю про исключения на 1м и в принципе понимаю, но не очень. ps: зачем только я начал с java core. pss: если вы это читаете, и я до сих пор на первом, то либо я прохожу другой курс, либо читаю книгу по джаве, параллельно проходя этот курс, либо решил взять перерыв на неопределенный срок времени. никогда не сдамся)

���������������������������������� Уровень 23
6 ноября 2021

Есть подозрение, что так будет правильнее.

Mark Vladimirovich Уровень 16
25 марта 2021

обращу внимание на некоторую неточность. цитата "Создание исключения При исполнении программы исключение генерируется JVM или вручную, с помощью оператора throw" в java исключения это тоже объекты поэтому создается исключение так же как объект new Exception. а бросается в программе с помощью оператора throw. обычно эти операции объединяют в одну throw new Exception("aaa");

19 сентября 2020

если что я пишу это с 3 уровня. Под конец лекций я читал статью про бафридер, после нашел там ссылку на потоки вводов, а потом чтобы понять что там говориться ввел гугл про исключение и нашел эту статью, спасибо автору, это статья очень помогла. PS если ты читаешь этот комментарий и видишь что у меня нет прогресса(то есть если я все еще на 3 уровне или чуточку больше), то скажи мне, что я нуб и не дошел до 40 лвла

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

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