Глава 11. Исключения
Оглавление
Когда программа нарушает семантические ограничения языка программирования Java, виртуальная машина Java сигнализирует об этой ошибке программе как об исключении.
Пример такого нарушения — попытка обратиться к элементу массива за пределами его границ. Некоторые языки программирования и их реализации реагируют на такие ошибки, прерывая программу; другие языки позволяют реализации реагировать произвольным или непредсказуемым образом. Ни один из этих подходов не совместим с целями проектирования платформы Java SE: обеспечение переносимости и надёжности.
Вместо этого язык программирования Java определяет, что исключение будет выбрасываться при нарушении семантических ограничений и вызовет нелокальную передачу управления от места возникновения исключения к месту, которое может быть указано программистом.
Исключение считается выброшенным из точки его возникновения и перехваченным в точке, в которую передаётся управление.
Программы также могут явно выбрасывать исключения, используя throw операторы (§14.18).
Явное использование throw операторов предоставляет альтернативу устаревшему стилю обработки ошибок путём возврата необычных значений, таких как целое значение -1, где отрицательное значение обычно не ожидается. Опыт показывает, что такие необычные значения часто игнорируются или не проверяются вызывающими сторонами, что приводит к ненадёжным программам, демонстрирующим нежелательное поведение или и то, и другое.
Каждое исключение представлено экземпляром класса Throwable или одним из его подклассов (§11.1). Такой объект может использоваться для передачи информации из точки возникновения исключения обработчику, который его перехватывает. Обработчики устанавливаются с помощью catch блоков try операторов (§14.20).
Во время процесса выброса исключения виртуальная машина Java прерывает, по очереди, все выражения, операторы, вызовы методов и конструкторов, инициализаторы и выражения инициализации полей, которые были начаты, но не завершены в текущей нити. Этот процесс продолжается до тех пор, пока не будет найден обработчик, указывающий на то, что он обрабатывает это конкретное исключение, назвав класс исключения или суперкласс класса исключения (§11.2). Если такой обработчик не найден, то исключение может быть обработано одним из иерархии обработчиков необработанных исключений (§11.3) — таким образом, предпринимаются все усилия, чтобы не допустить оставление исключения необработанным.
Механизм исключений платформы Java SE интегрирован с её моделью синхронизации (§17.1), поэтому мониторы разблокируются при выполнении synchronized операторов (§14.19) и вызовах synchronized методов (§8.4.3.6, §15.12) прерываются.
Исключение представляется экземпляром класса Throwable (прямого подкласса Object) или одного из его подклассов.
Класс Throwable и все его подклассы вместе составляют классы исключений.
Классы Exception и Error являются прямыми подклассами Throwable:
-
Exceptionявляется суперклассом всех исключений, от которых обычные программы могут захотеть восстановиться.Класс
RuntimeExceptionявляется прямым подклассомException.RuntimeExceptionявляется суперклассом всех исключений, которые могут быть возбуждены по многим причинам во время вычисления выражений, но от которых всё ещё может быть возможно восстановление.Класс
RuntimeExceptionи все его подклассы вместе составляют классы исключений времени выполнения. -
Errorявляется суперклассом всех исключений, от которых обычные программы обычно не ожидают восстановления.Класс
Errorи все его подклассы вместе составляют классы ошибок.
Классы исключений, не требующих проверки – это классы исключений времени выполнения и классы ошибок.
Классы исключений, требующих проверки – это все классы исключений, кроме исключений, не требующих проверки. То есть, классы исключений, требующих проверки – это Throwable и все его подклассы, кроме RuntimeException и его подклассов, а также Error и его подклассов.
Программы могут использовать существующие классы исключений API платформы Java SE в операторах throw или определять дополнительные классы исключений как подклассы Throwable или любого из его подклассов, как это необходимо. Для использования проверки исключений во время компиляции (§11.2) обычно новые классы исключений определяются как классы исключений, требующих проверки, то есть как подклассы Exception, которые не являются подклассами RuntimeException.
Класс Error является отдельным подклассом Throwable, отличным от Exception в иерархии классов, чтобы позволить программам использовать конструкцию «} catch (Exception e)
{» (§11.2.3), чтобы перехватывать все исключения, от которых возможно восстановление, без перехвата ошибок, от которых восстановление обычно невозможно.
Обратите внимание, что подкласс Throwable не может быть обобщённым (§8.1.2).
Исключение возбуждается по одной из четырёх причин:
-
Был выполнен оператор
throw(§14.18). -
Был выполнен оператор
assert, и вычисление его булевого выражения вернуло значениеfalse(§14.10). -
Виртуальная машина Java синхронно обнаружила аномальное состояние выполнения, а именно:
-
вычисление выражения нарушает нормальную семантику языка программирования Java (§15.6), например, целочисленное деление на ноль.
-
произошла ошибка при загрузке, линковке или инициализации части программы (§12.2, §12.3, §12.4); в этом случае возбуждается экземпляр подкласса
LinkageError. -
внутренняя ошибка или ограничение ресурсов препятствует виртуальной машине Java выполнить семантику языка программирования Java; в этом случае возбуждается экземпляр подкласса
VirtualMachineError.
Эти исключения не возбуждаются в произвольной точке программы, а в точке, где они указаны как возможный результат вычисления выражения или выполнения оператора.
-
-
Произошло асинхронное исключение (§11.1.3).
Большинство исключений возникают синхронно в результате действия потока, в котором они происходят, и в точке программы, которая может привести к такому исключению. Асинхронное исключение, напротив, – это исключение, которое потенциально может произойти в любой точке выполнения программы.
Асинхронные исключения возникают только в результате внутренней ошибки или ограничения ресурсов в виртуальной машине Java, препятствующей её выполнению семантики языка программирования Java. Возбуждаемое асинхронное исключение является экземпляром подкласса VirtualMachineError.
Обратите внимание, что StackOverflowError, подкласс VirtualMachineError, может быть возбуждён синхронно вызовом метода (§15.12.4.5), а также асинхронно из-за выполнения метода native или ограничений ресурсов виртуальной машины Java. Аналогично, OutOfMemoryError, другой подкласс VirtualMachineError, может быть возбуждён синхронно во время создания экземпляра класса (§15.9.4, §12.5), создания массива (§15.10.2, §10.6), инициализации класса (§12.4.2) и преобразования в обертку (§5.1.7), а также асинхронно.
Платформа Java SE допускает небольшое, но ограниченное выполнение перед возбуждением асинхронного исключения.
Асинхронные исключения редки, но необходимо хорошо понимать их семантику, чтобы генерировать высококачественный машинный код.
Задержка, указанная выше, допускается, чтобы оптимизированный код мог обнаружить и возбудить эти исключения в точках, где их можно практично обработать, соблюдая семантику языка программирования Java. Простая реализация может проверять наличие асинхронных исключений в точке каждого перехода управления. Поскольку программа имеет конечный размер, это даёт ограничение на общую задержку при обнаружении асинхронного исключения. Поскольку между переходами управления не возникнет асинхронного исключения, генератор кода имеет некоторую гибкость в переупорядочении вычислений между переходами управления для повышения производительности. Рекомендована статья Polling Efficiently on Stock Hardware Марка Фелея, Proc. 1993 Conference on Functional Programming and Computer Architecture, Копенгаген, Дания, стр. 179-187.
Язык программирования Java требует, чтобы программа содержала обработчики исключений, проверяемых во время компиляции, которые могут возникнуть в результате выполнения метода или конструктора (§8.4.6, §8.8.5). Эта проверка на этапе компиляции наличия обработчиков исключений предназначена для уменьшения количества исключений, которые не обрабатываются должным образом. Для каждого проверяемого исключения, которое может быть результатом, в строке throws для метода или конструктора должна быть указана класс этого исключения или один из суперклассов класса этого исключения (§11.2.3).
Классы проверяемых исключений (§11.1.1), указанные в строке throws, являются частью соглашения между разработчиком и пользователем метода или конструктора. Строка throws переопределяемого метода не может указывать, что этот метод приведет к выбрасыванию какого-либо проверяемого исключения, которое переопределяемый метод не разрешен, согласно строке throws, выбрасывать (§8.4.8.3). Когда участвуют интерфейсы, несколько объявлений методов могут быть переопределены одним объявлением переопределения. В этом случае объявление переопределения должно содержать строку throws, которая совместима со всеми переопределенными объявлениями (§9.4.1).
Классы исключений, не проверяемых во время компиляции (§11.1.1), освобождаются от проверки на этапе компиляции.
Классы ошибок освобождаются, потому что они могут возникнуть во многих точках программы, и восстановление после них затруднено или невозможно. Программа, объявляющая такие исключения, будет перегружена, бесполезно. Сложные программы могут все же захотеть перехватывать и пытаться восстановиться от некоторых из этих условий.
Классы исключений времени выполнения освобождаются, потому что, по мнению разработчиков языка программирования Java, объявление таких исключений не будет существенно способствовать установлению правильности программ. Многие операции и конструкции языка программирования Java могут привести к исключениям во время выполнения. Информация, доступная компилятору Java, и уровень анализа, выполняемого компилятором, обычно недостаточны для установления того, что такие исключения во время выполнения не могут возникнуть, даже если это очевидно для программиста. Требование объявления таких классов исключений просто будет раздражать программистов.
Например, некоторый код может реализовывать циклическую структуру данных, которая по построению никогда не может включать ссылки null; программист может быть уверен, что NullPointerException не может произойти, но компилятору Java будет сложно это доказать. Технология теоретического доказательства, необходимая для установления таких глобальных свойств структур данных, выходит за рамки этой спецификации.
Мы говорим, что оператор или выражение может бросить класс исключения E, если, согласно правилам в §11.2.1 и §11.2.2, выполнение оператора или выражения может привести к тому, что будет брошено исключение класса E.
Мы говорим, что блок catch может перехватить его перехватываемые классы исключений:
-
Перехватываемый класс исключений однострочного блока
catch- это объявленный тип его параметра исключения (§14.20). -
Перехватываемые классы исключений многострочного блока
catch- это альтернативы в объединении, которые обозначают тип его параметра исключения.
Выражение создания экземпляра класса (§15.9) может бросить исключение класса E, если:
-
Выражение является квалифицированным выражением создания экземпляра класса, и квалифицирующее выражение может бросить E; или
-
Некоторые выражения из списка аргументов могут бросить E; или
-
E - это один из типов исключений типа вызова выбранного конструктора (§15.12.2.6); или
-
Выражение создания экземпляра класса включает ClassBody, и некоторые инициализаторы экземпляра или инициализаторы переменных экземпляра в ClassBody могут бросить E.
Выражение вызова метода (§15.12) может бросить исключение класса E, если:
-
Выражение вызова метода имеет вид Primary
.[TypeArguments] Identifier и выражение Primary может бросить E; или -
Некоторые выражения из списка аргументов могут бросить E; или
-
E - это один из типов исключений типа вызова выбранного метода (§15.12.2.6).
Выражение лямбда-выражения (§15.27) не может бросить классы исключений.
Выражение switch (§15.28) может бросить исключение класса E, если:
-
Выражение-селектор может бросить E; или
-
Некоторые выражение-правило переключения, блок правила переключения,
throwоператор или группа операторов с меткой в блоке переключения могут бросить E.
Для всех других типов выражений выражение может бросить исключение класса E, только если одно из его непосредственных подвыражений может бросить E.
Обратите внимание, что выражение ссылки на метод (§15.13) вида Primary :: [TypeArguments] Identifier может бросить исключение, если подвыражение Primary может бросить исключение. В отличие от этого, выражение лямбда-выражения ничего не может бросить и не имеет непосредственных подвыражений, на которых можно выполнить анализ исключений. Именно тело выражения лямбда-выражения, содержащее выражения и операторы, может бросать классы исключений.
Оператор §14.18, в котором выражение брошенного исключения имеет статический тип E и не является окончательным или фактически окончательным параметром исключения, может бросить исключение типа E или любой класс исключения, который может бросить выражение.
Например, оператор throw new
java.io.FileNotFoundException(); может бросить только исключение java.io.FileNotFoundException. Формально, он не «может бросить» подкласс или надкласс исключения java.io.FileNotFoundException.
Оператор throw, чье выражение исключения является окончательным или фактически окончательным параметром исключения блока catch C, может бросить исключение типа E, если:
-
E — это класс исключения, которое может бросить блок
tryоператораtry, который объявляет C; и -
E совместим с присваиванием с любым из классов исключений, которые можно перехватить в C; и
-
E не совместим с присваиванием ни с одним из классов исключений, которые можно перехватить в блоках
catch, расположенных слева от C в этом же оператореtry.
Оператор try (§14.20) может бросить исключение типа E, если выполняется хотя бы одно из следующих условий:
-
Блок
tryможет бросить исключение E, или выражение, используемое для инициализации ресурса (в оператореtry-с-ресурсами), может бросить исключение E, или автоматическое вызов методаclose()ресурса (в оператореtry-с-ресурсами) может бросить исключение E, и E не совместим с присваиванием ни с одним из классов перехватываемых исключений любого блокаcatchоператораtry, и либо блокfinallyотсутствует, либо блокfinallyможет завершиться нормально; или -
Некоторые блок
catchоператораtryможет бросить исключение E и либо блокfinallyотсутствует, либо блокfinallyможет завершиться нормально; или -
Присутствует блок
finallyи может бросить исключение E.
Оператор явного вызова конструктора (§8.8.7.1) может бросить исключение типа E, если выполняется хотя бы одно из следующих условий:
-
Некоторые выражение в списке параметров вызова конструктора может бросить исключение E; или
-
E определяется как класс исключения блока
throwsвызываемого конструктора (§15.12.2.6).
Оператор switch (§14.11) может бросить исключение типа E, если выполняется хотя бы одно из следующих условий:
-
Выражение селектора может бросить исключение E; или
-
Некоторые выражение, блок, оператор
throwили группа помеченных операторов в блоке switch могут бросить исключение E.
Любой другой оператор S может бросить исключение типа E, если выражение или оператор, непосредственно содержащийся в S, может бросить исключение E.
Если метод или тело конструктора может бросить класс исключения E, где E — проверяемое исключение и E не является подклассом какого-либо класса, объявленного в блоке throws метода или конструктора, то это ошибка компиляции.
Если тело лямбда-выражения может бросить класс исключения E, где E — проверяемое исключение и E не является подклассом какого-либо класса, объявленного в блоке throws типа функции, на который направлено лямбда-выражение, то это ошибка компиляции.
Ошибка компиляции, если инициализатор переменной класса (§8.3.2) или статический инициализатор (§8.7) именованного класса или интерфейса может бросить проверяемый класс исключения.
Ошибка компиляции, если инициализатор переменной экземпляра (§8.3.2) или инициализатор экземпляра (§8.6) именованного класса может бросить проверяемый класс исключения, если именованный класс не имеет хотя бы одного явно объявленного конструктора, и класс исключения или один из его предков явно объявлен в блоке throws каждого конструктора.
Обратите внимание, что ошибка компиляции не возникает, если инициализатор переменной экземпляра или инициализатор экземпляра анонимного класса (§15.9.5) может бросить исключение. В именованном классе программист отвечает за распространение информации о классах исключений, которые могут быть брошены инициализаторами, объявляя соответствующий блок throws в любом явном объявлении конструктора. Эта взаимосвязь между проверяемыми классами исключений, брошенными инициализаторами класса, и проверяемыми классами исключений, объявленными конструкторами класса, гарантируется для объявления анонимного класса, поскольку явные объявления конструкторов невозможны, и компилятор Java всегда генерирует конструктор с подходящим блоком throws для объявления анонимного класса, основываясь на проверяемых классах исключений, которые могут быть брошены его инициализаторами.
Ошибка компиляции, если блок catch может перехватить проверяемый класс исключения E1, но блок try, соответствующий блоку catch, не может бросить проверяемый класс исключения, являющийся подклассом или надклассом E1, за исключением случаев, когда E1 является Exception или надклассом Exception.
Ошибка компиляции, если блок catch может перехватить класс исключения E1, и предшествующий блок catch оператора try может перехватить E1 или его надкласс.
Компилятор Java должен выдать предупреждение, если блок catch может перехватить проверяемый класс исключения E1, а блок try, соответствующий блоку catch, может бросить проверяемый класс исключения E2, где E2 <: E1, и предшествующий блок catch оператора try может перехватить проверяемый класс исключения E3, где E2 <: E3 <: E1.
Пример 11.2.3-1. Перехват проверяемых исключений
import java.io.FileNotFoundException;
import java.io.IOException;
class StaticallyThrownExceptionsIncludeSubtypes {
public static void main(String[] args) {
try {
throw new FileNotFoundException();
} catch (IOException ioe) {
// "catch IOException" catches IOException
// and any subtype.
}
try {
throw new FileNotFoundException();
// Statement "can throw" FileNotFoundException.
// It is not the case that statement "can throw"
// a subtype or supertype of FileNotFoundException.
} catch (FileNotFoundException fnfe) {
// ... Handle exception ...
} catch (IOException ioe) {
// Legal, but compilers are encouraged to give
// warnings as of Java SE 7, because all subtypes of
// IOException that the try block "can throw" have
// already been caught by the prior catch clause.
}
try {
m();
// m's declaration says "throws IOException", so
// m "can throw" IOException. It is not the case
// that m "can throw" a subtype or supertype of
// IOException (e.g. Exception).
} catch (FileNotFoundException fnfe) {
// Legal, because the dynamic type of the exception
// might be FileNotFoundException.
} catch (IOException ioe) {
// Legal, because the dynamic type of the exception
// might be a different subtype of IOException.
} catch (Throwable t) {
// Can always catch Throwable.
}
}
static void m() throws IOException {
throw new FileNotFoundException();
}
}
По вышеуказанным правилам, каждый альтернативный блок в операторе с множественными catch блоками (§14.20) должен быть способен перехватить какой-либо класс исключения, брошенный блоком try и не перехваченный предыдущими catch блоками. Например, второй catch блок ниже вызовет ошибку компиляции, так как анализ исключений определит, что SubclassOfFoo уже перехвачен первым catch блоком:
try { ... }
catch (Foo f) { ... }
catch (Bar | SubclassOfFoo e) { ... }
При возникновении исключения (§14.18), управление передается от кода, вызвавшего исключение, к ближайшему динамически охватывающему catch блоку, если таковой имеется, оператора try (§14.20), который может обработать это исключение.
Оператор или выражение динамически охватываются блоком catch, если он находится внутри блока try оператора try, частью которого является блок catch, или если вызывающий оператор или выражение динамически охватывается блоком catch.
Вызывающий оператор или выражение определяется местом их расположения:
-
Если внутри метода, то вызывающим выражением является выражение вызова метода (§15.12), который был выполнен для вызова метода.
-
Если внутри конструктора, инициализатора экземпляра или инициализатора переменной экземпляра, то вызывающим выражением является выражение создания экземпляра класса (§15.9) или вызов метода
newInstance, который был выполнен для создания объекта. -
Если внутри статического инициализатора или инициализатора переменной
static, то вызывающим выражением является выражение, использующее класс или интерфейс для его инициализации (§12.4).
Способность конкретного блока catch обработать исключение определяется путем сравнения класса объекта, который был сброшен, с перехватываемыми классами исключений блока catch. Блок catch может обработать исключение, если один из его перехватываемых классов исключений является классом исключения или суперклассом класса исключения.
Аналогично, блок catch перехватит любой объект исключения, являющийся объектом класса instanceof (§15.20.2) одного из его перехватываемых классов исключений.
Передача управления, происходящая при возникновении исключения, вызывает прерывание выполнения выражений (§15.6) и операторов (§14.1), пока не будет встречен блок catch, способный обработать это исключение; затем выполнение продолжается выполнением блока этого блока catch. Код, вызвавший исключение, никогда не возобновляется.
Все исключения (синхронные и асинхронные) являются точными: при передаче управления все эффекты операторов, выполненных и выражений, вычисленных до точки, в которой сбрасывается исключение, должны быть выполнены. Никакие выражения, операторы или их части, которые встречаются после точки, в которой сбрасывается исключение, не могут быть выполнены.
Если оптимизированный код спекулятивно выполнил некоторые выражения или операторы, которые следуют за точкой возникновения исключения, такой код должен быть готов скрыть это спекулятивное выполнение от видимого пользователем состояния программы.
Если нет блока catch, способного обработать исключение, то текущий поток (поток, в котором возникло исключение) завершается. Перед завершением выполняются все блоки finally, а неперехваченное исключение обрабатывается в соответствии со следующими правилами:
-
Если у текущего потока установлен обработчик неперехваченных исключений, то этот обработчик выполняется.
-
В противном случае вызывается метод
uncaughtExceptionдля потокаThreadGroup, являющегося родителем текущего потока. Если потокThreadGroupи его родительскиеThreadGroupне переопределяютuncaughtException, то вызывается методuncaughtExceptionобработчика по умолчанию.
В ситуациях, когда желательно гарантировать, что один блок кода всегда выполняется после другого, даже если этот другой блок завершается внезапно, можно использовать оператор try с блоком finally (§14.20.2).
Если блок try или catch в операторе try-finally или try-catch-finally завершается внезапно, то блок finally выполняется во время распространения исключения, даже если в конечном итоге не будет найден соответствующий блок catch.
Если блок finally выполняется из-за внезапного завершения блока try, и сам блок finally завершается внезапно, то причина внезапного завершения блока try отбрасывается, и новая причина внезапного завершения распространяется оттуда.
Точные правила внезапного завершения и перехвата исключений подробно описаны в спецификации каждого оператора в §14 (Блоки, операторы и шаблоны) и для выражений в §15 (Выражения) (особенно §15.6).
Пример 11.3-1. Сброс и перехват исключений
Следующая программа объявляет класс исключений TestException. Метод main класса Test вызывает метод thrower четыре раза, что приводит к сбросу исключений в трех случаях из четырех. Оператор try в методе main перехватывает каждое исключение, сброшенное вызывающим методом. Независимо от того, завершается ли вызов thrower нормально или внезапно, выводится сообщение, описывающее произошедшее.
class TestException extends Exception {
TestException() { super(); }
TestException(String s) { super(s); }
}
class Test {
public static void main(String[] args) {
for (String arg : args) {
try {
thrower(arg);
System.out.println("Test \"" + arg +
"\" didn't throw an exception");
} catch (Exception e) {
System.out.println("Test \"" + arg +
"\" threw a " + e.getClass() +
"\n with message: " +
e.getMessage());
}
}
}
static int thrower(String s) throws TestException {
try {
if (s.equals("divide")) {
int i = 0;
return i/i;
}
if (s.equals("null")) {
s = null;
return s.length();
}
if (s.equals("test")) {
throw new TestException("Test message");
}
return 0;
} finally {
System.out.println("[thrower(\"" + s + "\") done]");
}
}
}
Если мы выполним программу, передав ей аргументы:
divide null not test
она выведет следующий результат:
[thrower("divide") done]
Test "divide" threw a class java.lang.ArithmeticException
with message: / by zero
[thrower("null") done]
Test "null" threw a class java.lang.NullPointerException
with message: null
[thrower("not") done]
Test "not" didn't throw an exception
[thrower("test") done]
Test "test" threw a class TestException
with message: Test message
Определение метода thrower должно содержать блок throws, так как он может сбрасывать экземпляры TestException, являющегося проверенным классом исключения (§11.1.1). Ошибка компиляции произойдет, если блок throws будет опущен.
Обратите внимание, что блок finally выполняется при каждом вызове thrower, независимо от того, возникает ли исключение или нет, как показано выводом "[thrower(...)
done]", который появляется при каждом вызове.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.