Глава 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, и уровень анализа, который выполняет компилятор, обычно недостаточны для установления того, что такие исключения во время выполнения не могут произойти, даже если это очевидно для программиста. Требование объявления таких классов исключений просто будет раздражать программистов.
Например, некоторый код может реализовать циклическую структуру данных, которая по построению никогда не может включать нулевые ссылки; программист может быть уверен, что 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 может сгенерировать класс исключения. В отличие от этого, лямбда-выражение ничего не может сгенерировать и не имеет непосредственных подвыражений, для которых можно выполнить анализ исключений. Именно тело лямбда-выражения, содержащее выражения и операторы, может генерировать классы исключений.
Оператор throw (§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оператораtry, расположенными слева от C.
Оператор 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.