Spec-Zone.ru › Java Language Specification 7

Глава 11. Исключения

Оглавление

11.1. Виды и причины исключений
11.1.1. Виды исключений
11.1.2. Причины исключений
11.1.3. Асинхронные исключения
11.2. Проверка исключений на этапе компиляции
11.2.1. Анализ выражений на исключения
11.2.2. Анализ операторов на исключения
11.2.3. Проверка на исключения
11.3. Обработка исключений во время выполнения

Когда программа нарушает семантические ограничения языка программирования 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) завершаются внезапно.

11.1. Виды и причины исключений

11.1.1. Виды исключений

Исключение представляется экземпляром класса Throwable (прямого подкласса Object) или одного из его подклассов.

Throwable и все его подклассы в совокупности являются классами исключений.

Обратите внимание, что подкласс Throwable не должен быть обобщённым (§8.1.2).

Классы Exception и Error являются прямыми подклассами Throwable.

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

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

Error и все его подклассы в совокупности являются классами ошибок.

Класс Error является отдельным подклассом Throwable, отличным от Exception в иерархии классов, чтобы позволить программам использовать идиому "} catch (Exception e) {" (§11.2.3) для перехвата всех исключений, из которых возможно восстановление, без перехвата ошибок, из которых восстановление обычно невозможно.

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

RuntimeException и все его подклассы в совокупности являются классами исключений времени выполнения.

Классы исключений, не требующих проверки — это классы исключений времени выполнения и классы ошибок.

Классы исключений, требующих проверки — это все классы исключений, кроме исключений, не требующих проверки. То есть классы исключений, требующих проверки, — это все подклассы Throwable, кроме RuntimeException и его подклассов, а также Error и его подклассов.

Программы могут использовать существующие классы исключений API платформы Java SE в операторах throw или определять дополнительные классы исключений как подклассы Throwable или любого из его подклассов, как это необходимо. Для того, чтобы воспользоваться проверкой обработчиков исключений во время компиляции (§11.2), обычно большинство новых классов исключений определяются как классы исключений, требующих проверки, то есть как подклассы Exception, которые не являются подклассами RuntimeException.

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

Исключение вызывается по одной из трёх причин:

  • Был выполнен оператор throw (§14.18).

  • Виртуальная машина Java синхронно обнаружила аномальное условие выполнения, а именно:

    • вычисление выражения нарушает обычную семантику языка программирования Java (§15.6), например, целочисленное деление на ноль.

    • происходит ошибка при загрузке, линковке или инициализации части программы (§12.2, §12.3, §12.4); в этом случае вызывается экземпляр подкласса LinkageError.

    • внутренняя ошибка или ограничение ресурсов препятствуют виртуальной машине Java в реализации семантики языка программирования Java; в этом случае вызывается экземпляр подкласса VirtualMethodError.

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

  • Произошло асинхронное исключение (§11.1.3).

11.1.3. Асинхронные исключения

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

Асинхронные исключения возникают только в результате:

  • Вызова метода (устаревшего) stop класса Thread или ThreadGroup.

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

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

    Обратите внимание, что StackOverflowError, подкласс VirtualMethodError, может быть вызван синхронно методом вызова (§15.12.4.5), а также асинхронно из-за выполнения метода native или ограничений ресурсов виртуальной машины Java. Аналогично, OutOfMemoryError, ещё один подкласс VirtualMethodError, может быть вызван синхронно во время создания объекта (§12.5), создания массива (§15.10.1, §10.6), инициализации класса (§12.4.2) и преобразования к объекту-оболочке (§5.1.7), а также асинхронно.

Платформа Java SE разрешает некоторое ограниченное выполнение кода перед вызовом асинхронного исключения.

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

Указанная выше задержка разрешается для оптимизированного кода, чтобы обнаружить и вызвать эти исключения в точках, где их можно практично обработать, соблюдая семантику языка программирования Java. Простая реализация может проверять наличие асинхронных исключений в каждой точке перехода управления. Поскольку размер программы конечен, это обеспечивает ограничение общей задержки обнаружения асинхронного исключения. Поскольку асинхронное исключение не возникает между переходами управления, генератор кода имеет некоторую гибкость для переупорядочения вычислений между переходами управления для повышения производительности. Статья Polling Efficiently on Stock Hardware Марка Филли (Marc Feeley), Proc. 1993 Conference on Functional Programming and Computer Architecture, Копенгаген, Дания, стр. 179-187, рекомендуется для дальнейшего изучения.

11.2. Проверка исключений на этапе компиляции

Язык программирования Java требует, чтобы программа содержала обработчики проверяемых исключений, которые могут возникнуть в результате выполнения метода или конструктора. Для каждого проверяемого исключения, которое может быть результатом, в блоке throws метода (§8.4.6) или конструктора (§8.8.5) необходимо указать класс этого исключения или один из его суперклассов (§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 может перехватить свой/свои перехватываемый/ые класс/ы исключений.

Класс перехватываемого исключения оператора try-catch без альтернатив — это объявленный тип его параметра исключения (§14.20).

Классы перехватываемых исключений оператора try-catch с альтернативами — это варианты в объединении, обозначающие тип его параметра исключения (§14.20).

11.2.1. Анализ исключений для выражений

Выражение создания экземпляра класса (§15.9) может выбросить класс исключения E, если:

  • Выражение является выражением квалифицированного создания экземпляра класса, и квалифицирующее выражение может выбросить E; или

  • Некоторое выражение в списке аргументов может выбросить E; или

  • E определяется как класс исключения из блока throws вызываемого конструктора (§15.12.2.6); или

  • Выражение создания экземпляра класса включает блок ClassBody, и некоторый блок инициализации экземпляра или выражение инициализации переменной экземпляра в блоке ClassBody может выбросить E.

Выражение вызова метода (§15.12) может выбросить класс исключения E, если:

  • Вызываемый метод имеет вид Primary.Identifier, и выражение Primary может выбросить E; или

  • Некоторое выражение в списке аргументов может выбросить E; или

  • E определяется как класс исключения из блока throws вызываемого метода (§15.12.2.6).

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

11.2.2. Анализ исключений в операторах

Оператор 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, объявленными слева от 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).

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

11.2.3. Проверка исключений

Ошибка компиляции, если тело метода или конструктора может бросить исключение класса E, когда E — это класс проверяемого исключения, и E не является подклассом какого-либо класса, объявленного в блоке throws метода или конструктора.

Ошибка компиляции, если инициализатор переменной класса (§8.3.2) или статический инициализатор (§8.7) именованного класса или интерфейса может бросить класс проверяемого исключения.

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

Обратите внимание, что при инициализации переменных экземпляра или инициализаторе экземпляра анонимного класса (§15.9.5) ошибка компиляции не возникает, даже если он может бросить исключение. В именованном классе программист отвечает за распространение информации о классах исключений, которые могут быть брошены инициализаторами, объявляя соответствующий блок throws для каждого явного объявления конструктора. Эта связь между классами проверяемых исключений, которые могут быть брошены инициализаторами класса, и классами проверяемых исключений, объявленных в конструкторах класса, неявно обеспечивается для объявления анонимного класса, потому что явные объявления конструкторов невозможны, и компилятор Java всегда генерирует конструктор с подходящим блоком throws для этого объявления анонимного класса, основываясь на классах проверяемых исключений, которые могут быть брошены его инициализаторами.

Ошибка компиляции, если блок catch может перехватить класс проверяемого исключения E1, и не выполняется условие, что блок try, соответствующий блоку catch, может бросить проверяемый класс исключения, который является подклассом или надклассом E1, за исключением случаев, когда E1 — это Exception или надкласс Exception.

Ошибка компиляции, если блок catch может перехватить (§11.2) проверяемый класс исключения E1, а предыдущий блок catch в том же операторе try может перехватить E1 или его надкласс.

Компилятор Java рекомендуется выдавать предупреждение, если блок catch может перехватить (§11.2) проверяемый класс исключения E1, и блок try, соответствующий блоку catch, может бросить проверяемый класс исключения E2, являющийся подклассом E1, а предыдущий блок catch в том же операторе try может перехватить проверяемый класс исключения E3, где E2 <: E3 <: E1.

Пример 11.2.3-1. Перехват проверяемых исключений

import java.io.*;

class StaticallyThrownExceptionsIncludeSubtypes {
    public static void main(String[] args) {
        try {
            throw new FileNotFoundException();
        } catch (IOException ioe) {
            // Legal in Java SE 6 and 7. "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) {
            // Legal in Java SE 6 and 7.
        } catch (IOException ioe) {
            // Legal in Java SE 6 and 7, but compilers are
            // encouraged to throw warnings as of Java SE 7.
            // All subtypes of IOException that the try block
            // can throw have already been caught.
        }

        try {
            m();
              // Method m's declaration says "throws IOException".
              // m "can throw" IOException. It is not the case
              // that m "can throw" a subtype or supertype of
              // IOException, e.g. Exception, though Exception or
              // a supertype of Exception can always be caught.
        } catch (FileNotFoundException fnfe) {
            // Legal in Java SE 6 and 7, because the dynamic type
            // of the IOException might be FileNotFoundException.
        } catch (IOException ioe) {
            // Legal in Java SE 6 and 7.
        } catch (Throwable t) {
            // Legal in Java SE 6 and 7.
        }
    }

    static void m() throws IOException {
        throw new FileNotFoundException();
    }
}

Согласно вышеприведённым правилам, каждый вариант в блоке с множественным catch (§14.20) должен быть способен перехватить какой-либо класс исключения, выброшенный блоком try и не перехваченный предыдущими блоками catch. Например, второй блок catch ниже вызовет ошибку компиляции, поскольку анализ исключений определит, что исключение SubclassOfFoo уже перехвачено первым блоком catch:

try { ... }
catch (Foo f) { ... }
catch (Bar | SubclassOfFoo e) { ... }

11.3. Обработка исключений во время выполнения

При возникновении исключения (§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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API