Java виды ошибок

Исключение (exception) — это ненормальная ситуация (термин «исключение» здесь следует понимать как «исключительная ситуация»), возникающая во время выполнения программного кода. Иными словами, исключение — это ошибка, возникающая во время выполнения программы (в runtime).

Исключение — это способ системы Java (в частности, JVM — виртуальной машины Java) сообщить вашей программе, что в коде произошла ошибка. К примеру, это может быть деление на ноль, попытка обратиться к массиву по несуществующему индексу, очень распространенная ошибка нулевого указателя (NullPointerException) — когда вы обращаетесь к ссылочной переменной, у которой значение равно null и так далее.

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

Обработка исключений (exception handling) — название объектно-ориентированной техники, которая пытается разрешить эти ошибки.

Программа в Java может сгенерировать различные исключения, например:

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

  • программа может попытаться записать файл на диск, но диск заполнен или не отформатирован;

  • программа может попросить пользователя ввести данные, но пользователь ввел данные неверного типа;

  • программа может попытаться осуществить деление на ноль;

  • программа может попытаться обратиться к массиву по несуществующему индексу.

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

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

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

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

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

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


Так как в Java
ВСЁ ЯВЛЯЕТСЯ ОБЪЕКТОМ, то исключение тоже является объектом некоторого класса, который описывает исключительную ситуацию, возникающую в определенной части программного кода.

«Обработка исключений» работает следующим образом:

  • когда возникает исключительная ситуация, JVM генерирует (говорят, что JVM ВЫБРАСЫВАЕТ исключение, для описания этого процесса используется ключевое слово throw) объект исключения и передает его в метод, в котором произошло исключение;

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

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

Если вы не предусмотрите обработчик исключений, то исключение будет перехвачено стандартным обработчиком Java. Стандартный обработчик прекратит выполнение программы и выведет сообщение об ошибке.

Рассмотрим пример исключения и реакцию стандартного обработчика Java.

public static void main(String[] args) {

System.out.println(5 / 0);

Мы видим, что стандартный обработчик вывел в консоль сообщение об ошибке. Давайте разберемся с содержимым этого сообщения:

«C:\Program Files\Java\jdk1.8.0_60\bin\java»

Exception in thread «main» java.lang.ArithmeticException: / by zero

at ua.opu.Main.main(Main.java:6)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Process finished with exit code 1

Exception in thread «main» java.lang.ArithmeticException: / by zero

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

at ua.opu.Main.main(Main.java:6)

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

at ua.opu.Main.main(Main.java:6)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

называются «трассировкой стека» (stack tracing). О каком стеке идет речь? Речь идет о стеке вызовов (call stack). Соответственно, эти строки означают последовательность вызванных методов, начиная от метода, в котором произошло исключение, заканчивая самым первым вызванным методом.

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

Вся эта информация хранится в специальной структуре – стеке вызовов. Каждая запись в стеке вызовов называется кадром или фреймом (stack frame).

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

Как уже было сказано выше, исключение это объект некоторого класса. В Java существует разветвленная иерархия классов исключений.

В Java, класс исключения служит для описания типа исключения. Например, класс NullPointerException описывает исключение нулевого указателя, а FileNotFoundException означает исключение, когда файл, с которым пытается работать приложение, не найден. Рассмотрим иерархию классов исключений:

На самом верхнем уровне расположен класс Throwable, который является базовым для всех исключений (как мы помним, JVM «выбрасывает» исключение», поэтому класс Throwable означает – то, что может «выбросить» JVM).

От класса Throwable наследуются классы Error и Exception. Среди подклассов Exception отдельно выделен класс RuntimeException, который играет важную роль в иерархии исключений.

В Java существует некоторая неопределенность насчет того – существует ли два или три вида исключений.

Если делить исключения на два вида, то это:

  1. 1.

    контролируемые исключения (checked exceptions) – подклассы класса Exception, кроме подкласса RuntimeException и его производных;

  2. 2.

    неконтролируемые исключения (unchecked exceptions) – класс Error с подклассами, а также класс RuntimeException и его производные;

В некоторых источниках класс Error и его подклассы выделяют в отдельный вид исключений — ошибки (errors).

Далее мы видим класс Error. Классы этой ветки составляют вид исключений, который можно обозначить как «ошибки» (errors). Ошибки представляют собой серьезные проблемы, которые не следует пытаться обработать в собственной программе, поскольку они связаны с проблемами уровня JVM.

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

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

В качестве примеров «ошибок» можно привести: переполнение стека вызова (класс StackOverflowError); нехватка памяти в куче (класс OutOfMemoryError), вследствие чего JVM не может выделить память под новый объект и сборщик мусора не помогает; ошибка виртуальной машины, вследствие которой она не может работать дальше (класс VirtualMachineError) и так далее.

Несмотря на то, что в нашей программе мы никак не можем помочь этой проблеме, и приложение не может работать дальше (ну как может работать приложение, если стек вызовов переполнен или JVM не может дальше выполнять код?!); знание природы этих ошибок поможет вам предпринять некоторые действия, чтобы избежать этих ошибок в дальнейшем. Например, ошибки типа StackOverflowError и OutOfMemoryError могут быть следствием вашего некорректного кода.

Например, попробуем спровоцировать ошибку StackOverflowError

public static void main(String[] args) {

public static void methodA() {

private static void methodB() {

Получим такое сообщение об ошибке

Exception in thread «main» java.lang.StackOverflowError

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

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

Exception in thread «main» java.lang.OutOfMemoryError: Java heap space

at java.base/java.util.Arrays.copyOf(Arrays.java:3511)

at java.base/java.util.Arrays.copyOf(Arrays.java:3480)

at java.base/java.util.ArrayList.grow(ArrayList.java:237)

at java.base/java.util.ArrayList.grow(ArrayList.java:244)

at java.base/java.util.ArrayList.add(ArrayList.java:454)

at java.base/java.util.ArrayList.add(ArrayList.java:467)

at com.company.Main.main(Main.java:13)

Process finished with exit code 1

Ошибка VirtualMachineError может означать, что следует переустановить библиотеки Java.

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

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

В классе Exception описаны исключения двух видов: контролируемые исключения (checked exceptions) и неконтролируемые исключения (unchecked exceptions).

Неконтролируемые исключения содержатся в подклассе RuntimeException и его наследниках. Контролируемые исключения содержатся в остальных подклассах Exception.

В чем разница между контролируемыми и неконтролируемыми исключениями, мы узнаем позже, а теперь рассмотрим вопрос – а как же именно нам обрабатывать исключения?

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

  1. 1.

    с помощью связки try-catch;

  2. 2.

    с помощью ключевого слова throws в сигнатуре метода.

Рассмотрим оба метода поподробнее:

Способ 1. Связка try-catch

Этот способ кратко можно описать следующим образом.

Код, который теоретически может вызвать исключение, записывается в блоке try{}. Сразу за блоком try идет блок код catch{}, в котором содержится код, который будет выполнен в случае генерации исключения. В блоке finally{} содержится код, который будет выполнен в любом случае – произошло ли исключение или нет.

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

public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);

System.out.println(«Введите первое число: «);

String firstNumber = scanner.nextLine();

System.out.println(«Введите второе число: «);

String secondNumber = scanner.nextLine();

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

System.out.println(«Результат: « + (a + b));

Первое, что нам нужно определить – и что является главным при работе с исключениями, КАКАЯ ИНСТРУКЦИЯ МОЖЕТ ПРИВЕСТИ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ?

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

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

в которых происходит преобразование ввода пользователя в целое число (метод parseInt() преобразует цифры в строке в число).

Почему здесь может возникнуть исключение? Потому что пользователь может ввести не число, а просто какой-то текст и тогда непонятно – что записывать в переменную a или b. И да, действительно, если пользователь введет некорректное значение, возникнет исключение в методе Integer.parseInt().

Итак, что мы можем сделать. «Опасный код» нужно поместить в блок try{}

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

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

System.out.println(«Результат: « + (a + b));

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

Когда произойдет исключение, то система Java прервет выполнение инструкций в блоке try и передаст управление блоку catch и запишет в этот аргумент объект исключения, который сгенерировала Java-машина.

То есть, как только в блоке try возникнет исключение, то дальше инструкции в блоке try выполняться не будут! А сразу же начнут выполняться действия в блоке catch.

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

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

Например, если в нашем примере мы напишем код, который потенциально может выбросить исключение типа IOException, но не изменим блок catch

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

тогда обработчик не будет вызван и исключение будет обработано стандартным обработчиком Java.

Способ 2. Использование ключевого слова throws

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

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

public static void main(String[] args) {

int a = getNumberFromConsole(«Введите первое число»);

int b = getNumberFromConsole(«Введите второе число»);

System.out.println(«Результат: « + (a + b));

public static int getNumberFromConsole(String message) {

Scanner scanner = new Scanner(System.in);

System.out.print(message + «: «);

String s = scanner.nextLine();

return Integer.parseInt(s);

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

  1. 1.

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

  2. 2.

    это не входит в нашу компетенцию как программиста – обработкой исключений занимается другой программист;

  3. 3.

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

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

public static int getNumberFromConsole(String message) throws NumberFormatException {

Scanner scanner = new Scanner(System.in);

System.out.print(message + «: «);

String s = scanner.nextLine();

return Integer.parseInt(s);

Обратите внимание на расположение сигнатуру метода. Мы привыкли, что при объявлении метода сразу после скобок входных аргументов мы открываем фигурную скобку и записываем тело метода. Здесь же, после входных аргументов, мы пишем ключевое слово throws и потом указываем тип исключения, которое может быть сгенерировано в нашем методе. Если метод может выбрасывать несколько типов исключений, они записываются через запятую

public static void foo() throws NumberFormatException, ArithmeticException, IOException {

Тогда, в методе main мы должны написать примерно следующее

public static void main(String[] args) {

a = getNumberFromConsole(«Введите первое число»);

b = getNumberFromConsole(«Введите второе число»);

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

System.out.println(«Результат: « + (a + b));

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

Отличия между контролируемыми и неконтролируемыми исключениями

Если вы вызываете метод, который выбрасывает checked исключение, то вы ОБЯЗАНЫ предусмотреть обработку возможного исключения, то есть связку try-catch.

Яркий пример checked исключения – класс IOException и его подклассы.

Рассмотрим пример – попробуем прочитать файл и построчно вывести его содержимое на экран консоли:

public static void main(String[] args) {

Path p = Paths.get(«c:\\temp\\file.txt»);

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Как мы видим, компилятор не хочет компилировать наш код. Чем же он недоволен? У нас в коде происходит вызов двух методов – статического метода Files.newBufferedReader() и обычного метода BufferedReader.readLine().

Если посмотреть на сигнатуры этих методов то можно увидеть, что оба этих метода выбрасывают исключения типа IOException. Этот тип исключения относится к checked-исключению и поэтому, если вы вызываете эти методы, компилятор ТРЕБУЕТ от вас предусмотреть блок catch, либо в самом вашем методе указать throws IOException и, таким образом, передать обязанность обрабатывать исключение другому методу, который будет вызывать ваш.

Таким образом, «оборачиваем» наш код в блок try и пишем блок catch.

public static void main(String[] args) {

Path p = Paths.get(«c:\\temp\\file.txt»);

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

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

public static void main(String[] args) {

Path p = Paths.get(«c:\\temp\\file.txt»);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

public static void printFile(Path p) throws IOException {

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Eсли метод выбрасывает checked-исключение, то проверка на наличие catch-блока происходит на этапе компиляции. И вы обязаны предусмотреть обработку исключения для checked-исключения.

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

Зачем необходимо наличие двух видов исключений?

В большинстве языков существует всего лишь один тип исключений – unchecked. Некоторые языки, например, C#, в свое время отказались от checked-исключений.

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

С другой стороны, зачем нужно делать некоторые типы исключений checked? Почему просто не сделать все исключения unchecked и оставить решения об обработке исключений целиком на совести программиста?

В официальной документации написано, что unchecked-исключения – это те исключения, от которых программа «не может восстановиться», тогда как checked-исключения позволяют откатить некоторую операцию и повторить ее снова.

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

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

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

Дополнительно об исключениях

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

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

public static void foo() {

} catch (ArithmeticException e) {

// обработка арифметического исключения

} catch (IndexOutOfBoundsException e) {

// обработка выхода за пределы коллекции

} catch (IllegalArgumentException e) {

// обработка некорректного аргумента

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

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

public static void main(String[] args) {

Path p = Paths.get(«c:\\temp\\file.txt»);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

} catch (FileNotFoundException e) {

// данный блок никогда не будет вызван

public static void printFile(Path p) throws IOException {

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Один блок для обработки нескольких типов исключений

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

public static void foo() {

} catch (ArithmeticException | IllegalArgumentException | IndexOutOfBoundsException e) {

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

Как мы видим, один блок catch используется для обработки и типа IOException и NullPointerException и NumberFormaException.

Вы можете использовать вложенные блоки try, которые могут помещаться в других блоках try. После вложенного блока try обязательно идет блок catch

public static void foo() {

} catch (IllegalArgumentException e) {

// обработка вложенного блока try

} catch (ArithmeticException e) {

Выбрасывание исключения с помощью ключевого слова throw

С помощью ключевого слова throw вы можете преднамеренно «выбросить» определенный тип исключения.

public static void foo(int a) {

throw new IllegalArgumentException(«Аргумент не может быть отрицательным!»);

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

public static void foo(int a) {

FileOutputStream fout = null;

File file = new File(«file.txt»);

fout = new FileOutputStream(file);

} catch (IOException e) {

// обработка исключения при записи в файл

} catch (IOException e) {

При любом развитии события в блоке try, код в блоке finally отработает в любом случае.

Блок finally отработает, даже если в try-catch присутствует оператор return.

Как правило, блок finally используется, когда мы в блоке try работаем с ресурсами (файлы, базы данных, сокеты и т.д.), когда по окончании блока try-catch мы освобождаем ресурсы. Например, допустим, в процессе работы программы возникло исключение, требующее ее преждевременного закрытия. Но в программе открыт файл или установлено сетевое соединение, а, следовательно, файл нужно закрыть, а соединение – разорвать. Для этого удобно использовать блок finally.

Блок try-with-resources является модификацией блока try. Данный блок позволяет автоматически закрывать ресурс после окончания работы блока try и является удобной альтернативой блоку finally.

public static void foo() {

Path p = Paths.get(«c:\\temp\\file.txt»);

try (BufferedReader reader = Files.newBufferedReader(p)) {

while ((line = reader.readLine()) != null)

System.out.println(line);

} catch (IOException e) {

Внутри скобок блока try объявляется один или несколько ресурсов, которые после отработки блока try-catch будут автоматически освобождены. Для этого объект ресурса должен реализовывать интерфейс java.lang.AutoCloseable.

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

Встроенные в Java исключения позволяют обрабатывать большинство распространенных ошибок. Тем не менее, вы можете создавать и обрабатывать собственные типы исключений. Для того, чтобы создать класс собственного исключения, достаточно определить как его произвольный от Exception или от RuntimeException (в зависимости от того, хотите ли вы использовать checked или unchecked – исключения).

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

  1. 1.

    определитесь, исключения какого типа вы хотите использовать для собственных исключений (checked или unchecked) и старайтесь создавать исключения только этого типа;

  2. 2.

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

Плохие практики при обработке исключений

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

  1. 1.

    Указание в блоке catch объекта исключения типа Exception. Существует очень большой соблазн при создании блока catch указать тип исключения Exception и, таким образом, перехватывать все исключения, которые относятся к этому классу (а это все исключения, кроме системных ошибок). Делать так крайне не рекомендуется, т.к. вместо того чтобы решать проблему с исключениями, мы фактически игнорируем ее и просто реализуем некоторую «заглушку», чтобы приложение продолжило работу дальше. Кроме того, каждый тип исключения должен быть обработан своим определенным образом.

  2. 2.

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

  3. 3.

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

Java defines several types of exceptions that relate to its various class libraries. Java also allows users to define their own exceptions. 

exceptions-in-java

Built-in Exceptions:

Built-in exceptions are the exceptions that are available in Java libraries. These exceptions are suitable to explain certain error situations. Below is the list of important built-in exceptions in Java. 

  1. ArithmeticException: It is thrown when an exceptional condition has occurred in an arithmetic operation.
  2. ArrayIndexOutOfBoundsException: It is thrown to indicate that an array has been accessed with an illegal index. The index is either negative or greater than or equal to the size of the array.
  3. ClassNotFoundException: This Exception is raised when we try to access a class whose definition is not found
  4. FileNotFoundException: This Exception is raised when a file is not accessible or does not open.
  5. IOException: It is thrown when an input-output operation failed or interrupted
  6. InterruptedException: It is thrown when a thread is waiting, sleeping, or doing some processing, and it is interrupted.
  7. NoSuchFieldException: It is thrown when a class does not contain the field (or variable) specified
  8. NoSuchMethodException: It is thrown when accessing a method that is not found.
  9. NullPointerException: This exception is raised when referring to the members of a null object. Null represents nothing
  10. NumberFormatException: This exception is raised when a method could not convert a string into a numeric format.
  11. RuntimeException: This represents an exception that occurs during runtime.
  12. StringIndexOutOfBoundsException: It is thrown by String class methods to indicate that an index is either negative or greater than the size of the string
  13. IllegalArgumentException : This exception will throw the error or error statement when the method receives an argument which is not accurately fit to the given relation or condition. It comes under the unchecked exception. 
  14. IllegalStateException : This exception will throw an error or error message when the method is not accessed for the particular operation in the application. It comes under the unchecked exception.

Examples of Built-in Exception

A. Arithmetic exception

Java

class ArithmeticException_Demo

{

    public static void main(String args[])

    {

        try {

            int a = 30, b = 0;

            int c = a/b; 

            System.out.println ("Result = " + c);

        }

        catch(ArithmeticException e) {

            System.out.println ("Can't divide a number by 0");

        }

    }

}

Output

Can't divide a number by 0

B. NullPointer Exception

Java

class NullPointer_Demo

{

    public static void main(String args[])

    {

        try {

            String a = null;

            System.out.println(a.charAt(0));

        } catch(NullPointerException e) {

            System.out.println("NullPointerException..");

        }

    }

}

Output

NullPointerException..

C. StringIndexOutOfBound Exception

Java

class StringIndexOutOfBound_Demo

{

    public static void main(String args[])

    {

        try {

            String a = "This is like chipping ";

            char c = a.charAt(24);

            System.out.println(c);

        }

        catch(StringIndexOutOfBoundsException e) {

            System.out.println("StringIndexOutOfBoundsException");

        }

    }

}

Output

StringIndexOutOfBoundsException

D. FileNotFound Exception

Java

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileReader;

 class File_notFound_Demo {

    public static void main(String args[])  {

        try {

            FileReader fr = new FileReader(file);

        } catch (FileNotFoundException e) {

           System.out.println("File does not exist");

        }

    }

}

Output:

File does not exist

E. NumberFormat Exception

Java

class  NumberFormat_Demo

{

    public static void main(String args[])

    {

        try {

            int num = Integer.parseInt ("akki") ;

            System.out.println(num);

        } catch(NumberFormatException e) {

            System.out.println("Number format exception");

        }

    }

}

Output

Number format exception

F. ArrayIndexOutOfBounds Exception

Java

class ArrayIndexOutOfBound_Demo

{

    public static void main(String args[])

    {

        try{

            int a[] = new int[5];

            a[6] = 9;

        }

        catch(ArrayIndexOutOfBoundsException e){

            System.out.println ("Array Index is Out Of Bounds");

        }

    }

}

Output

Array Index is Out Of Bounds

G. IO Exception 

Java

class IOException_Demo {

    public static void main(String[] args)

    {

        Scanner scan = new Scanner("Hello Geek!");

        System.out.println("" + scan.nextLine());

        System.out.println("Exception Output: "

                           + scan.ioException());

        scan.close();

    }

}

Output:

Hello Geek! 
Exception Output: null

H. NoSuchMethod Exception 

Java

public class NoSuchElementException_Demo {

    public static void main(String[] args)

    {

        Set exampleleSet = new HashSet();

        Hashtable exampleTable = new Hashtable();

        exampleleSet.iterator().next();

        exampleTable.elements().nextElement();

    }

}

I. IllegalArgumentException: This program, checks whether the person is eligible for voting or not. If the age is greater than or equal to 18 then it will not throw any error. If the age is less than 18 then it will throw an error with the error statement. 

Also, we can specify “throw new IllegalArgumentException()” without the error message. We can also specify Integer.toString(variable_name) inside the IllegalArgumentException() and It will print the argument name which is not satisfied the given condition.

Java

import java.io.*;

class GFG {

   public static void print(int a) 

    {

         if(a>=18){

          System.out.println("Eligible for Voting");

          }

          else{

          throw new IllegalArgumentException("Not Eligible for Voting");

          }

    }

    public static void main(String[] args) {

         GFG.print(14); 

    }

}

Output : 

Exception in thread "main" java.lang.IllegalArgumentException: Not Eligible for Voting
at GFG.print(File.java:13)
at GFG.main(File.java:19)

J. IllegalStateException: This program, displays the addition of numbers only for Positive integers. If both the numbers are positive then only it will call the print method to print the result otherwise it will throw the IllegalStateException with an error statement. Here, the method is not accessible for non-positive integers.

Also, we can specify the “throw new IllegalStateException()” without the error statement.

Java

import java.io.*;

class GFG {

      public static void  print(int a,int b)

     {

         System.out.println("Addition of Positive Integers :"+(a+b));

     }

    public static void main(String[] args) {

    int n1=7;

    int n2=-3;

     if(n1>=0 && n2>=0)

     {

         GFG.print(n1,n2);

     }

     else

     {

         throw new IllegalStateException("Either one or two numbers are not Positive Integer");

     }

    }

}

Output :

Exception in thread "main" java.lang.IllegalStateException: Either one or two numbers are not Positive Integer
at GFG.main(File.java:20)

k. ClassNotFound Exception :

Java

public class ClassNotFoundException_Demo

{

    public static void main(String[] args) {

        try{

            Class.forName("Class1");  

        }

        catch(ClassNotFoundException e){

            System.out.println(e);

            System.out.println("Class Not Found...");

        }

    }

}

Output

java.lang.ClassNotFoundException: Class1
Class Not Found...

User-Defined Exceptions

Sometimes, the built-in exceptions in Java are not able to describe a certain situation. In such cases, the user can also create exceptions which are called ‘user-defined Exceptions’. 

The following steps are followed for the creation of a user-defined Exception. 

  • The user should create an exception class as a subclass of the Exception class. Since all the exceptions are subclasses of the Exception class, the user should also make his class a subclass of it. This is done as: 
class MyException extends Exception
  • We can write a default constructor in his own exception class. 
MyException(){}
  • We can also create a parameterized constructor with a string as a parameter. 
    We can use this to store exception details. We can call the superclass(Exception) constructor from this and send the string there. 
MyException(String str)
{
   super(str);
}
  • To raise an exception of a user-defined type, we need to create an object to his exception class and throw it using the throw clause, as: 
MyException me = new MyException(“Exception details”);
throw me;
  • The following program illustrates how to create your own exception class MyException.
  • Details of account numbers, customer names, and balance amounts are taken in the form of three arrays.
  • In main() method, the details are displayed using a for-loop. At this time, a check is done if in any account the balance amount is less than the minimum balance amount to be apt in the account.
  • If it is so, then MyException is raised and a message is displayed “Balance amount is less”.

Example

Java

class MyException extends Exception

{

    private static int accno[] = {1001, 1002, 1003, 1004};

    private static String name[] =

                 {"Nish", "Shubh", "Sush", "Abhi", "Akash"};

    private static double bal[] =

         {10000.00, 12000.00, 5600.0, 999.00, 1100.55};

    MyException() {    }

    MyException(String str) { super(str); }

    public static void main(String[] args)

    {

        try  {

            System.out.println("ACCNO" + "\t" + "CUSTOMER" +

                                           "\t" + "BALANCE");

            for (int i = 0; i < 5 ; i++)

            {

                System.out.println(accno[i] + "\t" + name[i] +

                                               "\t" + bal[i]);

                if (bal[i] < 1000)

                {

                    MyException me =

                       new MyException("Balance is less than 1000");

                    throw me;

                }

            }

        }

        catch (MyException e) {

            e.printStackTrace();

        }

    }

}

Runtime Error 

 MyException: Balance is less than 1000
    at MyException.main(fileProperty.java:36)

Output:  

ACCNO    CUSTOMER    BALANCE
1001    Nish    10000.0
1002    Shubh    12000.0
1003    Sush    5600.0
1004    Abhi    999.0

Related Articles: 

  • Checked vs Unchecked Exceptions in Java
  • Catching base and derived classes as exceptions
  • Quiz on Exception Handling

This article is contributed by Nishant Sharma. If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please write comments if you find anything incorrect, or if you want to share more information about the topic discussed above.

Last Updated :
11 Sep, 2023

Like Article

Save Article

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

Зміст

  1. ArithmeticException 
  2. ArrayIndexOutOfBoundsException 
  3. ArrayStoreException 
  4. ClassCastException 
  5. ConcurrentModificationException 
  6. EmptyStackException
  7. IllegalArgumentException 
  8. IllegalMonitorStateException 
  9. IllegalStateException 
  10. IllegalThreadStateException 
  11. IndexOutOfBoundsException 
  12. MissingResourceException 
  13. NegativeArraySizeException 
  14. NoSuchElementException 
  15. NullPointerException 
  16. NumberFormatException
  17. SecurityException
  18. StringIndexOutOfBoundsException
  19. UndeclaredThrowableException
  20. UnsupportedOperationException 

ArithmeticException 

Возникла исключительная ситуация, связанная с ошибкой при выполнении арифметического вычисления (например, с попыткой целочисленного деления на нуль). Класс ArithmeticalException унаследован от RuntimeException.

ArrayIndexOutOfBoundsException 

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

ArrayStoreException 

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

Класс ArrayStoreException унаследован от RuntimeException.

ClassCastException 

Выполнена неверная операция преобразования типов (ошибка приведения типов).

Класс ClassCastException унаследован от RuntimeException.

ConcurrentModificationException 

Осуществлена попытка изменения объекта конкурирующим потоком вычислений (thread) с нарушением контракта класса (тип определен в пакете jav.util).

Также исключение может происходить при работе с коллекциями при обычной однопоточной работе. ConcurrentModificationException возникает когда коллекция модифицируется «одновременно» с проходом по коллекции итератором любыми средствами кроме самого итератора.

Класс ConcurrentModificationException унаследован от RuntimeException.

EmptyStackException

Возникает при попытке извлечения объекта из пустого стека. Тип обладает только конструктором без параметров, поскольку причина ситуации очевидна без дополнительных разъяснений (тип определен в пакете java.util). 

Класс EmptyStackExceptionунаследован от RuntimeException.

IllegalArgumentException 

Методу передано неверное значение аргумента (например, отрицательное, когда метод предполагает задание положительных значений).

Класс IllegalArgumentExceptionунаследован от RuntimeException.

IllegalMonitorStateException 

Выполнено обращение к методу wait, notifyAll или notify объекта, когда текущий поток вычислений не обладает блокировкой (lock) этого объекта.

Класс IllegalMonitorStateException унаследован от RuntimeException.

IllegalStateException 

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

Класс IllegalStateExceptionунаследован от RuntimeException.

IllegalThreadStateException 

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

Класс IllegalThreadStateException унаследован от IllegalArgumentException

IndexOutOfBoundsException 

Задано значение индекса массива или содержимого строки типа String, не принадлежащее допустимому диапазону.

Класс IndexOutOfBoundsException унаследован от RuntimeException

MissingResourceException 

Не найден требуемый ресурс или пакет ресурсов (resource bundle). Единственный конструктор типа предусматривает задание трех аргументов: строки описательного сообщения, наименования класса ресурсов и объекта ключа, отвечающего отсутствующему ресурсу. Для получения строк наименования класса и ключа применяются методы detClassName и getKey соответственно (тип определен в пакете java.util).

Класс MissingResourceExceptionунаследован от RuntimeException.

NegativeArraySizeException 

Предпринята попытка создания массива с размером, значение которого задано отрицательным числом.

Класс NegativeArraySizeException унаследован от RuntimeException.

NoSuchElementException 

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

Класс NoSuchElementException унаследован от RuntimeException.

NullPointerException 

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

Класс NullPointerException унаследован от RuntimeException.

NumberFormatException

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

Класс NumberFormatException унаследован от IllegalArgumentException.

SecurityException

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

Класс SecurityException унаследован от RuntimeException.

StringIndexOutOfBoundsException

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

Класс StringIndexOutOfBoundsException унаследован от IndexOutOfBoundsException.

UndeclaredThrowableException

Выбрасывается при обращении к методу целевого объекта посредством объекта рефлективного класса Proxy, если метод invoke объекта InvocationHandler генерирует объявляемое исключение, которое не допускает присваивания ни одному из типов исключений, упомянутых в предложении throws метода целевого объекта. Рассматриваемое исключение содержит ссылку на исключение, генерируемое методом invoke, которое может быть получено с помощью метода getUndeclaredThrowable. Класс исключений UndeclaredThrowableException поддерживает два конструктора: оба принимают в качестве параметров ссылку на объект Throwable, а один из них, помимо того, строку описания (тип определен в пакете java.lang.reflect).

Класс UndeclaredThrowableException унаследован от RuntimeException.

UnsupportedOperationException 

Предпринята попытка выполнения операции над объектом, который ее не поддерживает (например, модификация объекта, обозначенного признаком “только для чтения”). используется также классами коллекций из состава пакета java.util как реакция на вызов методов производного класса, реализация которых не обязательна.

Класс UnsupportedOperationException унаследован от RuntimeException.

Смотрите также: Методы обработки исключений в java

Java_Deep_7.4-5020-83cb21.png

JavaSpec_Welcome_970x90-1801-439a19.png

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

Что называют исключением. Исключения в мире программирования

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

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

Используемые ключевые слова

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

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

Кроме того:
1. Для возбуждения исключения используем throw.
2. Для предупреждения в сигнатуре методов о том, что метод может выбросить исключение, применяем throws.

Давайте на примере посмотрим, как используются ключевые слова в Java-программе:

//метод считывает строку с клавиатуры

public String input() throws MyException {//предупреждаем с помощью throws,
// что метод может выбросить исключение MyException
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String s = null;
//в блок try заключаем код, в котором может произойти исключение, в данном
// случае компилятор нам подсказывает, что метод readLine() класса
// BufferedReader может выбросить исключение ввода/вывода
    try {
        s = reader.readLine();
// в блок  catch заключаем код по обработке исключения IOException
    } catch (IOException e) {
        System.out.println(e.getMessage());
// в блоке finally закрываем поток чтения
    } finally {
// при закрытии потока тоже возможно исключение, например, если он не был открыт, поэтому “оборачиваем” код в блок try
        try {
            reader.close();
// пишем обработку исключения при закрытии потока чтения
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    if (s.equals("")) {
// мы решили, что пустая строка может нарушить в дальнейшем работу нашей программы, например, на результате этого метода нам надо вызывать метод substring(1,2), поэтому мы вынуждены прервать выполнение программы с генерацией своего типа исключения MyException с помощью throw
        throw new MyException("String can not be empty!");
    }
    return s;
}

Зачем нам механизм исключений?

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

То есть мы видим, что из-за правильных действий дорожной службы шоферы крупногабаритных транспортных средств:
1) получили возможность заранее изменить свой путь;
2) были предупреждены об опасности;
3) были предупреждены о невозможности проезжать по мосту при определённых условиях.

Вот как наш жизненный пример соотносится с применением исключения на Java:

1-20219-9bd18c.jpg

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

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

В общем, механизм исключений в Java работает схожим образом. На стадии разработки программы мы выполняем «ограждение» опасных участков кода в отношении наших исключений, используя блок try{}. Чтобы предусмотреть запасные пути, применяем блок catch{}. Код, выполняемый в программе при любом исходе, пишем в блоке finally{}.

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

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

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

Предупреждаем о неприятностях

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

После упоминания ключевого слова throws мы указываем тип исключения. Как правило, речь идёт о наследниках класса Exception Java. Но так как Java — это объектно-ориентированный язык программирования, все исключения представляют собой объекты.

2-20219-ee1e82.jpg

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

Когда возникают ошибки при выполнении программы, исполняющая среда Java Virtual Machine обеспечивает создание объекта нужного типа, используя иерархию исключений Java — речь идёт о множестве возможных исключительных ситуаций, которые унаследованы от класса Throwable — общего предка. При этом исключительные ситуации, которые возникают в программе, делят на 2 группы:
1. Ситуации, при которых восстановление нормальной дальнейшей работы невозможно.
2. Ситуации с возможностью восстановления.

К первой группе можно отнести случаи, при которых возникают исключения, которые унаследованы из класса Error. Это ошибки, возникающие во время выполнения программы при сбое работы Java Virtual Machine, переполнении памяти либо сбое системы. Как правило, такие ошибки говорят о серьёзных проблемах, устранение которых программными средствами невозможно. Данный вид исключений в Java относят к неконтролируемым исключениям на стадии компиляции (unchecked). К этой же группе относятся и исключения-наследники класса Exception, генерируемые Java Virtual Machine в процессе выполнения программы — RuntimeException. Данные исключения тоже считаются unchecked на стадии компиляции, а значит, написание кода по их обработке необязательно.

Что касается второй группы, то к ней относят ситуации, которые можно предвидеть ещё на стадии написания приложения, поэтому для них код обработки должен быть написан. Это контролируемые исключения (checked). И в большинстве случаев Java-разработчики работают именно с этими исключениями, выполняя их обработку.

Создание исключения

В процессе исполнения программы исключение генерируется Java Virtual Machine либо вручную посредством оператора throw. В таком случае в памяти происходит создание объекта исключения, выполнение основного кода прерывается, а встроенный в JVM обработчик исключений пробует найти способ обработать это самое исключение.

Обработка исключения

Обработка исключений в Java подразумевает создание блоков кода и производится в программе посредством конструкций try{}finally{}, try{}catch, try{}catch{}finally.

3-20219-4ec690.jpg

В процессе возбуждения исключения в try обработчик исключения ищется в блоке catch, который следует за try. При этом если в catch присутствует обработчик данного вида исключения, происходит передача управления ему. Если же нет, JVM осуществляет поиск обработчика данного типа исключения, используя для этого цепочку вызова методов. И так происходит до тех пор, пока не находится подходящий catch. После того, как блок catch выполнится, управление переходит в необязательный блок finally. Если подходящий блок catch найден не будет, Java Virtual Machine остановит выполнение программы, выведя стек вызовов методов под названием stack trace. Причём перед этим выполнится код блока finally при наличии такового.

Рассмотрим практический пример обработки исключений:

public class Print {

     void print(String s) {
        if (s == null) {
            throw new NullPointerException("Exception: s is null!");
        }
        System.out.println("Inside method print: " + s);
    }

    public static void main(String[] args) {
        Print print = new Print();
        List list= Arrays.asList("first step", null, "second step");

        for (String s:list) {
            try {
                print.print(s);
            }
            catch (NullPointerException e) {
                System.out.println(e.getMessage());
                System.out.println("Exception was processed. Program continues");
            }
            finally {
                System.out.println("Inside bloсk finally");
            }
            System.out.println("Go program....");
            System.out.println("-----------------");
        }

    }
    }

А теперь глянем на результаты работы метода main:

Inside method print: first step
Inside bloсk finally
Go program....
-----------------
Exception: s is null!
Exception was processed. Program continues
Inside bloсk finally
Go program....
-----------------
Inside method print: second step
Inside bloсk finally
Go program....
-----------------

Блок finally чаще всего используют, чтобы закрыть открытые в try потоки либо освободить ресурсы. Но при написании программы уследить за закрытием всех ресурсов возможно не всегда. Чтобы облегчить жизнь разработчикам Java, была предложена конструкция try-with-resources, автоматически закрывающая ресурсы, открытые в try. Используя try-with-resources, мы можем переписать наш первый пример следующим образом:

public String input() throws MyException {
    String s = null;
    try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
        s = reader.readLine();
   } catch (IOException e) {
       System.out.println(e.getMessage());
   }
    if (s.equals("")){
        throw new MyException ("String can not be empty!");
    }
    return s;
}

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

public String input() {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        s = reader.readLine();
        if (s.equals("")) {
            throw new MyException("String can not be empty!");
        }
    } catch (IOException | MyException e) {
        System.out.println(e.getMessage());
    }
    return s;
}

Итоги

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

Основные вопросы об исключениях в Java

1.Что такое проверяемые и непроверяемые исключения?
Если говорить коротко, то первые должны быть явно пойманы в теле метода либо объявлены в секции throws метода. Вторые вызываются проблемами, которые не могут быть решены. Например, это нулевой указатель или деление на ноль. Проверяемые исключения очень важны, ведь от других программистов, использующих ваш API, вы ожидаете, что они знают, как обращаться с исключениями. К примеру, наиболее часто встречаемое проверяемое исключение — IOException, непроверяемое — RuntimeException.
2.Почему переменные, определённые в try, нельзя использовать в catch либо finally?
Давайте посмотрим на нижеследующий код. Обратите внимание, что строку s, которая объявлена в блоке try, нельзя применять в блоке catch. То есть данный код не скомпилируется.

try {
    File file = new File("path");
    FileInputStream fis = new FileInputStream(file);
    String s = "inside";
} catch (FileNotFoundException e) {
    e.printStackTrace();
    System.out.println(s);
}

А всё потому, что неизвестно, где конкретно в try могло быть вызвано исключение. Вполне вероятно, что оно было вызвано до объявления объекта.
3.Почему Integer.parseInt(null) и Double.parseDouble(null) вызывают разные исключения?
Это проблема JDK. Так как они были разработаны разными людьми, то заморачиваться вам над этим не стоит:

Integer.parseInt(null);
// вызывает java.lang.NumberFormatException: null

Double.parseDouble(null);
// вызывает java.lang.NullPointerException

4.Каковы основные runtime exceptions в Java?
Вот лишь некоторые из них:

IllegalArgumentException
ArrayIndexOutOfBoundsException

Их можно задействовать в операторе if, если условие не выполняется:

if (obj == null) {
   throw new IllegalArgumentException("obj не может быть равно null");

5.Возможно ли поймать в одном блоке catch несколько исключений?
Вполне. Пока классы данных исключений можно отследить вверх по иерархии наследования классов до одного и того же суперкласса, возможно применение только этого суперкласса.
6.Способен ли конструктор вызывать исключения?
Способен, ведь конструктор — это лишь особый вид метода.

class FileReader{
    public FileInputStream fis = null;

    public FileReader() throws IOException{
        File dir = new File(".");//get current directory
        File fin = new File(dir.getCanonicalPath() + File.separator + "not-existing-file.txt");
        fis = new FileInputStream(fin);
    }
}

7.Возможен ли вызов исключений в final?
В принципе, можете сделать таким образом:

public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            FileInputStream fis = new FileInputStream(file2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Но если желаете сохранить читабельность, объявите вложенный блок try-catch в качестве нового метода и вставьте вызов данного метода в блок finally.

finally. 
public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        methodThrowException();
    }
}

JavaSpec_Welcome_970x90-1801-439a19.png

#База знаний


  • 0

Разбираемся, что такое исключения, зачем они нужны и как с ними работать.

 vlada_maestro / shutterstock

Мария Помазкина

Хлебом не корми — дай кому-нибудь про Java рассказать.

Из этой статьи вы узнаете:

  • что такое исключения (Exceptions);
  • как они возникают и чем отличаются от ошибок (Errors);
  • зачем нужна конструкция try-catch;
  • как разобраться в полученном исключении
  • и как вызвать исключение самому.

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

За примером далеко ходить не надо: сделаем то, что нам запрещали ещё в школе, — поделим на ноль.

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

public static void hereWillBeTrouble(int a, int b) {
    int oops = a / b;
    System.out.println(oops);
}

А получим вот что:

Это и есть исключение.

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

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

У всех классов исключений есть общий класс-предок Throwable, от него наследуются классы Error и Exception, базовые для всех прочих.

Верхушка иерархии исключений Java

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

Error is the superclass of all the exceptions from which ordinary programs are not ordinarily expected to recover.

Что в переводе означает: ошибки (Error) — это такие исключительные ситуации, в которых восстанавливать работу программы не предполагается.

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

static void notGood() {
    System.out.println("Только не снова!");
    notGood();
}

При работе этого метода у нас возникнет ошибка: Exception in thread «main» java.lang.StackOverflowError — стек вызовов переполнился, так как мы не указали условие выхода из рекурсии.

А теперь об Exception. Эти исключительные ситуации возникают, если разработчик допустил невыполнимую операцию, не предусмотрел особые случаи в бизнес-логике программы (или сообщает о них с помощью исключений).

1. Невыполнимая операция

Мир не рухнул, как в случае с Error, просто Java не знает, что делать дальше. Как раз из этого разряда деление на ноль в начале статьи: и правда, какое значение тогда присвоить переменной oops?

Убедитесь сами, что исключение класса ArithmeticException наследуется как раз от Exception.

Стоит запомнить. В IntelliJ IDEA, чтобы увидеть положение класса в иерархии, выберите его и нажмите Ctrl + H (или на пункт Type Hierarchy в меню Navigate).

Другая частая ситуация — обращение к несуществующему элементу массива. Например, у нас в нём десять элементов, а мы пытаемся обратиться к одиннадцатому.

2. Особый случай в бизнес-логике программы

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

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

Или, допустим, у нас есть метод, который читает файл. Сам метод написан верно. Пользователь передал в него корректный путь. Только вот у этого работника нет права читать этот файл (его роль и права обусловлены предметной областью). Что же тогда методу возвращать? Вернуть-то нечего, ведь метод не отработал. Самое очевидное решение — выдать исключение.

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

Простейший вариант — ничего; возникает исключение — программа просто прекращает работать.

Чтобы убедиться в этом, выполним код:

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

public static void hereWillBeTrouble(int a, int b) {
    System.out.println("Всё, что было до...");
    int oops = a / b;
    System.out.println(oops);
    System.out.println("Всё, что будет после...");
}

Так и есть: до деления на ноль код выполнялся, а после — нет.

Это интересно: когда возникает исключение, программисты выдают что-то вроде «код [вы]бросил исключение» или «код кинул исключение». А глагол таков потому, что все исключения — наследники класса Throwable, что значит «бросаемый» / «который можно бросить».

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

Для этого нужно заключить кусок кода, который может вызвать исключение, в конструкцию try-catch.

Как это работает: если в блоке try возникает исключение, которое указано в блоке catch, то исполнение блока try прервётся и выполнится код из блока catch.

Например:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble(int a, int b) {
    int oops;
    try {
        System.out.println("Всё, что было до...");
        oops = a / b;
        System.out.println(oops);
        System.out.println("Всё, что будет после...");
    } catch (ArithmeticException e) {
        System.out.println("Говорили же не делить на ноль!");
        oops = 0;
    }
    System.out.println("Метод отработал");
}

Разберём этот код.

Если блок try кинет исключение ArithmeticException, то управление перехватит блок catch, который выведет строку «Говорили же не делить на ноль!», а значение oops станет равным 0.

После этого программа продолжит работать как ни в чём не бывало: выполнится код после блока try-catch, который сообщит: «Метод отработал».

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

Есть ещё и третий вариант — пробросить исключение наверх. Но об этом в следующей статье.

Вернёмся к первой картинке. Посмотрим, что нам сказала Java, когда произошло исключение:

Начинаем разбирать сверху вниз:

— это указание на поток, в котором произошло исключение. В нашей простой однопоточной программе это поток main.

— какое исключение брошено. У нас это ArithmeticException. А java.lang.ArithmeticException — полное название класса вместе с пакетом, в котором он размещается.

— весточка, которую принесло исключение. Дело в том, что одно и то же исключение нередко возникает по разным причинам. И тут мы видим стандартное пояснение «/ by zero» — из-за деления на ноль.

— это самое интересное: стектрейс.

Стектрейс (Stack trace) — это упорядоченный список методов, сквозь которые исключение пронырнуло.

У нас оно возникло в методе hereWillBeTrouble на 8-й строке в классе Main (номер строки и класс указаны в скобках синим). А этот метод, в свою очередь, вызван методом main на 3-й строке класса Main.

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

Советую закреплять теорию на практике. Поэтому вернитесь в блок про Error и вызовите метод notGood — увидите любопытный стектрейс.

Всё это время мы имели дело с исключением, которое бросает Java-машина — при делении на ноль. Но как вызвать исключение самим?

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

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

private static void hereWillBeTrouble(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("ты опять делишь на ноль?");
    }
    int oops = a / b;
    System.out.println(oops);
}

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

А получим мы то же самое, что и в самом первом примере, только вместо стандартной фразы «/by zero» теперь выдаётся наш вопрос-пояснение «ты опять делишь на ноль?»:

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

Как зарабатывать больше с помощью нейросетей?
Бесплатный вебинар: 15 экспертов, 7 топ-нейросетей. Научитесь использовать ИИ в своей работе и увеличьте доход.

Узнать больше

Понравилась статья? Поделить с друзьями:
  • Java деление на ноль какая ошибка
  • Java вызов ошибки
  • Java выдает ошибку 1603
  • Java virtual machine launcher ошибка как исправить майнкрафт
  • Java ошибка 1606