Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция
Скачать 25.04 Mb.
|
ГЛАВА Обработка исключений В этой главе рассматривается механизм обработки исключений Java. Исключение — это нештатная ситуация, возникающая вовремя выполнения последовательности кода. Другими словами, исключение — это ошибка времени выполнения. В языках программирования, которые не поддерживают обработки исключений, ошибки должны проверяться и обрабатываться вручную — как правило, за счет использования кодов ошибок и т.п. Этот подход как обременителен, таки чреват проблемами. Обработка исключений Java позволяет избежать этих проблем и, кроме того, переносит управление ошибками времени выполнения в объектно-ориентированный мир. Исключение Java представляет собой объект, который описывает исключительную (те. ошибочную) ситуацию, возникающую в части программного кода. Когда возникает такая ситуация, в вызвавшем ошибку методе создается и передается объект, который представляет исключение. Этот метод может либо обработать исключение самостоятельно, либо пропустить его. В обоих случаях в некоторой точке исключение перехватывается и обрабатывается Исключения могут создаваться системой времени выполнения Java либо могут быть созданы вручную вашим кодом. Исключения, которые передает Java, имеют отношение к фундаментальным ошибкам, которые нарушают правила языка Java либо ограничения системы выполнения Java. Исключения, созданные вручную, обычно применяются для сообщения о неких ошибках вызывающей стороне метода. Обработка исключений Java управляется пятью ключевыми словами t r y , c a t c h , th ro w , th ro w s и f i n a l l y . Если кратко, они работают следующим образом. Операторы программы, которые вы хотите отслеживать на предмет исключений, помещаются в блок t r y . Если исключение происходит в блоке t r y , оно создается и передается. Ваш код может перехватить исключение (используя блоки обработать его некоторым осмысленным способом. Системные исключения автоматически передаются системой времени выполнения Java. Чтобы передать исключение вручную, используется ключевое слово th ro w . Любое исключение, которое создается и передается внутри метода, должно быть указано в его интерфейсе ключевым словом th ro w s . Любой код, который в обязательном порядке должен быть выполнен после завершения блока t r y , помещается в блок f i n a l l y . Ниже показана общая форма блока обработки исключений { // блок кода, в котором отслеживаются ошибки } Основы обработки исключений 2 4 0 Часть I. Язык Java catch ( тип_исключения_1 exOb) { // обработчик исключений типа тип_исключения_1 } catch ( тип_исключения_2 ехОЬ) { // обработчик исключений типа тип_исключения_2 } I I . . . finally { // блок кода, который должен быть выполнен // после завершения блока Здесь тип исключения тип происходящего исключения. Последующий материал настоящей главы посвящен описанию применения этой программной структуры. На заметку В комплекте JDK 7 добавлена новая форма оператора try, обеспечивающего автоматическое управление ресурсами. Эта форма, называемая t r y -с-р есур сами описана в главе 13 в контексте управления файлами, поскольку файлы — это один из наиболее часто используемых ресурсов. Типы исключений Все типы исключений являются подклассами встроенного класса Trowable. То есть класс Trowable расположен на вершине иерархии классов исключений. Непосредственно подклассом в ней находятся два подкласса, которые делят все исключения на две отдельные ветви. Одну ветвь возглавляет класс Ехс ер t i on. Этот класс используется для исключительных условий, которые должна перехватывать пользовательская программа. Это также класс, от которого выбудете наследовать свои подклассы при создании собственных типов исключений. У класса Exception имеется важный подкласс по имени Runt imeException. Исключения этого типа автоматически определяются для программ, которые выпишете, и включают такие ошибки, как деление на нуль и ошибочная индексация массивов. Другая ветвь начинается с класса Error, определяющего исключения, возникновение которых не ожидается при нормальном выполнении программы. Исключения типа Error используются системой времени выполнения Java для обозначения ошибок, происходящих внутри самой среды. Примером такой ошибки может служить переполнение стека. В этой главе не рассматриваются исключения типа Error, поскольку они обычно создаются в ответ на катастрофические сбои, которые не могут быть обработаны вашей программой. Необработанные исключения Прежде чем вы узнаете, как обрабатывать исключения в своей программе, полезно будет посмотреть, что происходит, когда вы не обрабатываете их. Следующая небольшая программа представляет пример, который намеренно вызывает ошибку деления на нуль ЕхсО { public static void main(String a r g s []) { int d = 0; int a = 42 / d; } } Глава 10. Обработка исключений 4 Рис. 10.1. Высоко ур о в нев о е представление иерархии исключений Когда система времени выполнения Java обнаруживает попытку деления на нуль, она создает новый объект исключения, а затем передает его. Это прерывает выполнение класса ЕхсО, поскольку, как только исключение передано, оно должно быть перехвачено обработчиком исключений, который должен немедленно с ним что-то сделать. В данном примере мы не применили никакого собственного обработчика исключений, поэтому исключение перехватывается стандартным обработчиком, предоставленным системой времени выполнения Java. Любое исключение, которое не перехвачено вашей программой, в конечном итоге будет перехвачено и обработано этим стандартным обработчиком. Стандартный обработчик отображает строку, описывающую исключение, выводит трассировку стека от точки возникновения исключения и прерывает программу. Ниже приведен пример исключения, созданного представленным выше кодом / by zero at Обратите внимание на то, что имя класса ЕхсО, имя метода main, имя файла ExcO . java и номер строки 4 включены в трассировку стека. Также нужно обратить внимание на то, что переданное исключение является подклассом класса Exception, по имени Arithmet icExcept ion, который более точно описывает тип возникшей ошибки. Как будет показано далее в настоящей главе, Java применяет несколько встроенных типов исключений, соответствующих разным типам ошибок времени выполнения, которые могут быть созданы. Трассировка стека всегда покажет последовательность вызовов методов, которая привела к ошибке. Например, вот другая версия предыдущей программы, представляющая туже ошибку, нов методе, отдельном от метода main (). class Excl { static void subroutine() { int d = 0; int a = 10 / d; } public static void main(String a r g s []) { E x c l Результирующая трассировка стека стандартного обработчика исключений показывает весь стек вызовов 2 4 Часть I. Язык Java java.lang.ArithmeticException: / by zero at E x c l .subroutine(Excl.java:4) at Как видите, в нижней части стека находится строка 7 метода main () , в которой расположен вызов метода subroutine () , породивший исключение в строке 4. Трассировка стека достаточно удобна для отладки, поскольку показывает всю последовательность вызовов, приведших к ошибке. Использование блоков t r y и Хотя стандартный обработчик исключений, который предоставляет система времени выполнения Java, удобен для отладки, обычно вы захотите обрабатывать исключения самостоятельно. Это дает два существенных преимущества. Во- первых, вы получаете возможность исправить ошибку. Во-вторых, предотвращается автоматическое прерывание выполнения программы. Большинство пользователей будут недовольны (и это как минимум, если ваша программа будет останавливаться и выводить трассировку стека всякий раз при возникновении ошибки. К счастью, предотвратить это достаточно просто. Чтобы противостоять этому и обрабатывать ошибки времени выполнения, нужно просто поместить код, который вы хотите наблюдать, внутрь блока try. Непосредственно за блоком try следует включить конструкцию catch, которая задает тип перехватываемого исключения. Чтобы проиллюстрировать, насколько это просто делается, в следующую программу включен блок try с конструкцией catch, который обрабатывает исключение ArithmeticException, создаваемое в результате попытки деления на нуль Ехс2 { public static void main(String a r g s []) { int d, a; try { // Мониторинг блока кода d = 0; a = 42 / Это не будет выведено catch (ArithmeticException e) { // перехват ошибки деления на нуль Деление на нуль."); } System.out.printIn("После оператора Эта программа создает следующий вывод. Деление на нуль. После оператора Обратите внимание на то, что вызов метода println () внутри блока try никогда не будет выполняться. Как только исключение передано, управление передается из блока try в блок catch. То есть строка Это не будет выведено не отображается. После того как блок catch будет выполнен, управление передается на строку программы, следующую за всем блоком Операторы и catch составляют единый узел. Область действия блока catch не распространяется нате операторы, которые идут перед оператором try. Оператор catch не может перехватить исключение, переданное другим оператором try кроме случаев вложенных конструкций try, которые будут описа- Глава 10. Обработка исключений 4 3 ны ниже. Операторы, которые защищены блоком t r y , должны быть заключены в фигурные скобки (те. они должны находиться внутри блока. Вы не можете применить оператор t r y к отдельному оператору программы. Целью правильно построенных операторов c a t c h является разрешение исключительных ситуаций и продолжение работы, как если бы ошибки вообще не случались. Например, в следующей программе каждая итерация цикла f o r получает два случайных числа. Эти два числа делятся одно на другое, а результат используется для деления значения 12345. Окончательный результат помещается в переменную а. Если какая-либо из операций деления вызывает ошибку деления на нуль, эта ошибка перехватывается, значение переменной а устанавливается равными выполнение программы продолжается Обработка исключения с продолжением работы import java.u t i l .Random; class HandleError { public static void main(String a r g s []) { int a=0, b=0, c=0 ; Random r = new R a ndom(); for(int i=0; i<32000; i++) { try { b = r.nextInt(); с = r .nextInt(); a = 12345 / (b/c); } catch (ArithmeticException e) Деление на ноль a = 0; // присвоить нуль и продолжить работу } System.out.println("а: " + а); } } Отображение описания исключения Класс T h ro w a b le переопределяет метод t o S t r i n g () (определенный в классе Ob j e c t ) таким образом, что он возвращает строку, содержащую описание исключения. Вы можете отобразить это описание с помощью метода p r i n t l n ( ), просто передав исключение в виде аргумента. Например, блок c a t c h из предыдущего примера может быть переписан следующим образом (ArithmeticException е) Исключение " + e ) ; a = 0; // присвоить нуль и продолжить работу } Когда эта версия подставляется в программу и программа запускается, каждая попытка деления на нуль отобразит следующее сообщение. Исключение: java.lang.ArithmeticException: / by Хотя в данном контексте это не имеет особого значения, все же возможность отобразить описание исключения в некоторых случаях полезна — в частности, когда вы экспериментируете с исключениями или занимаетесь отладкой 2 4 4 Часть I. Язык Множественные операторы c a t c В некоторых случаях один фрагмент кода может инициировать более одного исключения. Чтобы справиться с такой ситуацией, вы можете задать два или более операторов catch, каждый для перехвата своего типа исключений. Когда передается исключение, каждый оператор catch проверяется по порядку, и первый из них, тип которого соответствует исключению, выполняется. После того как выполнится один из операторов catch, все остальные пропускаются и выполнение программы продолжается с места, следующего за блоком try /cat ch. В следующем примере кода перехватываются два разных типа исключений Демонстрация применения множественных операторов catch, class MultipleCatches { public static void main(String a r g s []) { try { int a = args.length; System.out.println("a = " + a); int b = 42 / a; int c[] = { 1 }; с [42] = 99; } catch(ArithmeticException e) { System.o u t Деление на 0: " + e) ; } c a t c h (ArraylndexOutOfBoundsException e) { System.o u t .p r Ошибка индекса массива " + e ) После блока Эта программа вызовет исключение деления на нуль, если будет запущена без аргументов командной строки, поскольку в этом случае значение переменной а будет равно 0. Она выполнит деление, если будет передан аргумент командной строки, устанавливающий значение переменной а равным значению больше нуля. Нов этом случае будет создано исключение ArraylndexOutOf BoundsException, так как длина массива целых чисел с равна 1, в то время как программа пытается присвоить значение элементу массива с [ 42 Вот результаты запуска этой программы обоими способами MultipleCatches а = Деление на 0: java.lang.ArithmeticException: / by zero После блока try/catch. C :\>j ava MultipleCatches TestArg a = Ошибка индекса массива java.lang.ArraylndexOutOfBoundsException:42 После блока Когда используются множественные операторы catch, важно помнить, что обработчики подклассов исключений должны следовать перед любыми обработчиками их суперклассов. Дело в том, что оператор catch, который использует суперкласс, будет перехватывать все исключения этого суперкласса плюс всех его подклассов. То есть исключения подкласса никогда не будут обработаны, если вы попытаетесь их перехватить после обработчика его суперкласса. Более того, в Java недостижимый код является ошибкой. Например, рассмотрим следующую программу Эта программа содержит ошибку. Подкласс должен идти перед его суперклассом в последовательности операторов catch. В противном случае Глава 10. Обработка исключений 4 будет создан недоступный код, что приведет к ошибке при компиляции SuperSubCatch { public static void main(String a r g s []) { try { int a = 0; int b = 42 / a; } catch(Exception e) Общий перехват Exception."); } /* Этот catch никогда не будет достигнут, потому что ArithmeticException — это подклассе Ошибка — недостижимый код Это никогда не выполнится."); } } } Если вы попытаетесь скомпилировать эту программу, то получите сообщение об ошибке, говорящее о том, что второй оператор catch недостижим, потому что исключение уже перехвачено. Поскольку класс исключения Ar i t hme tic Exc ept i on — подкласс класса Exception, первый оператор catch обработает все ошибки, основанные на классе Exception, включая ArithmeticException. Это означает, что второй оператор catch не будет никогда выполнен. Чтобы исправить это, потребуется изменить порядок следования операторов Вложенные операторы t r Операторы могут быть вложенными. То есть оператор try может находиться внутри блока другого оператора try. Всякий раз, когда управление попадает в блок try, контекст этого исключения заталкивается в стек. Если вложенный оператор try не имеет обработчика catch для определенного исключения, стек раскручивается и проверяются на соответствие обработчики catch следующего внешнего) блока try. Это продолжается до тех пор, пока не будет найден подходящий оператор catch либо пока не будут проверены все уровни вложенных операторов try. Если подходящий оператор catch не будет найден, то исключение обработает система времени выполнения Java. Ниже приведен пример, в котором используются вложенные операторы try. // Пример вложенных операторов try. class NestTry { public static void main(String a r g s []) { try { int a = args.length; /* Если не указаны параметры командной строки следующий оператор создаст исключение деления на нуль. */ int b = 42 / а n t I n ("a = " + a); try { // вложенный блок try /* Если используется один аргумент командной строки, то исключение деления на нуль будет создано следующим кодом. */ if(a==l) а = а/(а-а); // деление на нуль 2 4 6 Часть I. Язык Java /* Если используется два аргумента командной строки то создается исключение выхода за пределы массива f асс создается исключение выхода // за пределы массива catch(ArraylndexOutOfBoundsException е) Индекс за пределами массива " + ее) Деление на 0: " + е ) Как видите, в этой программе один блок t r y вложен в другой. Программа работает следующим образом. Когда вы запускаете ее без аргументов командной строки, внешним блоком t r y создается исключение деления на нуль. Запуск программы с одним аргументом приводит к передаче исключения деления на нуль во вложенном блоке t r y Поскольку вложенный блок не обрабатывает это исключение, оно передается внешнему блоку t r y , который обрабатывает его. Если программе передается два аргумента командной строки, то создается исключение выхода индекса заграницы массива во внутреннем блоке t r y . Вот примеры запуска этой программы, иллюстрирующие каждый случай Деление на 0: java.lang.ArithmeticException: / by zero C:\>java NestTry One a = Деление на 0: java.lang.ArithmeticException: / by zero C:\>java NestTry One Two a = Индекс за пределами массива j a v a Вложение операторов t r y может быть не столь очевидным, если в процессе выполняются вызовы методов. Например, вы можете в пределах блока t r y вызывать метода внутри этого метода иметь еще один блок t r y . В этом случае блок t r y в теле метода находится внутри внешнего блока t r y , который вызывает этот метод. Ниже представлена версия предыдущей программы с блоком t r y , перемещенным внутрь метода n e s t t r y (). /* Операторы try могут быть неявно вложены в вызовах методов. */ class MethNestTry { static void nesttry(int a) { try { // вложенный блок try /* Если используется один аргумент командной строки то исключение деления на нуль будет создано следующим кодом. */ if(a==l) а = а/(а-а); // деление на нуль Если используется два аргумента командной строки то создается исключение выхода за пределы массива f ас Глава 10. Обработка исключений 4 с [42] = 99; // создается исключение выхода за // пределы массива catch(ArraylndexOutOfBoundsException е) Индекс за пределами массива " + е ) ; } } public static void main(String a r g s []) { try { int a = args.length; /* Если не указаны параметры командной строки следующий оператор создаст исключение деления на нуль а; System.out.println("а = " + а ) ; n e а catch(ArithmeticException е) Деление на 0: " + e ) Вывод этой программы идентичен предыдущему примеру. Оператор t h r o До сих пор мы перехватывали только те исключения, которые передавала система времени выполнения Java. Однако существует возможность передавать исключения из ваших программ явным образом, используя оператор throw. Его общая форма показана ниже hrow экземпляр hrow ab lе ; Здесь экземпляр_ТЬго1л?аЬ1е должен быть объектом класса Throwable либо подклассом класса Throwable. Элементарные типы, такие как int или char, как и классы, отличные от класса Throwable, например классы String и Obj ect, не могут быть использованы для исключений. Существует два способа получить объект класса Throwable: с использованием параметра в операторе catch либо за счет создания объекта оператором Поток выполнения останавливается непосредственно после оператора throw — любые последующие операторы не выполняются. Обнаруживается ближайший закрытый блок try, имеющий оператор catch соответствующего исключению типа. Если соответствие найдено, управление передается этому оператору. Если же нет, проверяется следующий внешний блок t г у и т.д. Если не находится подходящего по типу оператора catch, то стандартный обработчик исключений прерывает программу и выводит трассировку стека. Ниже приведен пример программы, создающей и передающей исключение. Обработчик, который перехватывает его, повторно передает его для внешнего обработчика Демонстрация применения throw, class ThrowDemo { static void demoproc() { try { throw new NullPointerException("demo"); } catch(NullPointerException e) Перехвачено внутри demoproc."); 2 4 Часть I. Язык Java throw e; // повторно передать исключение static void main(String a r g s []) { try { demoproc(); } catch (NullPomterException e) { System.out.p r Повторный перехват " + e ) Эта программа получает две возможности обработки одной и той же ошибки. Сначала метод main () устанавливает контекст исключения, затем вызывает метод demoproc () , который устанавливает другой контекст обработки исключения и немедленно передает новый экземпляр исключения Null Point erExcept ion, который перехватывается в следующей строке. Затем исключение передается повторно. Ниже показан результирующий вывод. Перехвачено внутри Повторный перехват java.lang.NullPointerException: Эта программа также демонстрирует, как создавать собственные объекты стандартных исключений Java. Обратите внимание наследующую строку new Здесь оператор new используется для создания экземпляра исключения Null PointerException. Многие из встроенных исключений времени выполнения Java имеют, по меньшей мере, два конструктора без параметров и со строковым параметром. Когда применяется вторая форма, аргумент указывает строку, описывающую исключение. Эта строка отображается, когда объект используется в качестве аргумента методов print () или println () . Она также может быть получена вызовом метода getMessage () , который определен в классе Оператор Если метод может породить исключение, которое он сам не обрабатывает, он должен задать это поведение так, чтобы вызывающий его код мог позаботиться об этом исключении. Для этого к объявлению метода добавляется конструкция throws. Конструкция throws перечисляет типы исключений, которые метод может передавать. Это необходимо для всех исключений, кроме имеющих тип Error, RuntimeException либо их подклассов. Все остальные исключения, которые может передавать метод, должны быть объявлены в конструкции throws. Если этого не сделать, получится ошибка вовремя компиляции. Вот общая форма объявления метода, которая включает оператор тип имя_метода (список_параметров) throws список_исключений { // тело метода } Здесь список исключений это разделенный запятыми список исключений, которые метод может передать. Ниже представлен пример неправильной программы, пытающейся передать исключение, которое сама она не перехватывает. Поскольку в программе не указан оператор throws для отображения этого факта, такая программа не компилируется Эта программа содержит ошибку и потому не компилируется class ThrowsDemo { Глава 10. Обработка исключений 4 9 static void t h r o w O n e () { S y s t e m . o u t . p r i n t l n (Внутри t h r o w O n e ."); throw new I l l e g a l A c c e s s E x c e p t i o n (де м о "); } public static void main( S t r i n g args[]) { t h r o w O n e (Чтобы откомпилировать этот пример, нужно внести в него два изменения. Во-первых, следует объявить, что метод throw O ne () передает исключение I l l e g a l A c c e s s E x c e p t i o n . Во-вторых, метод m a i n ( ) должен определять блок t r y / c a t c h , который перехватит это исключение. Исправленный пример выглядит следующим образом Теперь код корректен class ThrowsDemo { static void t h r o w O n e () throws I l l e g alAccessException { S y s t e m . o u t . p r i n t l n (Внутри t h r o w O n e ."); throw new I l l e g a l A c c e s s E x c e p t i o n ("de m o " ); } public static v o i d ma i n ( S t r i n g a r g s []) { try { t h r o w O n e (); } catch (IllegalAccessException e) { S y s t e m . o u t .p r i n t l n (Перехвачено " + e ) Вот результат, полученный при запуске этой программы. Внутри Перехвачено E x c e p t i o n : Оператор f i n a l l Когда исключение передано, выполнение метода направляется по нелинейному пути, изменяющему нормальный поток управления внутри метода. В зависимости оттого, как написан метод, существует даже возможность преждевременного возврата управления. В некоторых методах это может служить причиной серьезных проблем. Например, если метод при входе открывает файл и закрывает его при выходе, вероятно, вы не захотите, чтобы выполнение кода, закрывающего файл, было пропущено из-за применения механизма обработки исключений. Ключевое слово f i n a l l y предназначено для того, чтобы справиться с такой ситуацией. Ключевое слово f i n a l l y создает блок кода, который будет выполнен после завершения блока t r y / c a t c h , но перед кодом, следующим за ним. Блок f i n a l l y выполняется независимо оттого, передано исключение или нет. Если исключение передано, блок f i n a l l y выполняется, даже если ни один оператор c a t c h этому исключению не соответствует. В любой момент, когда метод собирается возвратить управление вызывающему коду изнутри блока t r y / c a t c h (из-за необработанного исключения или явным применением оператора r e t u r n ) , блок f i n a l l y будет выполнен перед возвратом управления из метода. Это может быть удобно для закрытия файловых дескрипторов либо освобождения других ресурсов, которые были получены вначале метода и должны быть освобождены перед возвратом. Оператор необязателен. Однако каждый оператор t r y требует наличия, по крайней мере, одного оператора c a t c h или f i n a l l y . Ниже приведен пример программы, которая показывает три метода, возвращающих управление разными способами, но ни один из них не пропускает выполнения блока f i n a l у . 2 5 Часть I. Язык Java class FinallyDemo { // Передает исключение из метода static void p r o c A () { try внутри procA"); throw new RuntimeException("демо"); } finally блок finally procA"); } } // Возврат управления в блоке try. static void p r o c B () { try внутри procB"); return; } finally блок finally procB"); } } // Нормальное выполнение блока try. static void p r o c C () { try внутри procC"); } finally блок finally procC"); } } public static void main(String a r g s []) { try { p r o c A (); } catch (Exception e) { System.out.p r i Исключение перехвачено r o c B (); p r o c C (В этом примере метод procA () преждевременно прерывает выполнение в блоке try, передавая исключение. Блоку все равно выполняется. В методе procB () возврат управления осуществляется в блоке try оператором return. Блок finally выполняется перед возвратом из метода procB (). В методе procC () блок try выполняется нормально, без ошибок. Однако блок finally выполняется все равно. Помните! Если блок f i n a l l y ассоциируется с блоком try, то блок f i n a l l y будет выполнен по завершении блока Вот результат, созданный предыдущей программой. внутри блок finally Исключение перехвачено внутри procB блок finally внутри блок finally procC Глава 10. Обработка исключений 5 Встроенные исключения Внутри стандартного пакета j ava. 1 ang определено несколько классов исключений. Некоторые из них использовались в предыдущих примерах. Большинство из этих исключений являются подклассами стандартного типа Runt imeExcept ion. Как уже объяснялось ранее, эти исключения ненужно включать в список throws метода — они называются непроверяемыми исключениями, поскольку компилятор не проверяет факт обработки или передачи методом таких исключений. Непроверяемые исключения, определенные в пакете java. lang, описаны в табл. 10.1. В табл. 10.2 перечислены те определенные в пакете java. lang исключения, которые должны быть включены в списки throws методов, которые могут их создавать и не обрабатывают самостоятельно. Они называются проверяемыми исключениями. В Java также определено несколько других типов исключений, имеющих отношение к библиотекам классов. Таблица 10.1. Непроверяемые подклассы класса R u n T i m e E x c e p t i o n , определенные в пакете j a v a . l a n g Исключение Описание ClassNotFoundException Класс не найден CloneNotSupportedException Попытка клонировать объект, который не реализует интерфейс Доступ к классу не разрешен InstantiationException Попытка создать объект абстрактного класса или интерфейса InterruptedException Один поток прерван другим потоком NoSuchFieldException Запрошенное полене существует NoSuchMethodException Запрошенный метод не существует ReflectiveOperationException Суперкласс исключений, связанных с рефлексией. Добавлено в JDK Таблица 10.2. Проверяемые исключения, определенные в пакете j a v a . l a n g Исключение Описание ArithmeticException Арифметическая ошибка, такая как деление на нуль Выход индекса заграницу массива ArrayStoreException Присваивание элементу массива объекта несовместимого типа ClassCastException Неверное приведение Present Except ion Попытка использования неопределенного значения перечисления I11еда1ArgumentЕхсерtion Неверный аргумент использован при вызове метода IllegalMonitorStateException Неверная операция мониторинга, такая как ожидание незаблокированного потока IllegalStateException Среда или приложение в некорректном состоянии 2 5 Часть I. Язык Окончание табл. Исключение Описание . ■ IllegalThreadStateException Запрошенная операция несовместима с текущим состоянием потока BoundsExcept ion Некоторый тип индекса вышел за допустимые пределы Создан массив отрицательного размера Неверное использование пустой ссылки Format Except ion Неверное преобразование строки в числовой формат Exception Попытка нарушения безопасности Bounds Попытка использования индекса за пределами строки Тип не найден Обнаружена неподдерживаемая операция Создание собственных подклассов исключений Хотя встроенные исключения Java обрабатывают большинство распространенных ошибок, вероятно, вам потребуется создать ваши собственные типы исключений для обработки ситуаций, специфичных для ваших приложений. Это достаточно просто сделать определите подкласс класса Exception который, разумеется, является подклассом класса Throwable). Ваши подклассы не обязаны реализовать что-либо — важно само их присутствие в системе типов, что позволит использовать их как исключения. Класс Exception не определяет никаких собственных методов. Естественно, он наследует методы, представленные в классе Throwable. Таким образом, всем исключениям, включая те, что вы создадите сами, доступны методы, определенные в классе Throwable. Все они перечислены в табл. 10.3. Вы можете также переопределить один или несколько этих методов в собственных классах исключений. Таблица 10.3. Методы, определенные в классе T h r o w a b l e Метод ___________ Описание \ / С i па 1 vo i d Добавляет исключение в список подавляемых исключений, связанный с вызывающим исключени- исклю чение) ем. Используется, прежде всего, с новым оператором try -с-ресурсам и (Добавлено bJDK 7) Throwable f illlnStackTrace () Возвращает объект класса Throwable, содержащий полную трассировку стека. Этот объект может быть передан повторно Throwable getCause () Возвращает исключение, лежащее под текущим исключением. Если такого нет, возвращается значение null String getLocalizedMessage () Возвращает локализованное описание исключения getMe s s a g e Q Возвращает описание исключения Глава 10. Обработка исключений 5 Окончание табл. 10.3 Метод Описание StackTraceElement[ getStackTrace() final Throwable[] getSup- p r e s s e d () Throwable initCause(Throwable исключение printStackTrace() void printStackTrace(PrintStream поток поток элементы [ ] ) String t o S t r i n g (Возвращает массив, содержащий трассировку стека и состоящий из элементов класса StackTraceElement. Метод в верхушке стека — это метод, который был вызван непосредственно перед тем, как было передано исключение. Этот метод содержится в первом элементе массива. Класс StackTraceElement дает вашей программе доступ к информации о каждом элементе в трассировке, такой как имя его метода Получает подавленные исключения, связанные с вызывающим исключением, и возвращает массив, который содержит результат. Подавленные исключения создаются, прежде всего, новым оператором t r y -с-ресурсам и (Добавлено bJDK7) Ассоциирует исключение с вызывающим исключением, как причиной этого вызывающего исключения. Возвращает ссылку на исключение Отображает трассировку стека Посылает трассировку стека в заданный поток Посылает трассировку стека в заданный поток Устанавливает трассировку стека для элементов Этот метод предназначен для специализированных приложений, а не для нормального приме нения Возвращает объект класса String, содержащий описание исключения. Этот метод вызывается из метода print In () при выводе объекта класса Throwable В следующем примере объявляется новый подкласс класса Exception, который затем используется для сообщения об ошибке в методе. Он переопределяет метод toString (), позволяя отобразить тщательно настроенное описание исключения Эта программа создает пользовательский class MyException extends Exception { private int detail; гип исключения a) { detail = a; } public String toString() return "MyException[' } class ExceptionDemo { static void compute(int a) throws MyException { Вызван compute(" + a + if(a > 10) ”) ") 2 5 4 Часть I. Язык Java throw new Нормальное завершение static void main(String a r g s []) { try { compute(1); compute(20); } catch (MyException e) Перехвачено " + e ) В этом примере определен подкласс МуЕхс ер i on класса Ехс ер t i on. Этот подкласс достаточно прост он имеет только конструктор и переопределенный метод toString () , отображающий значение исключения. Класс Except ionDemo определяет метод compute ( ), который передает объект исключения MyException. Это исключение передается, когда целочисленный параметр метода compute () принимает значение больше Метод main () устанавливает обработчик исключений MyException, затем вызывает метод compute () с правильным параметром (меньше 10) и неправильным, чтобы продемонстрировать оба пути выполнения кода. Ниже показан результат. Вызван Нормальное завершение Вызван Перехвачено MyException[2 Сцепленные исключения Начиная св подсистему исключений было добавлено такое средство, как сцепленное исключение (chained exception). Это средство позволяет ассоциировать с одним исключением другое, которое описывает причину появления первого. Например, представьте ситуацию, когда метод передает исключение ArithmeticException, поскольку была предпринята попытка деления на нуль. Однако реальная причина проблемы заключается в ошибке ввода-вывода, что приводит к неправильному делению. И хотя метод должен передать исключение ArithmeticException, так как произошла именно эта ошибка, вы можете также позволить вызывающему коду узнать о том, что в основе лежит ошибка ввода- вывода. Сцепленные исключения позволяют справиться с этой, а также с любой другой ситуацией, в которой присутствуют уровни исключений. Чтобы разрешить сцепленные исключения, в класс Throwable были добавлены два конструктора и два метода. Ниже показаны конструкторы причинаИскл) Throwable(String сообщение Throwable причинаИскл) В первой форме причинаИскл — это исключение, послужившее причиной текущего исключения. Таким образом, причинаИскл — это основная причина исключения. Вторая форма позволяет задать описание, а также определить причину исключения. Эти два конструктора были также добавлены в классы Error, Exception и RuntimeException. Глава 10. Обработка исключений 5 Для сцепления исключений в класс Throwable были добавлены методы get- Cause () и initCause (). Они представлены в табл. 10.3 и повторяются здесь при обсуждении getCause() Throwable initCause(Throwable причинаИскл) Метод getCause () возвращает исключение, являющееся причиной текущего исключения. Если такого исключения нет, возвращается значение null. Метод initCause () ассоциирует исключение причинаИскл с вызывающим исключением и возвращает ссылку на исключение. Таким образом, вы можете ассоциировать причину с исключением уже после того, как исключение было создано. Однако причина исключения может быть задана только однажды. Таким образом, вы можете вызвать метод initCause() только однажды для каждого объекта исключения. Кроме того, если причина исключения была установлена конструктором, то вы не можете установить ее снова, используя метод initCause(). Вообще, метод init Cause () используется для установки причин устаревших классов исключений, которые не поддерживают два описанных ранее дополнительных конструктора. Вот пример, демонстрирующий применение механизма сцепления исключений Демонстрация сцепленных исключений ChainExcDemo { static void demoproc() { // создать исключение NullPointerException e = new верхний уровень добавить причину е .initCause(new причина throw e; } public static void main(String a r g s []) { try { demoproc(); } catch(NullPointerException e) { // отобразить исключение верхнего уровня System.out.pri n t l n (Перехвачено " + e ) ; // отобразить исключение-причину System.out.println("Исходная причина " + e Эта программа создает следующий вывод. Перехвачено: j a v a .lang.NullPointerException: верхний уровень Исходная причина java.lang.ArithmeticException: причина В этом примере исключением верхнего уровня является Nul 1 Point erExcept ion. К нему добавлено исключение-причина — ArithmeticException. Когда исключение передается из метода demoproc () , оно перехватывается в методе main () . Затем исключение верхнего уровня отображается, аза ним следует лежащее в основе исключение, которое извлекается методом getCause (Сцепленные исключения могут вкладываться на любую глубину. То есть причина исключения может иметь собственную причину. Но имейте ввиду, что слишком 2 5 Часть I. Язык длинные цепочки сцепленных исключений, скорее всего, свидетельствуют о плохом дизайне. Сцепленные исключения не являются тем, что совершенно необходимо в каждой программе. Однако в случаях, когда информация об исключении-причине все- таки нужна, они представляют собой элегантное решение. Три новых средства исключений JDK В систему исключений комплекта JDK 7 добавлено три интересных и полезных средства. Первое автоматизирует процесс освобождения ресурса, такого как файл, когда он больше ненужен. Оно основано на расширенной форме оператора try, называемой оператором try с ресурсами и описывается в главе 13 при рассмотрении файлов. Второе новое средство называется мультиобработчик (multi-catch), а третье иногда упоминается как финальная повторная передача (final rethrow) или более точная повторная передача (more precise rethrow). Последние два средства описаны здесь. Мультиобработчик позволяет обработать несколько исключений в том же операторе catch. Вполне обычна ситуация, когда обработчики нескольких исключений используют одинаковый код, хотя они соответствуют разным исключениям. Теперь, вместо индивидуальной обработки исключения каждого типа, вы можете использовать один блок catch для обработки всех исключений без дублирования кода. Чтобы использовать мультиобработчик, отделите в операторе catch каждый тип исключения оператором OR. Каждый параметр мультиобработчика неявно финальный. (При желании вы можете явно указать ключевое слово f i n a l , ноне обходимости в этом нет) Поскольку каждый параметр мультиобработчика неявно финальный, ему не может быть присвоено новое значение. Вот оператор catch, использующий мультиобработчик для обработки исключений ArithmeticException и ArraylndexOutOfBoundsException. catch(ArithmeticException I ArraylndexOutOfBoundsException e) Следующая программа демонстрирует мультиобработчик в действии Демонстрация мультиобработчика JDK 7. class MultiCatch { public static void main(String a r g s []) { int a=10, b = 0 ; int vals[] = { 1, 2, 3 }; try { int result = a / b; // создает ArithmeticException // v a l s [10] = 1 9 ; // создает ArraylndexOutOfBoundsException // Этот оператор catch обработает оба исключения catch(ArithmeticException I ArraylndexOutOfBoundsException Обрабатывается исключение " + e ) ; } System.out.p r После мультиобработчика."); } } Программа создаст исключение ArithmeticException при попытке деления на нуль. Если вы закомментируете оператор деления и снимете комментарий со следующей строки, будет создано исключение ArrayIndexOutOf BoundsExcept ion. Оба исключения обрабатываются одним оператором Средство более точной повторной передачи ограничивает тип исключений, которые могут быть повторно переданы только теми проверяемыми исключениями Глава 10. Обработка исключений 5 которые связаны с блоком передачи t r y , не обрабатываются приведенным выше оператором c a t c h и являются подтипом или супертипом параметра. Хотя необходимость в этом средстве, возможно, возникнет и нечасто, теперь оно доступно для использования. При применении более точной повторной передачи, параметр оператора c a t c h должен фактически быть финальным, это значит, что ему либо не должно присваиваться новое значение в блоке c a tc h , либо он должен быть явно объявлен как f i n a l Использование исключений Обработка исключений представляет собой мощный механизм для управления сложными программами, которые имеют много динамических характеристик времени выполнения. Средства t r y , th ro w s и c a t c h следует считать простым способом обработки ошибок и необычных критических условий в вашей программной логике. В отличие отряда других языков, в которых для индикации сбоев используются коды ошибок, в языке Java применяются исключения. Иными словами, когда метод может завершиться сбоем, он передает исключение. Это более простой способ справиться с ошибочными ситуациями. Последнее замечание операторы управления исключениями Java не должны рассматриваться как общий способ нелокального ветвления. Если выбудете это делать, то только запутаете ваш код и усложните его сопровождение |