Ошибка деление на ноль джава

Есть простенький калькулятор, но никак не могу именно в нем сделать обработку исключения деления на ноль. Я не знаю, где ловить ArithmeticException. Я знаю, как обработать это исключение, но я не знаю как сделать это именно в моем калькуляторе.

case '/':
    try {
        result = num1 / num2;
    } catch (ArithmeticException e) {
        System.out.println("division by zero");
    }

Вот весь код:

public class Try {
    static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        int num1 = getInt();
        int num2 = getInt();
        char operation = getOperation();
        int result = calc(num1, num2, operation);
        System.out.println("Result of operation: " + result);
    }

    // Читання числа і перевірка на цілочисельність
    public static int getInt() {
        System.out.println("Enter number: ");
        int num;
        if (scanner.hasNextInt()) {
            num = scanner.nextInt();
        } else {
            System.out.println("Wrong number, try again.");
            scanner.next();
            num = getInt();
        }
        return num;
    }

    // Вибір операції, повідомити, якщо символ неправильний
    public static char getOperation() {
        System.out.println("Choose operation + - * / ");
        char operation;
        if (scanner.hasNext()) {
            operation = scanner.next().charAt(0);
        } else {
            System.out.println("Wrong operation, try again + - * /");
            scanner.next();
            operation = getOperation();
        }
        return operation;
    }

    //Обчислення, на основі введених чисел і вибраної операції
    public static int calc(int num1, int num2, char operation) {
        int result;
        switch (operation) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num1 - num2;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                try {
                    result = num1 / num2;
                } catch (ArithmeticException e) {
                    System.out.println("division by zero");
                }
                break;
            default:
                System.out.println("Wrong operation, try again.");
                result = calc(num1, num2, getOperation());
        }

        return result;
    }
}

#База знаний

  • 24 фев 2021

  • 13

Разбираемся, что такое исключения, зачем они нужны и как с ними работать.

 vlada_maestro / shutterstock

Мария Помазкина

Хлебом не корми — дай кому-нибудь про Java рассказать.

Из этой статьи вы узнаете:

  • что такое исключения (Exceptions);
  • как они возникают и чем отличаются от ошибок (Errors);
  • зачем нужна конструкция try-catch;
  • как разобраться в полученном исключении
  • и как вызвать исключение самому.

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

За примером далеко ходить не надо: сделаем то, что нам запрещали ещё в школе, — поделим на ноль.

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

public static void hereWillBeTrouble(int a, int b) {
    int oops = a / b;
    System.out.println(oops);
}

А получим вот что:

Это и есть исключение.

«Исключение» — сокращение от слов «исключительный случай». Это ситуация, в которой программа не может продолжить работу или её работа становится бессмысленной. Причём речь не только о нештатных ситуациях — исключения бывают и намеренными, такие разработчик вызывает сам.

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

У всех классов исключений есть общий класс-предок Throwable, от него наследуются классы Error и Exception, базовые для всех прочих.

Верхушка иерархии исключений Java

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

Error is the superclass of all the exceptions from which ordinary programs are not ordinarily expected to recover.

Что в переводе означает: ошибки (Error) — это такие исключительные ситуации, в которых восстанавливать работу программы не предполагается.

То есть это проблемы, которые нельзя (недопустимо) исправлять на ходу. Всё, что нам остаётся, — извиниться перед пользователем и впредь писать программы, где возникнет меньше подобных ситуаций. Например, не допускать такой глубокой рекурсии, как в коде ниже:

static void notGood() {
    System.out.println("Только не снова!");
    notGood();
}

При работе этого метода у нас возникнет ошибка: Exception in thread «main» java.lang.StackOverflowError — стек вызовов переполнился, так как мы не указали условие выхода из рекурсии.

А теперь об Exception. Эти исключительные ситуации возникают, если разработчик допустил невыполнимую операцию, не предусмотрел особые случаи в бизнес-логике программы (или сообщает о них с помощью исключений).

1. Невыполнимая операция

Мир не рухнул, как в случае с Error, просто Java не знает, что делать дальше. Как раз из этого разряда деление на ноль в начале статьи: и правда, какое значение тогда присвоить переменной oops?

Убедитесь сами, что исключение класса ArithmeticException наследуется как раз от Exception.

Стоит запомнить. В IntelliJ IDEA, чтобы увидеть положение класса в иерархии, выберите его и нажмите Ctrl + H (или на пункт Type Hierarchy в меню Navigate).

Другая частая ситуация — обращение к несуществующему элементу массива. Например, у нас в нём десять элементов, а мы пытаемся обратиться к одиннадцатому.

2. Особый случай в бизнес-логике программы

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

Или пользователь вводит дату начала некоторого периода и дату его окончания. Вторая дата не может быть раньше первой.

Или, допустим, у нас есть метод, который читает файл. Сам метод написан верно. Пользователь передал в него корректный путь. Только вот у этого работника нет права читать этот файл (его роль и права обусловлены предметной областью). Что же тогда методу возвращать? Вернуть-то нечего, ведь метод не отработал. Самое очевидное решение — выдать исключение.

В дерево исключений мы ещё углубимся, а сейчас посмотрим, что и как с ними делают.

Простейший вариант — ничего; возникает исключение — программа просто прекращает работать.

Чтобы убедиться в этом, выполним код:

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

public static void hereWillBeTrouble(int a, int b) {
    System.out.println("Всё, что было до...");
    int oops = a / b;
    System.out.println(oops);
    System.out.println("Всё, что будет после...");
}

Так и есть: до деления на ноль код выполнялся, а после — нет.

Это интересно: когда возникает исключение, программисты выдают что-то вроде «код [вы]бросил исключение» или «код кинул исключение». А глагол таков потому, что все исключения — наследники класса Throwable, что значит «бросаемый» / «который можно бросить».

Второе, что можно делать с исключениями, — это их обрабатывать.

Для этого нужно заключить кусок кода, который может вызвать исключение, в конструкцию try-catch.

Как это работает: если в блоке try возникает исключение, которое указано в блоке catch, то исполнение блока try прервётся и выполнится код из блока catch.

Например:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble(int a, int b) {
    int oops;
    try {
        System.out.println("Всё, что было до...");
        oops = a / b;
        System.out.println(oops);
        System.out.println("Всё, что будет после...");
    } catch (ArithmeticException e) {
        System.out.println("Говорили же не делить на ноль!");
        oops = 0;
    }
    System.out.println("Метод отработал");
}

Разберём этот код.

Если блок try кинет исключение ArithmeticException, то управление перехватит блок catch, который выведет строку «Говорили же не делить на ноль!», а значение oops станет равным 0.

После этого программа продолжит работать как ни в чём не бывало: выполнится код после блока try-catch, который сообщит: «Метод отработал».

Проверьте сами: запустите код выше. Вызовите метод hereWillBeTrouble с любыми значениями аргументов кроме нулевого b. Если в блоке try не возникнет исключений, то его код выполнится целиком, а в блок catch мы даже не попадём.

Есть ещё и третий вариант — пробросить исключение наверх. Но об этом в следующей статье.

Вернёмся к первой картинке. Посмотрим, что нам сказала Java, когда произошло исключение:

Начинаем разбирать сверху вниз:

— это указание на поток, в котором произошло исключение. В нашей простой однопоточной программе это поток main.

— какое исключение брошено. У нас это ArithmeticException. А java.lang.ArithmeticException — полное название класса вместе с пакетом, в котором он размещается.

— весточка, которую принесло исключение. Дело в том, что одно и то же исключение нередко возникает по разным причинам. И тут мы видим стандартное пояснение «/ by zero» — из-за деления на ноль.

— это самое интересное: стектрейс.

Стектрейс (Stack trace) — это упорядоченный список методов, сквозь которые исключение пронырнуло.

У нас оно возникло в методе hereWillBeTrouble на 8-й строке в классе Main (номер строки и класс указаны в скобках синим). А этот метод, в свою очередь, вызван методом main на 3-й строке класса Main.

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

Советую закреплять теорию на практике. Поэтому вернитесь в блок про Error и вызовите метод notGood — увидите любопытный стектрейс.

Всё это время мы имели дело с исключением, которое бросает Java-машина — при делении на ноль. Но как вызвать исключение самим?

Раз исключение — это объект класса, то программисту всего-то и нужно, что создать объект с нужным классом исключения и бросить его с помощью оператора throw.

public static void main(String[] args) {
    hereWillBeTrouble(42, 0);
}

private static void hereWillBeTrouble(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("ты опять делишь на ноль?");
    }
    int oops = a / b;
    System.out.println(oops);
}

При создании большинства исключений первым параметром в конструктор можно передать сообщение — мы как раз сделали так выше.

А получим мы то же самое, что и в самом первом примере, только вместо стандартной фразы «/by zero» теперь выдаётся наш вопрос-пояснение «ты опять делишь на ноль?»:

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

Научитесь: Профессия Java-разработчик PRO
Узнать больше

Исключение (exception) — это ненормальная ситуация (термин «исключение» здесь следует понимать как «исключительная ситуация»), возникающая во время выполнения программного кода. Иными словами, исключение — это ошибка, возникающая во время выполнения программы (в runtime).

Исключение — это способ системы Java (в частности, JVM — виртуальной машины Java) сообщить вашей программе, что в коде произошла ошибка. К примеру, это может быть деление на ноль, попытка обратиться к массиву по несуществующему индексу, очень распространенная ошибка нулевого указателя (NullPointerException) — когда вы обращаетесь к ссылочной переменной, у которой значение равно null и так далее.

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

Обработка исключений (exception handling) — название объектно-ориентированной техники, которая пытается разрешить эти ошибки.

Программа в Java может сгенерировать различные исключения, например:

  • программа может пытаться прочитать файл из диска, но файл не существует;

  • программа может попытаться записать файл на диск, но диск заполнен или не отформатирован;

  • программа может попросить пользователя ввести данные, но пользователь ввел данные неверного типа;

  • программа может попытаться осуществить деление на ноль;

  • программа может попытаться обратиться к массиву по несуществующему индексу.

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

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

В Java все исключения представлены отдельными классами. Все классы исключений являются потомками класса Throwable. Так, если в программе возникнет исключительная ситуация, будет сгенерирован объект класса, соответствующего определенному типу исключения. У класса Throwable имеются два непосредственных подкласса: Exception и Error.

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

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

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


Так как в Java
ВСЁ ЯВЛЯЕТСЯ ОБЪЕКТОМ, то исключение тоже является объектом некоторого класса, который описывает исключительную ситуацию, возникающую в определенной части программного кода.

«Обработка исключений» работает следующим образом:

  • когда возникает исключительная ситуация, JVM генерирует (говорят, что JVM ВЫБРАСЫВАЕТ исключение, для описания этого процесса используется ключевое слово throw) объект исключения и передает его в метод, в котором произошло исключение;

  • вы можете перехватить исключение (используется ключевое слово catch), чтобы его каким-то образом обработать. Для этого, необходимо определить специальный блок кода, который называется обработчиком исключений, этот блок будет выполнен при возникновении исключения, код должен содержать реакцию на исключительную ситуацию;

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

Если вы не предусмотрите обработчик исключений, то исключение будет перехвачено стандартным обработчиком Java. Стандартный обработчик прекратит выполнение программы и выведет сообщение об ошибке.

Рассмотрим пример исключения и реакцию стандартного обработчика Java.

public static void main(String[] args) {

System.out.println(5 / 0);

Мы видим, что стандартный обработчик вывел в консоль сообщение об ошибке. Давайте разберемся с содержимым этого сообщения:

«C:Program FilesJavajdk1.8.0_60binjava»

Exception in thread «main» java.lang.ArithmeticException: / by zero

at ua.opu.Main.main(Main.java:6)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Process finished with exit code 1

Exception in thread «main» java.lang.ArithmeticException: / by zero

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

at ua.opu.Main.main(Main.java:6)

в каком классе, методе и строке произошло исключение. Используя эту информацию, мы можем найти ту строчку кода, которая привела к исключительной ситуации, и предпринять какие-то действия. Строки

at ua.opu.Main.main(Main.java:6)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

называются «трассировкой стека» (stack tracing). О каком стеке идет речь? Речь идет о стеке вызовов (call stack). Соответственно, эти строки означают последовательность вызванных методов, начиная от метода, в котором произошло исключение, заканчивая самым первым вызванным методом.

Для вызова методов в программе используется инструкция «call». Когда вы вызываете метод в программе, важно сохранить адрес следующей инструкции, чтобы, когда вызванный метод отработал, программа продолжила работу со следующей инструкции. Этот адрес нужно где-то хранить в памяти. Также перед вызовом необходимо сохранить аргументы функции, которые тоже необходимо где-то хранить.

Вся эта информация хранится в специальной структуре – стеке вызовов. Каждая запись в стеке вызовов называется кадром или фреймом (stack frame).

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

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

В Java, класс исключения служит для описания типа исключения. Например, класс NullPointerException описывает исключение нулевого указателя, а FileNotFoundException означает исключение, когда файл, с которым пытается работать приложение, не найден. Рассмотрим иерархию классов исключений:

На самом верхнем уровне расположен класс Throwable, который является базовым для всех исключений (как мы помним, JVM «выбрасывает» исключение», поэтому класс Throwable означает – то, что может «выбросить» JVM).

От класса Throwable наследуются классы Error и Exception. Среди подклассов Exception отдельно выделен класс RuntimeException, который играет важную роль в иерархии исключений.

В Java существует некоторая неопределенность насчет того – существует ли два или три вида исключений.

Если делить исключения на два вида, то это:

  1. 1.

    контролируемые исключения (checked exceptions) – подклассы класса Exception, кроме подкласса RuntimeException и его производных;

  2. 2.

    неконтролируемые исключения (unchecked exceptions) – класс Error с подклассами, а также класс RuntimeException и его производные;

В некоторых источниках класс Error и его подклассы выделяют в отдельный вид исключений — ошибки (errors).

Далее мы видим класс Error. Классы этой ветки составляют вид исключений, который можно обозначить как «ошибки» (errors). Ошибки представляют собой серьезные проблемы, которые не следует пытаться обработать в собственной программе, поскольку они связаны с проблемами уровня JVM.

На самом деле, вы конечно можете предпринять некоторые действия при возникновении ошибок, например, вывести сообщение для пользователя в удобном формате, выслать трассировку стека себе на почту, чтобы понять – что вообще произошло.

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

В качестве примеров «ошибок» можно привести: переполнение стека вызова (класс StackOverflowError); нехватка памяти в куче (класс OutOfMemoryError), вследствие чего JVM не может выделить память под новый объект и сборщик мусора не помогает; ошибка виртуальной машины, вследствие которой она не может работать дальше (класс VirtualMachineError) и так далее.

Несмотря на то, что в нашей программе мы никак не можем помочь этой проблеме, и приложение не может работать дальше (ну как может работать приложение, если стек вызовов переполнен или JVM не может дальше выполнять код?!); знание природы этих ошибок поможет вам предпринять некоторые действия, чтобы избежать этих ошибок в дальнейшем. Например, ошибки типа StackOverflowError и OutOfMemoryError могут быть следствием вашего некорректного кода.

Например, попробуем спровоцировать ошибку StackOverflowError

public static void main(String[] args) {

public static void methodA() {

private static void methodB() {

Получим такое сообщение об ошибке

Exception in thread «main» java.lang.StackOverflowError

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

at com.company.Main.methodB(Main.java:14)

at com.company.Main.methodA(Main.java:10)

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

Exception in thread «main» java.lang.OutOfMemoryError: Java heap space

at java.base/java.util.Arrays.copyOf(Arrays.java:3511)

at java.base/java.util.Arrays.copyOf(Arrays.java:3480)

at java.base/java.util.ArrayList.grow(ArrayList.java:237)

at java.base/java.util.ArrayList.grow(ArrayList.java:244)

at java.base/java.util.ArrayList.add(ArrayList.java:454)

at java.base/java.util.ArrayList.add(ArrayList.java:467)

at com.company.Main.main(Main.java:13)

Process finished with exit code 1

Ошибка VirtualMachineError может означать, что следует переустановить библиотеки Java.

В любом случае, следует относиться к типу Error не как к неизбежному злу и «воле богов», а просто как к сигналу к тому, что в вашем приложении что-то не так, или что-то не так с программным или аппаратным обеспечением, которое вы используете.

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

В классе Exception описаны исключения двух видов: контролируемые исключения (checked exceptions) и неконтролируемые исключения (unchecked exceptions).

Неконтролируемые исключения содержатся в подклассе RuntimeException и его наследниках. Контролируемые исключения содержатся в остальных подклассах Exception.

В чем разница между контролируемыми и неконтролируемыми исключениями, мы узнаем позже, а теперь рассмотрим вопрос – а как же именно нам обрабатывать исключения?

Обработка исключений в методе может выполняться двумя способами:

  1. 1.

    с помощью связки try-catch;

  2. 2.

    с помощью ключевого слова throws в сигнатуре метода.

Рассмотрим оба метода поподробнее:

Способ 1. Связка try-catch

Этот способ кратко можно описать следующим образом.

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

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

public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);

System.out.println(«Введите первое число: «);

String firstNumber = scanner.nextLine();

System.out.println(«Введите второе число: «);

String secondNumber = scanner.nextLine();

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

System.out.println(«Результат: « + (a + b));

Первое, что нам нужно определить – и что является главным при работе с исключениями, КАКАЯ ИНСТРУКЦИЯ МОЖЕТ ПРИВЕСТИ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ?

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

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

в которых происходит преобразование ввода пользователя в целое число (метод parseInt() преобразует цифры в строке в число).

Почему здесь может возникнуть исключение? Потому что пользователь может ввести не число, а просто какой-то текст и тогда непонятно – что записывать в переменную a или b. И да, действительно, если пользователь введет некорректное значение, возникнет исключение в методе Integer.parseInt().

Итак, что мы можем сделать. «Опасный код» нужно поместить в блок try{}

Обратите внимание на синтаксис блока try. В самом простом случае это просто ключевое слово try, после которого идут парные фигурные скобки. Внутри этих скобок и заключается «опасный» код, который может вызвать исключение. Сразу после блока try должен идти блок catch().

a = Integer.parseInt(firstNumber);

b = Integer.parseInt(secondNumber);

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

System.out.println(«Результат: « + (a + b));

Обратите внимание на синтаксис блока catch. После ключевого слова, в скобках описывается аргумент с именем e типа NumberFormatException.

Когда произойдет исключение, то система Java прервет выполнение инструкций в блоке try и передаст управление блоку catch и запишет в этот аргумент объект исключения, который сгенерировала Java-машина.

То есть, как только в блоке try возникнет исключение, то дальше инструкции в блоке try выполняться не будут! А сразу же начнут выполняться действия в блоке catch.

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

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

Например, если в нашем примере мы напишем код, который потенциально может выбросить исключение типа IOException, но не изменим блок catch

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

тогда обработчик не будет вызван и исключение будет обработано стандартным обработчиком Java.

Способ 2. Использование ключевого слова throws

Второй способ позволяет передать обязанность обработки исключения тому методу, который вызывает данный метод (а тот, в свою очередь может передать эту обязанность выше и т.д.).

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

public static void main(String[] args) {

int a = getNumberFromConsole(«Введите первое число»);

int b = getNumberFromConsole(«Введите второе число»);

System.out.println(«Результат: « + (a + b));

public static int getNumberFromConsole(String message) {

Scanner scanner = new Scanner(System.in);

System.out.print(message + «: «);

String s = scanner.nextLine();

return Integer.parseInt(s);

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

  1. 1.

    обработка исключений может происходить централизованно однотипным способом (например, показ окошка с сообщением и с определенным текстом);

  2. 2.

    это не входит в нашу компетенцию как программиста – обработкой исключений занимается другой программист;

  3. 3.

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

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

public static int getNumberFromConsole(String message) throws NumberFormatException {

Scanner scanner = new Scanner(System.in);

System.out.print(message + «: «);

String s = scanner.nextLine();

return Integer.parseInt(s);

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

public static void foo() throws NumberFormatException, ArithmeticException, IOException {

Тогда, в методе main мы должны написать примерно следующее

public static void main(String[] args) {

a = getNumberFromConsole(«Введите первое число»);

b = getNumberFromConsole(«Введите второе число»);

} catch (NumberFormatException e) {

// сохранить текст ошибки в лог

System.out.println(«Одно или оба значения некорректны!»);

System.out.println(«Результат: « + (a + b));

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

Отличия между контролируемыми и неконтролируемыми исключениями

Если вы вызываете метод, который выбрасывает checked исключение, то вы ОБЯЗАНЫ предусмотреть обработку возможного исключения, то есть связку try-catch.

Яркий пример checked исключения – класс IOException и его подклассы.

Рассмотрим пример – попробуем прочитать файл и построчно вывести его содержимое на экран консоли:

public static void main(String[] args) {

Path p = Paths.get(«c:\temp\file.txt»);

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Как мы видим, компилятор не хочет компилировать наш код. Чем же он недоволен? У нас в коде происходит вызов двух методов – статического метода Files.newBufferedReader() и обычного метода BufferedReader.readLine().

Если посмотреть на сигнатуры этих методов то можно увидеть, что оба этих метода выбрасывают исключения типа IOException. Этот тип исключения относится к checked-исключению и поэтому, если вы вызываете эти методы, компилятор ТРЕБУЕТ от вас предусмотреть блок catch, либо в самом вашем методе указать throws IOException и, таким образом, передать обязанность обрабатывать исключение другому методу, который будет вызывать ваш.

Таким образом, «оборачиваем» наш код в блок try и пишем блок catch.

public static void main(String[] args) {

Path p = Paths.get(«c:\temp\file.txt»);

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

Еще один способ — указать в сигнатуре метода, что он выбрасывает исключение типа IOException и переложить обязанность обработать ошибку в вызывающем коде

public static void main(String[] args) {

Path p = Paths.get(«c:\temp\file.txt»);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

public static void printFile(Path p) throws IOException {

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Eсли метод выбрасывает checked-исключение, то проверка на наличие catch-блока происходит на этапе компиляции. И вы обязаны предусмотреть обработку исключения для checked-исключения.

Что касается unchecked-исключения, то обязательной обработки исключения нет – вы можете оставить подобные ситуации без обработки.

Зачем необходимо наличие двух видов исключений?

В большинстве языков существует всего лишь один тип исключений – unchecked. Некоторые языки, например, C#, в свое время отказались от checked-исключений.

Во-первых, мы не можем сделать все исключения checked, т.к. очень многие операции могут генерировать исключения, и если каждый такой участок кода «оборачивать» в блок try-catch, то код получится слишком громоздким и нечитабельным.

С другой стороны, зачем нужно делать некоторые типы исключений checked? Почему просто не сделать все исключения unchecked и оставить решения об обработке исключений целиком на совести программиста?

В официальной документации написано, что unchecked-исключения – это те исключения, от которых программа «не может восстановиться», тогда как checked-исключения позволяют откатить некоторую операцию и повторить ее снова.

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

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

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

Дополнительно об исключениях

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

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

public static void foo() {

} catch (ArithmeticException e) {

// обработка арифметического исключения

} catch (IndexOutOfBoundsException e) {

// обработка выхода за пределы коллекции

} catch (IllegalArgumentException e) {

// обработка некорректного аргумента

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

Так как вы можете указать как точный класс, так и суперкласс, то если первым блоком будет блок для суперкласса – выполнится он. Например, исключение FileNotFoundException является подклассом IOException. И поэтому если вы первым поставите блок с IOException – он будет вызываться для всех подтипов исключений, в том числе и для FileNotFoundException и блок c FileNotFoundException никогда не выполнится.

public static void main(String[] args) {

Path p = Paths.get(«c:\temp\file.txt»);

} catch (IOException e) {

System.out.println(«Ошибка при чтении файла!»);

} catch (FileNotFoundException e) {

// данный блок никогда не будет вызван

public static void printFile(Path p) throws IOException {

BufferedReader reader = Files.newBufferedReader(p);

while ((line = reader.readLine()) != null) {

System.out.println(line);

Один блок для обработки нескольких типов исключений

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

public static void foo() {

} catch (ArithmeticException | IllegalArgumentException | IndexOutOfBoundsException e) {

// три типа исключений обрабатываются одинаково

Как мы видим, один блок catch используется для обработки и типа IOException и NullPointerException и NumberFormaException.

Вы можете использовать вложенные блоки try, которые могут помещаться в других блоках try. После вложенного блока try обязательно идет блок catch

public static void foo() {

} catch (IllegalArgumentException e) {

// обработка вложенного блока try

} catch (ArithmeticException e) {

Выбрасывание исключения с помощью ключевого слова throw

С помощью ключевого слова throw вы можете преднамеренно «выбросить» определенный тип исключения.

public static void foo(int a) {

throw new IllegalArgumentException(«Аргумент не может быть отрицательным!»);

Кроме блока try и catch существует специальный блок finally. Его отличительная особенность – он гарантированно отработает, вне зависимости от того, будет выброшено исключение в блоке try или нет. Как правило, блок finally используется для того, чтобы выполнить некоторые «завершающие» операции, которые могли быть инициированы в блоке try.

public static void foo(int a) {

FileOutputStream fout = null;

File file = new File(«file.txt»);

fout = new FileOutputStream(file);

} catch (IOException e) {

// обработка исключения при записи в файл

} catch (IOException e) {

При любом развитии события в блоке try, код в блоке finally отработает в любом случае.

Блок finally отработает, даже если в try-catch присутствует оператор return.

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

Блок try-with-resources является модификацией блока try. Данный блок позволяет автоматически закрывать ресурс после окончания работы блока try и является удобной альтернативой блоку finally.

public static void foo() {

Path p = Paths.get(«c:\temp\file.txt»);

try (BufferedReader reader = Files.newBufferedReader(p)) {

while ((line = reader.readLine()) != null)

System.out.println(line);

} catch (IOException e) {

Внутри скобок блока try объявляется один или несколько ресурсов, которые после отработки блока try-catch будут автоматически освобождены. Для этого объект ресурса должен реализовывать интерфейс java.lang.AutoCloseable.

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

Встроенные в Java исключения позволяют обрабатывать большинство распространенных ошибок. Тем не менее, вы можете создавать и обрабатывать собственные типы исключений. Для того, чтобы создать класс собственного исключения, достаточно определить как его произвольный от Exception или от RuntimeException (в зависимости от того, хотите ли вы использовать checked или unchecked – исключения).

Насчет создания рекомендуется придерживаться двух правил:

  1. 1.

    определитесь, исключения какого типа вы хотите использовать для собственных исключений (checked или unchecked) и старайтесь создавать исключения только этого типа;

  2. 2.

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

Плохие практики при обработке исключений

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

  1. 1.

    Указание в блоке catch объекта исключения типа Exception. Существует очень большой соблазн при создании блока catch указать тип исключения Exception и, таким образом, перехватывать все исключения, которые относятся к этому классу (а это все исключения, кроме системных ошибок). Делать так крайне не рекомендуется, т.к. вместо того чтобы решать проблему с исключениями, мы фактически игнорируем ее и просто реализуем некоторую «заглушку», чтобы приложение продолжило работу дальше. Кроме того, каждый тип исключения должен быть обработан своим определенным образом.

  2. 2.

    Помещение в блок try всего тела метода. Следующий плохой прием используется, когда программист не хочет разбираться с кодом, который вызывает исключение и просто, опять же, реализует «заглушку». Этот прием очень «хорошо» сочетается с первым приемом. В блок try должен помещаться только тот код, который потенциально может вызвать исключение, а не всё подряд, т.к. лень обрабатывать исключения нормально.

  3. 3.

    Игнорирование исключения. Следующий плохой прием состоит в том, что мы просто игнорируем исключение и оставляем блок catch пустым. Программа должна реагировать на исключения и должна информировать пользователя и разработчика о том, что что-то пошло не так. Безусловно, исключение это не повод тут же закрывать приложение, а попытаться повторить то действие, которое привело к исключению (например, повторно указать название файла, попытаться открыть базу данных через время и т.д.). В любом случае, когда приложение в ответ на ошибку никак не реагирует – не выдает сообщение, но и не делает того, чего от нее ожидали – это самый плохой вариант.

Глава 9

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

Основные навыки и понятия

  • Представление об иерархии исключений
  • Использование ключевых слов try и catch
  • Последствия неперехвата исключений
  • Применение нескольких операторов catch
  • Перехват исключений, генерируемых подклассами
  • Вложенные блоки try
  • Генерирование исключений
  • Представление о членах класса Throwable
  • Использование ключевого слова finally
  • Использование ключевого слова throws
  • Представление о исключениях, встроенные в Java
  • Создание специальных классов исключений

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

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

В Java определены стандартные исключения для наиболее часто встречающихся программных ошибок, в том числе деления на нуль или попытки открыть несуществующий файл. Для того чтобы обеспечить требуемую реакцию на конкретную ошибку, в программу следует включить соответствующий обработчик событий. Исключения широко применяются в библиотеке Java API.

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

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

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

Общее представление об обработке исключений

Для обработки исключений в Java предусмотрены пять ключевых слов: try, catch, throw, throws и finally. Они образуют единую подсистему, в которой использование одного ключевого слова почти всегда автоматически влечет за собой употребление другого. Каждое из упомянутых выше ключевых слов будет подробно рассмотрено далее в этой главе. Но прежде следует дать общее представление об их роли в процессе обработки исключений. Поэтому ниже поясняется вкратце, каким образом они действуют.

Операторы, в которых требуется отслеживать появление исключений, помещаются в блок try. Если в блоке try будет сгенерировано исключение, его можно перехватить и обработать нужным образом. Системные исключения генерируются автоматически. А для того чтобы сгенерировать исключение вручную, следует воспользоваться ключевым словом throw. Иногда возникает потребность обрабатывать исключения за пределами метода, в котором они возникают, и для этой цели служит ключевое слово throws. Если же некоторый фрагмент кода должен быть выполнен обязательно и независимо от того, возникнет исключение или нет, его следует поместить в блок finally.

Использование ключевых слов try и catch

Основными языковыми средствами обработки исключений являются ключевые слова try и catch. Они используются совместно. Это означает, что нельзя указать ключевое слово catch в коде, не указав ключевое слово try. Ниже приведена общая форма записи блоков try/catch, предназначенных для обработки исключений.

try {
    // Блок кода, в котором должны отслеживаться ошибки
}
catch (тип_исключения_1 объект_исключения) {
    // Обработчик исключения тип_исключения_1
}
catch (тип_исключения_2 объект_исключения) {
    // Обработчик исключения тип_исключения_2
}

В скобках, следующих за ключевым словом catch, указываются тип исключения и переменная, ссылающаяся на объект данного типа. Когда возникает исключение, оно перехватывается соответствующим оператором catch, обрабатывающим это исключение. Как следует из приведенной выше общей формы записи, с одним блоком try может быть связано несколько операторов catch. Тип исключения определяет, какой именно оператор catch будет выполняться. Так, если тип исключения соответствует типу оператора catch, то именно он и будет выполнен, а остальные операторы catch — пропущены. При перехвате исключения переменной, указанной в скобках после ключевого слова catch, присваивается ссылка на объект_исключения.

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

На заметку.
В версии JDK 7 внедрена новая форма оператора try, поддерживающая автоматическое управления ресурсами и называемая оператором try с ресурсами. Более подробно она описывается в главе 10 при рассмотрении потоков ввода-вывода, в том числе и тех, что связаны с файлами, поскольку потоки ввода-вывода относятся к числу ресурсов, наиболее употребительных в прикладных программах.

Простой пример обработки исключений

Рассмотрим простой пример, демонстрирующий перехват и обработку исключения. Как известно, попытка обратиться за границы массива приводит к ошибке, и виртуальная машина Java генерирует соответствующее исключение ArraylndexOutOf BoundsException. Ниже приведен код программы, в которой намеренно создаются условия для появления данного исключения, которое затем перехватывается.

// Демонстрация обработки исключений,
class ExcDemol {
    public static void main (String args[]) {
        int nums[] = new int[4];

        // Создание блока try.
        try {
            System.out.println("Before exception is generated.");

            // Попытка обратиться за границы массива.
            nums[7] = 10;
            System.out.println("this won't be displayed");
        }
        // Перехват исключения в связи с обращением за границы массива.
        catch (ArraylndexOutOfBoundsException exc) {
            System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

Результат выполнения данной программы выглядит следующим образом:

Before exception is generated.
Index out-of-bounds!
After catch statement.

Несмотря на всю простоту данного примера программы, он наглядно демонстрирует несколько важных особенностей обработки исключений. Во-первых, код, подлежащий проверке на наличие ошибок, помещается в блок try. И во-вторых, когда возникает исключение (в данном случае это происходит при попытке обратиться за границы массива), выполнение блока try прерывается и управление получает блок catch. Следовательно, явного обращения к блоку catch не происходит, но переход к нему осуществляется лишь при определенном условии, возникающем в ходе выполнения программы. Так, оператор вызова метода println(), следующий за выражением, в котором происходит обращение к несуществующему элементу массива, вообще не выполняется. По завершении блока catch выполнение программы продолжается с оператора, следующего за этим блоком. Таким образом, обработчик исключений предназначен для устранения программных ошибок, приводящих к исключительным ситуациям, а также для обеспечения нормального продолжения исполняемой программы.

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

на следующую строку кода:

Теперь исключение не возникнет и блок catch не выполнится.

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

/* Исключение может быть сгенерировано одним методом,
   а перехвачено другим. */

class ExcTest {
    // сгенерировать исключение
    static void genException()  {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // Здесь генерируется исключение в связи с
        // обращением за границы массива.
        nums[7] = 10;
        System.out.println("this won't be displayed");
    }
}

class ExcDemo2 {
    public static void main(String args[])  {
        try {
            ExcTest.genException() ;
        }
        //А здесь исключение перехватывается.
        catch (ArraylndexOutOfBoundsException exc) {
            System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

Выполнение этой версии программы дает такой же результат, как и при выполнении ее предыдущей версии. Этот результат приведен ниже.

Before exception is generated.
Index out-of-bounds!
After catch statement.

Метод genException() вызывается из блока try, и поэтому генерируемое, но не перехватываемое в нем исключение перехватывается далее в блоке catch в методе main(). Если бы метод genException() сам перехватывал исключение, оно вообще не дошло бы до метода main().

Последствия неперехвата исключений

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

// Обработка ошибки средствами виртуальной машины Java,
class NotHandled {
    public static void main(String args[]) {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // Попытка обращения за границы массива,
        nums[7] = 10;
    }
}

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

Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7 at NotHandled.main(NotHandled.java:9)

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

Как упоминалось выше, тип исключения должен соответствовать типу, указанному в операторе catch. В противном случае исключение не будет перехвачено. Так, в приведенном ниже примере программы делается попытка перехватить исключение, связанное с обращением за границы массива, с помощью оператора catch, в котором указан тип ArithmeticException еще одного встроенного в Java исключения. При неправильном обращении к массиву будет сгенерировано исключение ArraylndexOutOfBoundsException, не соответствующее типу, указанному в операторе catch. В результате программа будет завершена аварийно.

// Эта программа не будет работать нормально!
class ExcTypeMismatch {
    public static void main(String args[]) {
        int nums[] = new int[4];
        try {
        System.out.println("Before exception is generated.");
        // При выполнении следующего оператора возникает
        // исключение ArraylndexOutOfBoundsException
        nums[7] = 10;
        System.out.println("this won’t be displayed");
        }
        /* Исключение, связанное с обращением за границы массива,
        нельзя обработать с помощью оператора catch, в котором
        указан тип исключения ArithmeticException. */
        catch (ArithmeticException exc) {
        System.out.println("Index out-of-bounds!");
        }
        System.out.println("After catch statement.");
    }
}

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

Before exception is generated.
Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7
    at ExcTypeMismatch.main(ExcTypeMismatch.java:10)

Нетрудно заметить, что оператор catch, в котором указан тип исключения ArithmeticException, не может перехватить исключение ArraylndexOutOfBoundsException.

Обработка исключений — изящный способ устранения программных ошибок

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

// Изящная обработка исключения и продолжение выполнения программы,
class ExcDemo3 {
    public static void main(String args[])  {
        int numer[] = { 4, 8, 16, 32, 64, 128 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can't divide by Zero!");
            }
        }
    }
}

Результат выполнения данной программы выглядит следующим образом:

4 / 2 is 2
Can't divide by Zero!
16/ 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16

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

Применение нескольких операторов catch

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

// Применение нескольких операторов catch.  '
class ExcDemo4 {
    public static void main(String args[]) {
        // Здесь массив numer длиннее массива denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            // За блоком try следует несколько блоков catch подряд,
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can't divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
            }
        }
    }
}

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

4 / 2 is 2
Can't divide by Zero!
16 / 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16
No matching element found.
No matching element found.

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

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

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

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

Рассмотрим в качестве примера следующую программу

//В операторах catch исключения типа подкласса должны
// предшествовать исключениям типа суперкласса,
class ExcDemo5 {
    public static void main(String args[]) {
        // Здесь массив numer длиннее массива denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            // Перехват исключения от подкласса.
            catch (ArraylndexOutOfBoundsException exc) {
                System.out.println("No matching element found.");
            }
            // Перехват исключения от суперкласса.
            catch (Throwable exc) {
                System.out.println("Some exception occurred.");
            }
        }
    }
}

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

4 / 2 is 2
Some exception occurred.
16 / 4 is 4
32 / 4 is 8
Some exception occurred.
128 / 8 is 16
No matching element found.
No matching element found.

В данном случае оператор catch (Throwable) перехватывает все исключения, кроме ArraylndexOutOfBoundsException. Соблюдение правильного порядка следования операторов catch приобретает особое значение в том случае, когда исключения генерируются в самой программе.

Вложенные блоки try

Блоки try могут быть вложенными друг в друга. Исключение, возникшее во внутреннем блоке try и не перехваченное связанным с ним блоком catch, распростра¬няется далее во внешний блок try и обрабатывается связанным с ним блоком catch. Такой порядок обработки исключений демонстрируется в приведенном ниже примере программы, где исключение ArraylndexOutOfBoundsException не перехватывается во внутреннем блоке catch, но обрабатывается во внешнем.

// Применение вложенных блоков try.
class NestTrys {
    public static void main(String args[]) {
        // Массив numer длиннее, чем массив denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        // Вложенные блоки try.
        try { // Внешний блок try.
            for(int i=0; i<numer.length; i++)   {
                try { // Внутренний блок try.
                    System.out.println(numer[i] + " / " +
                                       denom[i] + " is " +
                                       numer[i]/denom[i]) ;
                }
                catch (ArithmeticException exc) {
                    // перехватить исключение
                    System.out.println("Can't divide by Zero!");
                }
            }
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("No matching element found.");
            System.out.println("Fatal error - program terminated.");
        }
    }
}

Выполнение этой программы может дать, например, следующий результат:

4 / 2 is 2
Can't divide by Zero!
16 / 4 is 4
32 / 4 is 8
Can't divide by Zero!
128 / 8 is 16
No matching element found.
Fatal error - program terminated.

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

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

Генерирование исключений

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

где объект_исключения должен быть объектом класса, производного от класса Throwable.

Ниже приведен пример программы, демонстрирующий применение оператора throw. В этой программе исключение ArithmeticException генерируется вручную.

// Генерирование исключения вручную,
class ThrowDemo {
    public static void main(String args[])  {
        try {
            System.out.println("Before throw.");
                // Генерирование исключения.
                throw new ArithmeticException() ;
        }
        catch (ArithmeticException exc) {
            // перехватить исключение
            System.out.println("Exception caught.");
        }

        System.out.println("After try/catch block.");
    }
}

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

Before throw.
Exception caught.
After try/catch block.

Обратите внимание на то, что исключение ArithmeticException генерируется с помощью ключевого слова new в операторе throw. Дело в том, что оператор throw генерирует исключение в виде объекта. Поэтому после ключевого слова throw недостаточно указать только тип исключения, нужно еще создать объект для этой цели.

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

Исключение, перехваченное блоком catch, может быть повторно сгенерировано для обработки другим аналогичным блоком. Чаще всего повторное генерирование исключений применяется с целью предоставить разным обработчикам доступ к исключению. Так, например, повторное генерирование имеет смысл в том случае, если один обработчик оперирует одним свойством исключения, а другой обработчик ориентирован на другое его свойство. Повторно сгенерированное исключение не может быть перехвачено тем же самым блоком catch. Оно распространяется в другие блоки catch.

Ниже приведен пример программы, демонстрирующий повторное генерирование исключений.

//•Повторное генерирование исключений,
class Rethrow {
    public static void genException()   {
        // Массив numer длиннее маесивв denom.
        int numer[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                System.out.println(numer[i] + " / " +
                                   denom[i] + " is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can11 divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
                throw exc; // Повторное генерирование исключения.
            }
        }
    }
}

class RethrowDemo {
    public static void main(String args[])  {
        try {
            Rethrow.genException();
        }
        catch(ArraylndexOutOfBoundsException exc) {
            // Перехват повторно сгенерированного включения.
            System.out.println("Fatal error - " +
                               "program terminated.");
        }
    }
}

В данной программе ошибка деления на нуль обрабатывается локально в методе genException(), а при попытке обращения за границы массива исключение генерируется повторно. На этот раз оно перехватывается в методе main().

Подробнее о классе Throwable

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

Таблица 9.1. Наиболее употребительные методы из класса Throwable

Метод Описание
Throwable filllnStackTrace() Возвращает объект типа Throwable, содержащий полную трассировку стека исключений. Этот объект пригоден для повторного генерирования исключений
String getLocalizedMessage() Возвращает описание исключения, локализованное по региональным стандартам
String getMessage() Возвращает описание исключения
void printStackTrace() Выводит трассировку стека исключений
void printStackTrace(PrintStream stream) Выводит трассировку стека исключений в указанный поток
void printStackTrace(PrintWriter stream) Направляет трассировку стека исключений в указанный поток
String toString() Возвращает объект типа String, содержащий полное описание исключения. Этот метод вызывается из метода println() при выводе объекта типа Throwable

Среди методов, определенных в классе Throwable, наибольший интерес представляют методы pr intStackTrace() и toString(). С помощью метода printStackTrace() можно вывести стандартное сообщение об ошибке и запись последовательности вызовов методов, которые привели к возникновению исключения, А метод toString() позволяет получить стандартное сообщение об ошибке. Этот метод также вызывается в том случае, когда объект исключения передается в качестве параметра методу println(). Применение этих методов демонстрируется в следующем примере программы:

// Применение методов из класса Throwable.
class ExcTest {
    static void genException()  {
        int nums[] = new int[4];

        System.out.println("Before exception is generated.");

        // сгенерировать исключение в связи с попыткой
        // обращения за границы массива
        nums[7] = 10;
        System.out.println("this won't be displayed");
    }
}

class UseThrowableMethods {
    public static void main(String args[])  {

        try {
            ExcTest.genException() ;
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("Standard message is: ");
            System.out.println(exc) ;
            System.out.println("nStack trace: ");
            exc.printStackTrace();
        }

        System.out.println("After catch statement.");
    }
}

Результат выполнения данной программы выглядит следующим образом:

Before exception is generated.
Standard message is:
java.lang.ArraylndexOutOfBoundsException: 7

Stack trace:
java.lang.ArraylndexOutOfBoundsException: 7
    at ExcTest.genException(UseThrowableMethods.java:10)
    at UseThrowableMethods.main(UseThrowableMethods.java:19)
After catch statement.

Использование ключевого слова finally

Иногда требуется определить кодовый блок, который должен выполняться по завершении блока try/catch. Допустим, в процессе работы программы возникло исключение, требующее ее преждевременного завершения. Но в программе открыт файл или установлено сетевое соединение, а следовательно, файл нужно закрыть, а соединение разорвать. Для выполнения подобных операций нормального завершения программы удобно воспользоваться ключевым словом finally.

Для того чтобы определить код, который должен выполняться по завершении блока try/catch, нужно указать блок finally в конце последовательности операторов try/catch. Ниже приведена общая форма записи блока try/catch вместе с блоком finally.

try {
    // Блок кода, в котором отслеживаются ошибки.
}
catch (тип_исключения_1 объект_исключения) {
    // Обработчик исключения тип_исключения_1
}
catch (тип_исключения_2 объект_исключения) {
    // Обработчик исключения тип_исключения_2
}
//. . .
finally {
// Код блока finally
}

Блок finally выполняется всегда по завершении блока try/catch независимо от того, какое именно условие к этому привело. Следовательно, блок finally получит управление как при нормальной работе программы, так и при возникновении ошибки. Более того, он будет вызван даже в том случае, если в блоке try или в одном из блоков catch будет присутствовать оператор return для немедленного возврата из метода.

Ниже приведен краткий пример программы, демонстрирующий применение блока finally.

// Применение блока finally,
class UseFinally {
    public static void genException(int what) {
        int t;
        int nums[] = new int[2];

        System.out.println("Receiving " + what);
        try {
            switch(what) {
            case 0:
                t = 10 / what; // сгенерировать ошибку деления на нуль
                break;
            case 1:
                nums[4] = 4; // сгенерировать ошибку обращения к массиву
                break;
            case 2:
                return; // возвратиться из блока try
            }
        }
        catch (ArithmeticException exc) {
            // перехватить исключение
            System.out.println("Can1t divide by Zero!");
            return; // возвратиться из блока catch
        }
        catch (ArraylndexOutOfBoundsException exc) {
            // перехватить исключение
            System.out.println("No matching element found.");
        }
        // Этот блок выполняется независимо от того, каким
        // образом завершается блок try/catch.
        finally {
            System.out.println("Leaving try.");
        }
    }
}

class FinallyDemo {
    public static void main(String args[]) {

        for(int i=0; i < 3; i++) {
            UseFinally.genException(i);
            System.out.println() ;
        }
    }
}

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

Receiving О
Can't divide by Zero!
Leaving try.

Receiving 1
No matching element found.
Leaving try.

Receiving 2
Leaving try.

Нетрудно заметить, что блок finally выполняется независимо от того, каким об¬
разом завершается блок try/catch.

Использование ключевого слова throws

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

возвращаемый_тип имя_метода(список_параметров) throws список_исключений {
    // Тело метода
}

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

Возможно, вам покажется странным, что в ряде предыдущих примеров ключевое слово throws не указывалось при генерировании исключений за пределами методов. Дело в том, что исключения, генерируемые подклассом Error или RuntimeException, можно и не указывать в списке оператора throws. Исполняющая система Java по умолчанию предполагает, что метод может их генерировать. А исключения всех остальных типов следует непременно объявить с помощью ключевого слова throws. Если этого не сделать, возникнет ошибка при компиляции.

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

throws java.io.IOException

Теперь вы знаете, зачем это было нужно. При вводе данных может возникнуть исключение IOException, а на тот момент вы еще не знали, как оно обрабатывается. Поэтому мы и указали, что исключение должно обрабатываться за пределами метода main(). Теперь, ознакомившись с исключениями, вы сможете без труда обработать исключение IOException самостоятельно.

Рассмотрим пример, в котором осуществляется обработка исключения IOException. В методе prompt() отображается сообщение, а затем выполняется ввод символов с клавиатуры. Такой ввод данных может привести к возникновению исключения IOException. Но это исключение не обрабатывается в методе prompt(). Вместо этого в объявлении метода указан оператор throws, т.е. обязанности по обработке данного исключению поручаются вызывающему методу. В данном случае вызывающим является метод main(), в котором и перехватывается исключение.

// Применение ключевого слова throws,
class ThrowsDemo {
    // Обратите внимание на оператор throws в объявлении метода.
    public static char prompt(String str)
        throws java.io.IOException {

        System.out.print(str + ": ");
        return (char) System.in.read() ;
    }

    public static void main(String args[]) {
        char ch;

        try {
            // В методе prompt() может быть сгенерировано исключение,
            // поэтому данный метод следует вызывать в блоке try.
            ch = prompt("Enter a letter");
        }
        catch(java.io.IOException exc) {
            System.out.println("I/O exception occurred.");
            ch = 'X';
        }
        System.out.println("You pressed " + ch);
    }
}

Обратите внимание на одну особенность приведенного выше примера. Класс IOException относится к пакету java. io. Как будет разъяснено в главе 10, в этом пакете содержатся многие языковые средства Java для организации ввода-вывода. Следовательно, пакет java.io можно импортировать, а в программе указать только имя класса IOException.

Новые средства обработки исключений, внедренные в версии JDK 7

С появлением версии JDK 7 механизм обработки исключений в Java был значительно усовершенствован благодаря внедрению трех новых средств. Первое из них поддерживает автоматическое управление ресурсами, позволяющее автоматизировать процесс освобождения таких ресурсов, как файлы, когда они больше не нужны. В основу этого средства положена расширенная форма оператора try, называемая оператором try с ресурсами и описываемая в главе 10 при рассмотрении файлов. Второе новое средство называется многократным перехватом, а третье — окончательным или более точным повторным генерированием исключений. Два последних средства рассматриваются ниже.

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

Для организации многократного перехвата следует указать список исключений в одном операторе catch, разделив их типы оператором поразрядного ИЛИ. Каждый параметр многократного перехвата неявно указывается как final. (По желанию модификатор доступа final можно указать и явным образом, но это совсем не обязательно.) А поскольку каждый параметр многократного перехвата неявно указывается как final, то ему нельзя присвоить новое значение.

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

catch(final ArithmeticException | ArraylndexOutOfBoundsException e) {

Ниже приведен краткий пример программы, демонстрирующий применение многократного перехвата исключений.

// Применение средства многократного перехвата исключений.
// Примечание: для компиляции этого кода требуется JDK 7
// или более поздняя версия данного комплекта,
class MultiCatch {
    public static void main(String args[]) {
        int a=88, b=0;
        int result;
        char chrs[] = { 'А', 'В', 'C' };

        for(int i=0; i < 2; i++)    {
            try {
                if (i == 0)
                    // сгенерировать исключение ArithmeticException
                    result = а / b;
                else
                    // сгенерировать исключение ArraylndexOutOfBoundsException
                    chrs[5] = 'X';
            }
            // В этом операторе catch организуется перехват обоих исключений,
            catch(ArithmeticException | ArraylndexOutOfBoundsException е) {
                System.out.println("Exception caught: " + e);
            }
        }

        System.out.println("After multi-catch.");
    }
}

В данном примере программы исключение ArithmeticException генерируется при попытке деления на нуль, а исключение ArraylndexOutOfBoundsException — при попытке обращения за границы массива chrs. Оба исключения перехватываются одним оператором catch.

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

Встроенные в Java исключения

В стандартном пакете java. lang определены некоторые классы, представляющие стандартные исключения Java. Часть из них использовалась в предыдущих примерах программ. Наиболее часто встречаются исключения из подклассов стандартного класса RuntimeException. А поскольку пакет java. lang импортируется по умолчанию во все программы на Java, то исключения, производные от класса RuntimeException, становятся доступными автоматически. Их даже обязательно включать в список оператора throws. В терминологии языка Java такие исключения называют непроверяемыми, поскольку компилятор не проверяет, обрабатываются или генерируются подобные исключения в методе. Непроверяемые исключения, определенные в пакете java.lang, приведены в табл. 9.2, тогда как в табл. 9.3 — те исключения из пакета j ava. lang, которые следует непременно включать в список оператора throws при объявлении метода, если, конечно, в методе содержатся операторы, способные генерировать эти исключения, а их обработка не предусмотрена в теле метода. Такие исключения принято называть проверяемыми. В Java предусмотрен также ряд других исключений, определения которых содержатся в различных библиотеках классов. К их числу можно отнести упоминавшееся ранее исключение IOException.

Таблица 9.2. Непроверяемые исключения, определенные в пакете java.lang

Исключение Описание
ArithmeticException Арифметическая ошибка, например попытка деления на нуль
ArraylndexOutOfBoundsException Попытка обращения за границы массива
ArrayStoreException Попытка ввести в массив элемент, несовместимый с ним по типу
ClassCastException Недопустимое приведение типов
EnumConstNotPresentException Попытка использования нумерованного значения, которое не было определено ранее
IllegalArgumentException Недопустимый параметр при вызове метода
IllegalMonitorStateException Недопустимая операция контроля, например, ожидание разблокировки потока
IllegalStateException Недопустимое состояние среды выполнения или приложения
IllegalThreadStateException Запрашиваемая операция несовместима с текущим состоянием потока
IndexOutOfBoundsException Недопустимое значение индекса
NegativeArraySizeException Создание массива отрицательного размера
NullPointerException Недопустимое использование пустой ссылки
NumberFormatException Неверное преобразование символьной строки в число
SecurityException Попытка нарушить систему защиты
StringlndexOutOfBounds Попытка обращения к символьной строке за ее границами
TypeNotPresentException Неизвестный тип
UnsupportedOperationException Неподдерживаемая операция

Таблица 9.3. Проверяемые исключения, определенные в пакете java.lang

Исключение Описание
ClassNotFoundException Класс не найден
CloneNotSupportedException Попытка клонирования объекта, не реализующего интерфейс Cloneable
IllegalAccessException Доступ к классу запрещен
InstantiationException Попытка создания объекта абстрактного класса или интер¬фейса
InterruptedException Прерывание одного потока другим
NoSuchFieldException Требуемое поле не существует
NoSuchMethodException Требуемый метод не существует
ReflectiveOperationException Суперкласс исключений, связанных с рефлексией (добавлен в версии JDK 7)

Создание подклассов, производных от класса Exception

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

В классе Exception не определены новые методы. Он лишь наследует методы, предоставляемые классом Throwable. Таким образом, все исключения, включая и создаваемые вами, содержат методы класса Throwable. Конечно же, вы вольны переопределить в создаваемом вами классе один или несколько методов.

Ниже приведен пример, в котором создается исключение NonlntResultException. Оно генерируется в том случае, если результатом деления двух целых чисел является дробное число. В классе NonlntResultException содержатся два поля, предназначенные для хранения целых чисел, а также конструктор. В нем также переопределен метод toString(), что дает возможность выводить описание исключения с помощью метода println().

// Применение специально создаваемого исключения.
// создать исключение
class NonlntResultException extends Exception {
    int n;
    int d;

    NonlntResultException(int i, int j) {
        n = i;
        d = j;
    }

    public String toString()    {
        return "Result of " + n + " / " + d +
                " is non-integer.";
    }
}

class CustomExceptDemo {
    public static void main(String args[]) {

        // В массиве numer содержатся нечетные числа,
        int numer[] = { 4, 8, 15, 32, 64, 127, 256, 512 };
        int denom[] = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.length; i++)   {
            try {
                if((numer[i]%2) != 0)
                    throw new
                        NonlntResultException(numer[i], denom[i]);
                System.out.println(numer[i] + " / " +
                                   denom[i] + 11 is " +
                                   numer[i]/denom[i]);
            }
            catch (ArithmeticException exc) {
                // перехватить исключение
                System.out.println("Can11 divide by Zero!");
            }
            catch (ArraylndexOutOfBoundsException exc) {
                // перехватить исключение
                System.out.println("No matching element found.");
            }
            catch (NonlntResultException exc) {
                System.out.println(exc) ;
            }
        }
    }
}

Результат выполнения данной программы выглядит следующим образом:

4 / 2 is 2
Can't divide by Zero!
Result of 15 / 4 is non-integer.
32 / 4 is 8
Can't divide by Zero!
Result of 127 / 8 is non-integer.
No matching element found.
No matching element found.

Пример для опробования 9.1.
Добавление исключений в класс очереди

В этом проекте предстоит создать два класса исключении, которые будут использоваться классом очереди, разработанным в примере для опробования 8.1. Эти исключения должны указывать на переполнение и опустошение очереди, а генерировать их будут методы put() и get() соответственно. Ради простоты эти исключения добавляются в класс FixedQueue, но вы можете без труда внедрить их в любые другие классы очереди, разработанные в примере для опробования 8.1.

Последовательность действий

  1. Создайте файл QExcDemo.java.
  2. Определите следующие исключения в файле QExcDemo.java:
    /*
    Пример для опробования 9.1.
    Добавление обработчиков исключений в класс очереди.
    */
    // Исключение, указывающее на переполнение очереди,
    class QueueFullException extends Exception {
        int size;
    
        QueueFullException(int s) { size = s; }
    
        public String toString()    {
            return "nQueue is full. Maximum size is " + size;
        }
    }
    
    // Исключение, указывающее на опустошение очереди,
    class QueueEmptyException extends Exception {
        public String toString()    {
            return "nQueue is empty.";
        }
    }
    

    Исключение QueueFullException генерируется при попытке поместить элемент в уже заполненную очередь, а исключение QueueEmptyException — в ответ на попытку извлечь элемент из пустой очереди.

  3. Измените класс FixedQueue таким образом, чтобы при возникновении ошибки он генерировал исключение. Соответствующий код приведен ниже. Введите этот код в файл QExcDemo.java.
    // Класс, реализующий очередь фиксированного размера
    // для хранения символов.
    class FixedQueue implements ICharQ {
        private char q[]; // Массив для хранения элементов очереди,
        private int putloc, getloc; // Индексы размещения и извлечения
    
        // элементов очереди.
        // создать пустую очередь заданного размера
        public FixedQueue(int size) {
            q = new char[size+1]; // выделить память для очереди
            putloc = getloc = 0;
        }
    
        // поместить символ в очередь
        public void put(char ch)
        throws QueueFullException {
    
            if(putloc==q.length-1)
                throw new QueueFullException(q.length-1);
    
            putloc++;
            q[putloc] = ch;
        }
    
        // извлечь символ из очереди
        public char get()
        throws QueueEmptyException {
    
            if(getloc == putloc)
                throw new QueueEmptyException();
    
            getloc++;
            return q[getloc];
        }
    }
    

    Добавление исключений в класс FixedQueue выполняется в два этапа. Сначала в определении методов get() и put() указывается оператор throws с типом генерируемого исключения. А затем в этих методах организуется генерирование исключений при возникновении ошибок. Используя исключения, можно организовать обработку ошибок в вызывающей части программы наиболее рациональным способом. Как вы помните, в предыдущих версиях рассматриваемой здесь программы выводились только сообщения об ошибках. А генерирование исключений является более профессиональным подходом к разработке данной программы.

  4. Для опробования усовершенствованного класса FixedQueue введите в файл QExcDemo.java приведенный ниже исходный код класса QExcDemo.
    // Демонстрация исключений при обращении с очередью,
    class QExcDemo {
        public static void main(String args[])  {
            FixedQueue q = new FixedQueue(10);
            char ch;
            int i;
    
            try {
                // Переполнение очереди.
                for(i=0; i < 11; i++)   {
                    System.out.print("Attempting to store : " +
                                     (char) ('A' + i));
                    q.put((char) (fA' + i));
                    System.out.println(" - OK");
                }
                System.out.println();
            }
            catch (QueueFullException exc) {
                System.out.println(exc);
            }
            System.out.println();
    
            try {
                // Попытка извлечь символ из пустой очереди.
                for(i=0; i < 11; i++) {
                System.out.print("Getting next char: ");
                ch = q.get();
                System.out.println(ch);
                }
            }
            catch (QueueEmptyException exc) {
                System.out.println(exc);
            }
        }
    }
    
  5. Класс FixedQueue реализует интерфейс ICharQ, в котором определены методы get() и put(), и поэтому интерфейс ICharQ необходимо изменить таким образом, чтобы в нем отражалось наличие операторов throws. Ниже приведен видоизмененный соответственно код интерфейса ICharQ. Не забывайте о том, что он должен храниться в файле ICharQjava.
    // Интерфейс очереди для хранения символов с генерированием исключений,
    public interface ICharQ {
        // поместить символ в очередь
        void put(char ch) throws QueueFullException;
        // извлечь символ из очереди
        char get() throws QueueEmptyException;
    }
    
  6. Скомпилируйте сначала новую версию исходного файла IQChar. j ava, а затем исходный файл QExcDemo. java и запустите программу QExcDemo на выполнение. В итоге вы получите следующий результат ее выполнения:
    Attempting to store A - OK
    Attempting to store В - OK
    Attempting to store С - OK
    Attempting to store D - OK
    Attempting to store E - OK
    Attempting to store F - OK
    Attempting to store G - OK
    Attempting to store H - OK
    Attempting to store I - OK
    Attempting to store J - OK
    Attempting to store К
    Queue is full. Maximum size is 10
    
    Getting next char: A
    Getting next char: В
    Getting next char: С
    Getting next char: D
    Getting next char: E
    Getting next char: F
    Getting next char: G
    Getting next char: H
    Getting next char: I
    Getting next char: J
    Getting next char:
    Queue is empty.
    

Упражнение для самопроверки по материалу главы 9

  1. Какой класс находится на вершине иерархии исключений?
  2. Объясните вкратце, как пользоваться ключевыми словами try и catch?
  3. Какая ошибка допущена в приведенном ниже фрагменте кода?
    // ...
    vals[18] = 10;
    catch (ArraylndexOutOfBoundsException exc) {
        // обработать ошибку
    }
    
  4. Что произойдет, если исключение не будет перехвачено?
  5. Какая ошибка допущена в приведенном ниже фрагменте кода?
    class A extends Exception { ...
    class В extends А { ...
        // ...
    try {
        // ...
    }
    catch (A exc) { ... }
    catch (В exc) { ... }
    
  6. Может ли внутренний блок catch повторно генерировать исключение, которое будет обработано во внешнем блоке catch?
  7. Блок finally — последний фрагмент кода, выполняемый перед завершением программы. Верно или неверно? Обоснуйте свой ответ.
  8. Исключения какого типа необходимо явно объявлять с помощью оператора throws, включаемого в объявление метода?
  9. Какая ошибка допущена в приведенном ниже фрагменте кода?
    class MyClass { // ... }
    // ...
    throw new MyClass();
    
  10. Отвечая на вопрос 3 упражнения для самопроверки по материалу главы 6, вы создали класс Stack. Добавьте в него специальные исключения для реагирования на попытку поместить элемент в переполненный стек и извлечь элемент из пустого стека.
  11. Какими тремя способами можно сгенерировать исключение?
  12. Назовите два подкласса, производных непосредственно от класса Throwable.
  13. Что такое многократный перехват?
  14. Следует ли перехватывать в программе исключения типа Error?

  • Ошибка деление на ноль делфи
  • Ошибка деление на ноль postgresql
  • Ошибка деление на 0 формула используемая при расчете оклад долянеполногорабочеговремени времявднях
  • Ошибка деление на 0 при увольнении
  • Ошибка декомпрессии при распаковке архива