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

Вследствие чего возникают исключения типа runtimeexception

  • автор:

Вследствие чего возникают исключения типа runtimeexception

На этом шаге мы начнем говорить про обработку ошибок

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

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

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

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

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

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

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

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

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

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

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

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

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

Остальные исключения возникают в следующих случаях:

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

Исключения типа RuntimeException практически всегда возникают по вине программиста. Например, исключение типа ArrayIndexOutBoundsException никогда не возникнет, если перед тем, как воспользоваться переменной, проверить не содержит ли она пустое значение null.

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

В спецификации языка Java любое исключение, производное от класса Error или RuntimeError, называется не проверяемым (unchecked). Все остальные исключения называются проверяемыми (checked). Для всех проверяемых исключений компилятор проверяет наличие соответствующих обработчиков.

На следующем шаге мы поговорим про проброс проверяемых исключений

Классификация исключений Java

Классификация исключений Java

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

Обратите внимание на то, что хотя все исключения имеют общего предка Throwable, иерархия сразу разделяется на две ветви: Error и Exception.

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

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

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

  • Неверное приведение типов;
  • Выход за пределы массива;
  • Попытка обратиться к объекту по ссылке null.

Остальные исключения возникают в следующих случаях:

  • Попытка чтения после конца файла;
  • Попытка установить соединение, используя неправильный URL;
  • Попытка получить объект Class, если в строке указан несуществующий класс.

Исключения RuntimeException практически всегда возникают по вине программиста. Исключения ArrayIndexOutBoundsException можно избежать, если всегда проверять индексы массива. Исключение NullPointerException никогда не возникает, если перед использованием переменной вы проверите, не содержит ли она значение null.

МИР МЕТАЛЛА – молодая и современная металлобаза, рожденная на берегах Волги. Компания «МИР МЕТАЛЛА» оказывает услуги по резке приобретенного металлопроката в размер, нарезке резьбы на водогазопроводной трубе, сварке металлоизделий, рубке и гибке стального листа.

А как насчет неверного URL-адреса? Всегда ли можно обнаружить ошибку заранее? Разные браузеры обрабатывают разные URL-адреса. Например, браузер NetScape может работать с URL-адресами вида mailto:, а программа appletviewer — нет. Следовательно, термин «неправильный» зависит от среды, а не от программы.

В спецификации языка Java любое исключение, производное от класса Error или RuntimeError, называется неконтролируемым(unchecked). Все остальные исключения называются контролируемыми(checked). Для всех контролируемых исключений компилятор проверяет наличие соответствующих обработчиков.

Имя RuntimeException иногда вводит в заблуждение. Разумеется, все ошибки, которые мы обсуждаем, возникают в ходе выполнения программы.

Исключения

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

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

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

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

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

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

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

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

Пример 1

import java.io.File; import java.io.FileReader; public class Test < public static void main(String args[]) < File f = new File("D://java/file.txt"); FileReader fr = new FileReader(f); >> 

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

C:\>javac Test.java Test.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown FileReader fr = new FileReader(f); ^ 1 error 

Примечание. В виду того, что методы read() и close() класса FileReader вызывают IOException, компилятор может уведомить вас об обработке IOException, совместно с FileNotFoundException.

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

К примеру, если вами в вашей программе был объявлен массив из 5 элементов, попытка вызова 6-го элемента массива повлечет за собой возникновение ArrayIndexOutOfBoundsExceptionexception.

Пример 2

public class Test < public static void main(String args[]) < int array[] = ; System.out.println(array[4]); > > 

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

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Exceptions.Test.main(Test.java:8) 
  • Ошибки — не являются исключениями, однако представляют проблемы, которые возникают независимо от пользователя либо программиста. Ошибки в вашем коде обычно игнорируются в виду того, что в редких случаях их обработка окажется результативной. К примеру, ошибка возникнет при переполнении стека. На этапе компиляции они также игнорируются.

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

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

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

Класс исключений делится на два основных подкласса: класс IOException и класс RuntimeException.

Исключения

По ссылке представлен перечень наиболее распространенных контролируемых (checked) и неконтролируемых (unchecked) встроенных исключений в Java.

Методы исключений

Далее представлен список важных методов, доступных в классе Throwable.

Метод и описание
1 public String getMessage()
Возврат подробного сообщения о произошедшем исключении. Инициализация данного сообщения производится в конструкторе Throwable.
2 public Throwable getCause()
Возврат причины исключения, представленной объектом Throwable.
3 public String toString()
Возврат имени класса, соединенного с результатом getMessage().
4 public void printStackTrace()
Выведение результата toString() совместно с трассировкой стека в System.err, поток вывода ошибок.
5 public StackTraceElement [] getStackTrace()
Возврат массива, содержащего каждый элемент в трассировке стека. Элемент с номером 0 представляет вершину стека вызовов, последний элемент массива отображает метод на дне стека вызовов.
6 public Throwable fillInStackTrace()
Заполняет трассировку стека данного объекта Throwable текущей трассировкой стека, дополняя какую-либо предшествующую информацию в трассировке стека.

Обработка исключений — try и catch

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

Описание

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

try < // Защищенный код >catch(НазваниеИсключения e1) < // Блок catch >

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

Оператор catch включает объявление типа исключения, которое предстоит обработать. При возникновении исключения в защищенном коде, блок catch (либо блоки), следующий за try, будет проверен. В случае, если тип произошедшего исключения представлен в блоке catch, исключение передается в блок catch аналогично тому, как аргумент передается в параметр метода.

Пример

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

import java.io.*; public class Test < public static void main(String args[]) < try < int array[] = new int[2]; System.out.println("Доступ к третьему элементу:" + array[3]); >catch(ArrayIndexOutOfBoundsException e) < System.out.println("Исключение:" + e); >System.out.println("Вне блока"); > > 

Вследствие этого будет получен следующий результат:

Исключение:java.lang.ArrayIndexOutOfBoundsException: 3 Вне блока 

Многократные блоки catch

За блоком try могут следовать несколько блоков catch. Синтаксис многократных блоков catch выглядит следующим образом:

try < // Защищенный код >catch(ИсключениеТип1 e1) < // Блок catch >catch(ИсключениеТип2 e2) < // Блок catch >catch(ИсключениеТип3 e3) < // Блок catch >

Представленные выше операторы демонстрируют три блока catch, однако, после однократного try количество данных используемых блоков может быть произвольным. В случае возникновения исключения в защищенном коде, исключение выводится в первый блок catch в списке. Если тип данных генерируемого исключения совпадает с ИсключениеТип1, он перехватывается в указанной области. В обратном случае, исключение переходит ко второму оператору catch. Это продолжается до тех пор, пока не будет произведен перехват исключения, либо оно не пройдет через все операторы, в случае чего выполнение текущего метода будет прекращено, и исключение будет перенесено к предшествующему методу в стеке вызовов.

Пример

Далее представлен сегмент кода, демонстрирующий использование многократных операторов try/catch.

try < file = new FileInputStream(fileName); x = (byte) file.read(); >catch(IOException e1) < e1.printStackTrace(); return -1; >catch(FileNotFoundException e2) // Недействительно!

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

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

catch (IOException|FileNotFoundException ex) < logger.log(ex); throw ex; 

Ключевые слова throws/throw

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

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

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

Представленный ниже метод отображает, что им генерируется RemoteException:

Пример 1

import java.rmi.RemoteException; public class Test < public void deposit(double amount) throws RemoteException < // Реализация метода throw new RemoteException(); >// Остаток определения класса > 

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

Пример 2

import java.rmi.RemoteException; public class Test < public void withdraw(double amount) throws RemoteException, InsufficientFundsException < // Реализация метода >// Остаток определения класса > 

Блок finally

В Java finally следует за блоком try либо блоком catch. Блок finally в коде выполняется всегда независимо от наличия исключения.

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

Блок finally в Java появляется по окончании блоков catch, его синтаксис выглядит следующим образом:

Синтаксис

try < // Защищенный код >catch(ИсключениеТип1 e1) < // Блок catch >catch(ИсключениеТип2 e2) < // Блок catch >catch(ИсключениеТип3 e3) < // Блок catch >finally < // Блок finally всегда выполняется. >

Пример

public class Test < public static void main(String args[]) < int array[] = new int[2]; try < System.out.println("Доступ к третьему элементу:" + array[3]); >catch(ArrayIndexOutOfBoundsException e) < System.out.println("Исключение:" + e); >finally < array[0] = 6; System.out.println("Значение первого элемента: " + array[0]); System.out.println("Оператор finally выполнен."); >> > 

Вследствие этого будет получен следующий результат:

Исключение:java.lang.ArrayIndexOutOfBoundsException: 3 Значение первого элемента: 6 Оператор finally выполнен. 

Следует помнить, что:

  • Выражение catch не может существовать без оператора try.
  • При наличии блока try/catch, выражение finally не является обязательным.
  • Блок try не может существовать при отсутствии выражения catch либо выражения finally.
  • Существование какого-либо кода в промежутке между блоками try, catch, finally является невозможным.

Конструкция try-with-resources

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

Пример 1

import java.io.FileReader; import java.io.File; import java.io.IOException; public class Test < public static void main(String args[]) < FileReader fr = null; try < File f = new File("file.txt"); fr = new FileReader(f); char [] array = new char[10]; fr.read(array); // чтение содержимого массива for(char c : array) System.out.print(c); // вывод символов на экран, один за одним >catch(IOException e1) < e1.printStackTrace(); >finally < try < fr.close(); >catch(IOException e2) < e2.printStackTrace(); >> > > 

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

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

Синтаксис

try(FileReader fr = new FileReader("Путь к файлу")) < // использование ресурса >catch() < // тело catch >> 

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

Пример 2

import java.io.FileReader; import java.io.IOException; public class Test < public static void main(String args[]) < try(FileReader fr = new FileReader("E://Soft/NetBeans 8.2/Projects/test/test/file.txt")) < char [] array = new char[10]; fr.read(array); // чтение содержимого массива for(char c : array) System.out.print(c); // вывод символов на экран, один за одним >catch(IOException e) < e.printStackTrace(); >> > 

При работе с конструкцией try-with-resources следует принимать во внимание следующие нюансы:

  • С целью использования конструкции try-with-resources следует реализовать интерфейс AutoCloseable, после чего соответствующий метод close() будет вызван автоматически во время выполнения.
  • В конструкции try-with-resources возможно указание одного и более классов.
  • При указании нескольких классов в блоке try конструкции try-with-resources, закрытие данных классов будет производиться в обратном порядке.
  • За исключением внесения ресурсов в скобки, все элементы являются равными аналогично нормальному блоку try/catch в составе блока try.
  • Ресурсы, внесенные в try, конкретизируются до запуска блока try.
  • Ресурсы непосредственно в составе блока try указываются как окончательные.

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

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

  • Все исключения должны быть дочерними элементами Throwable.
  • Если вы планируете произвести запись контролируемого исключения с автоматическим использованием за счет правила обработки или объявления, вам следует расширить класс Exception.
  • Если вы хотите произвести запись исключения на этапе выполнения, вам следует расширить класс RuntimeException.

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

class MyException extends Exception

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

Пример

// Название файла InsufficientFundsException.java import java.io.*; public class InsufficientFundsException extends Exception < private double amount; public InsufficientFundsException(double amount) < this.amount = amount; >public double getAmount() < return amount; >> 

С целью демонстрации наших исключений, определяемых пользователем, следующий класс Checking содержит метод withdraw(), генерирующий InsufficientFundsException.

// Название файла Checking.java import java.io.*; public class Checking < private int number; private double balance; public Checking(int number) < this.number = number; >public void deposit(double amount) < balance += amount; >public void withdraw(double amount) throws InsufficientFundsException < if(amount else < double needs = amount - balance; throw new InsufficientFundsException(needs); >> public double getBalance() < return balance; >public int getNumber() < return number; >> 

Следующая программа Bank демонстрирует вызов методов deposit() и withdraw() класса Checking.

// Название файла Bank.java public class Bank < public static void main(String [] args) < Checking c = new Checking(101); System.out.println("Депозит $300. "); c.deposit(300.00); try < System.out.println("\nСнятие $100. "); c.withdraw(100.00); System.out.println("\nСнятие $400. "); c.withdraw(400.00); >catch(InsufficientFundsException e) < System.out.println("Извините, но у Вас $" + e.getAmount()); e.printStackTrace(); >> > 

Скомпилируйте все три выше обозначенные файла и произведите запуск Bank. Вследствие этого будет получен следующий результат:

Депозит $300. Снятие $100. Снятие $400. Извините, но у Вас $200.0 InsufficientFundsException at Checking.withdraw(Checking.java:25) at Bank.main(Bank.java:13) 

Общие исключения

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

  • Исключения JVM — данная группа представлена исключениями/ошибками, которые вызываются непосредственно и логически со стороны JVM. Примеры: NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException.
  • Программные исключения — данные исключения вызываются непосредственно приложением либо программистами API. Примеры: IllegalArgumentException, IllegalStateException.

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

2. Коды возврата. Основная идея — в случае ошибки возвращать специальное значение, которое не может быть корректным. Например, если в методе есть операция деления, то придется проверять делитель на равенство нулю. Также проверим корректность аргументов a и b :

Double f(Double a, Double b) < if ((a == null) || (b == null)) < return null; > //. if (Math.abs(b) < EPS) < return null; > else < return a / b; > >

При вызове метода необходимо проверить возвращаемое значение:

Double d = f(a, b); if (d != null) < //. > else < //. >

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

3.Использовать флаг ошибки: при возникновении ошибки устанавливать флаг в соответствующее значение:

boolean error; Double f(Double a, Double b) < if ((a == null) || (b == null)) < error = true; return null; > //. if (Math.abs(b) < EPS) < error = true; return b; > else < return a / b; > >
error = false; Double d = f(a, b); if (error) < //. > else < //. >

Минусы такого подхода аналогичны минусам использования кодов возврата.

4.Можно вызвать метод обработки ошибки и возвращать то, что вернет этот метод.

Double f(Double a, Double b) < if ((a == null) || (b == null)) < return nullPointer(); > //. if (Math.abs(b) < EPS) < return divisionByZero(); > else < return a / b; > >

Но в таком случае не всегда возможно проверить корректность результата вызова основного метода.

5.В случае ошибки просто закрыть программу.

if (Math.abs(b) < EPS) < System.exit(0); return this; >

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

Исключения

В Java возможна обработка ошибок с помощью исключений:

Double f(Double a, Double b) < if ((a == null) || (b == null)) < throw new IllegalArgumentException("arguments of f() are null"); > //. return a / b; >

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

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

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

Таким образом, механизм обработки исключений содержит следующие операции:

  1. Создание объекта-исключения.
  2. Заполнение stack trace'а этого исключения.
  3. Stack unwinding (раскрутка стека) в поисках нужного обработчика.

Классификация исключений

Класс Java Throwable описывает все, что может быть брошено как исключение. Наследеники Throwable - Exception и Error - основные типы исключений. Также RuntimeException , унаследованный от Exception , является существенным классом.

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

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

Наследники класса Exception (кроме наслеников RuntimeException ) являются проверяемыми исключениями(checked exception). Как правило, это ошибки, возникшие по вине внешних обстоятельств или пользователя приложения – неправильно указали имя файла, например. Эти исключения должны обрабатываться в ходе работы программы, поэтому компилятор проверяет наличие обработчика или явного описания тех типов исключений, которые могут быть сгенерированы некоторым методом.

Все исключения, кроме классов Error и RuntimeException и их наследников, являются проверяемыми.

Error

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

RuntimeException

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

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

Чтобы сгенерировать исключение используется ключевое слово throw . Как и любой объект в Java, исключения создаются с помощью new .

if (t == null) < throw new NullPointerException("t = null"); >

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

Возможна ситуация, когда одно исключение становится причиной другого. Для этого существует механизм exception chaining. Практически у каждого класса исключения есть конструктор, принимающий в качестве параметра Throwable – причину исключительной ситуации. Если же такого конструктора нет, то у Throwable есть метод initCause(Throwable) , который можно вызвать один раз, и передать ему исключение-причину.

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

void f() throws InterruptedException, IOException < //. 

try-catch-finally

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

try < // Код, который может сгенерировать исключение >

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

try < // Код, который может сгенерировать исключение > catch(Type1 id1) < // Обработка исключения Type1 > catch(Type2 id2) < // Обработка исключения Type2 >

Сatch -блоки обрабатывают исключения, указанные в качестве аргумента. Тип аргумента должен быть классом, унаследованного от Throwable , или самим Throwable . Блок catch выполняется, если тип брошенного исключения является наследником типа аргумента и если это исключение не было обработано предыдущими блоками.

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

NB: Если JVM выйдет во время выполнения кода из try или catch , то finally -блок может не выполниться. Также, например, если поток выполняющий try или catch код остановлен, то блок finally может не выполниться, даже если приложение продолжает работать.

Блок finally удобен для закрытия файлов и освобождения любых других ресурсов. Код в блоке finally должен быть максимально простым. Если внутри блока finally будет брошено какое-либо исключение или просто встретится оператор return , брошенное в блоке try исключение (если таковое было брошено) будет забыто.

import java.io.IOException; public class ExceptionTest < public static void main(String[] args) < try < try < throw new Exception("a"); > finally < throw new IOException("b"); > > catch (IOException ex) < System.err.println(ex.getMessage()); > catch (Exception ex) < System.err.println(ex.getMessage()); > > >

После того, как было брошено первое исключение - new Exception("a") - будет выполнен блок finally , в котором будет брошено исключение new IOException("b") , именно оно будет поймано и обработано. Результатом его выполнения будет вывод в консоль b . Исходное исключение теряется.

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

При использовании нескольких потоков бывают ситуации, когда поток завершается из-за исключения. Для того, чтобы определить с каким именно, начиная с версии Java 5 существует интерфейс Thread.UncaughtExceptionHandler . Его реализацию можно установить нужному потоку с помощью метода setUncaughtExceptionHandler . Можно также установить обработчик по умолчанию с помощью статического метода Thread.setDefaultUncaughtExceptionHandler .

Интерфейс Thread.UncaughtExceptionHandler имеет единственный метод uncaughtException(Thread t, Throwable e) , в который передается экземпляр потока, завершившегося исключением, и экземпляр самого исключения. Когда поток завершается из-за непойманного исключения, JVM запрашивает у потока UncaughtExceptionHandler , используя метод Thread.getUncaughtExceptionHandler() , и вызвает метод обработчика – uncaughtException(Thread t, Throwable e) . Все исключения, брошенные этим методом, игнорируются JVM.

Информация об исключениях

  • getMessage() . Этот метод возвращает строку, которая была первым параметром при создании исключения;
  • getCause() возвращает исключение, которое стало причиной текущего исключения;
  • printStackTrace() печатает stack trace, который содержит информацию, с помощью которой можно определить причину исключения и место, где оно было брошено.
Exception in thread "main" java.lang.IllegalStateException: A book has a null property at com.example.myproject.Author.getBookIds(Author.java:38) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) Caused by: java.lang.NullPointerException at com.example.myproject.Book.getId(Book.java:22) at com.example.myproject.Author.getBookIds(Author.java:35)

Все методы выводятся в обратном порядке вызовов. В примере исключение IllegalStateException было брошено в методе getBookIds , который был вызван в main . "Caused by" означает, что исключение NullPointerException является причиной IllegalStateException .

Разработка исключений

Чтобы определить собственное проверяемое исключение, необходимо создать наследника класса java.lang.Exception . Желательно, чтобы у исключения был конструкор, которому можно передать сообщение:

public class FooException extends Exception < public FooException() < super(); > public FooException(String message) < super(message); > public FooException(String message, Throwable cause) < super(message, cause); > public FooException(Throwable cause) < super(cause); > >

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

  • обработка нескольких типов исключений в одном catch -блоке:

catch (IOException | SQLException ex)

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

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

  • Try с ресурсами позволяет прямо в try -блоке объявлять необходимые ресурсы, которые по завершению блока будут корректно закрыты (с помощью метода close() ). Любой объект реализующий java.lang.AutoCloseable может быть использован как ресурс.
static String readFirstLineFromFile(String path) throws IOException < try (BufferedReader br = new BufferedReader(new FileReader(path))) < return br.readLine(); > >

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

Можно объявлять несколько ресурсов, разделяя их точкой с запятой:

public static void viewTable(Connection con) throws SQLException < String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query)) < //Work with Statement and ResultSet > catch (SQLException e) < e.printStackTrace; >>

Во время закрытия ресурсов тоже может быть брошено исключение. В try-with-resources добавленна возможность хранения "подавленных" исключений, и брошенное try -блоком исключение имеет больший приоритет, чем исключения получившиеся во время закрытия. Получить последние можно вызовом метода getSuppressed() от исключения брошенного try -блоком.

  • Перебрасывание исключений с улучшенной проверкой соответствия типов.

Компилятор Java SE 7 тщательнее анализирует перебрасываемые исключения. Рассмотрим следующий пример:

static class FirstException extends Exception < >static class SecondException extends Exception < >public void rethrowException(String exceptionName) throws Exception < try < if ("First".equals(exceptionName)) < throw new FirstException(); > else < throw new SecondException(); > > catch (Exception ex) < throw e; > >

В примере try -блок может бросить либо FirstException , либо SecondException . В версиях до Java SE 7 невозможно указать эти исключения в декларации метода, потому что catch -блок перебрасывает исключение ex , тип которого - Exception .

В Java SE 7 вы можете указать, что метод rethrowException бросает только FirstException и SecondException . Компилятор определит, что исключение Exception ex могло возникнуть только в try -блоке, в котором может быть брошено FirstException или SecondException . Даже если тип параметра catch - Exception , компилятор определит, что это экземпляр либо FirstException , либо SecondException :

public void rethrowException(String exceptionName) throws FirstException, SecondException < try < // . > catch (Exception e) < throw e; > >

Если FirstException и SecondException не являются наследниками Exception , то необходимо указать и Exception в объявлении метода.

Примеры исключений

  • любая операция может бросить VirtualMachineError . Как правило это происходит в результате системных сбоев.
  • OutOfMemoryError . Приложение может бросить это исключение, если, например, не хватает места в куче, или не хватает памяти для того, чтобы создать стек нового потока.
  • IllegalArgumentException используется для того, чтобы избежать передачи некорректных значений аргументов. Например:
public void f(Object a) < if (a == null) < throw new IllegalArgumentException("a must not be null"); > >
  • IllegalStateException возникает в результате некорректного состояния объекта. Например, использование объекта перед тем как он будет инициализирован.

Гарантии безопасности

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

  • Отсутствие гарантий (no exceptional safety). Если было брошено исключение, то не гарантируется, что все ресурсы будут корректно закрыты и что объекты, методы которых бросили исключения, могут в дальнейшем использоваться. Пользователю придется пересоздавать все необходимые объекты и он не может быть уверен в том, что может переиспозовать те же самые ресурсы.
  • Отсутствие утечек (no-leak guarantee). Объект, даже если какой-нибудь его метод бросает исключение, освобождает все ресурсы или предоставляет способ сделать это.
  • Слабые гарантии (weak exceptional safety). Если объект бросил исключение, то он находится в корректном состоянии, и все инварианты сохранены. Рассмотрим пример:
class Interval < //invariant: left double left; double right; //. >

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

  • Сильные гарантии (strong exceptional safety). Если при выполнении операции возникает исключение, то это не должно оказать какого-либо влияния на состояние приложения. Состояние объектов должно быть таким же как и до вызовов методов.
  • Гарантия отсутствия исключений (no throw guarantee). Ни при каких обстоятельствах метод не должен генерировать исключения. В Java это невозможно, например, из-за того, что VirtualMachineError может произойти в любом месте, и это никак не зависит от кода. Кроме того, эту гарантию практически невозможно обеспечить в общем случае.

Источники

  • Обработка ошибок и исключения — Сайт Георгия Корнеева
  • Лекция Георгия Корнеева — Лекториум
  • The Java Tutorials. Lesson: Exceptions
  • Обработка исключений — Википедия
  • Throwable (Java Platform SE 7 ) — Oracle Documentation
  • try/catch/finally и исключения — www.skipy.ru

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

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