Меню

Если мы хотим выкинуть ошибку нам достаточно java

Зміст

  • Что такое исключение в Java (Java Exception)?
  • Операторы try, catch, finally, throw, throws: обработка исключений и примеры использования
  • Иерархия исключений Java
  • Заключение

Что такое исключение в Java (Java Exception)?

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

Помогаем

Unrecognizable

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

Приведем пример кода, в котором возникает исключение.

class ExceptionTest{ 
    // Метод принимает два целых числа
    // и возвращает результат деления
    // первого на второе
    static float divide(int x, int y){
        float result = x / y;
        return result;
    }

    public static void main(String args[]){
        // Эта строка будет выполнена
        System.out.println(divide(4, 2));
        // Эта тоже
        System.out.println(divide(0, 2));
        // Эта выбросит исключение
        System.out.println(divide(4, 0));
    }
}

Если его запустить, получим следующий вывод:

Залучити нових та утримати наявних клієнтів за допомогою вау-сервіса – це можливо! Переходьте та дізнайтеся більше.

РЕЄСТРУЙТЕСЯ!

clyent

2.0
0.0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at ExceptionTest.divide(ExceptionTest.java:6)
        at ExceptionTest.main(ExceptionTest.java:16)

Выводятся два результата деления и сообщение об исключении. В этом сообщении указана следующая информация:

  • тип исключения (ArithmeticException);
  • какое именно исключение возникло (деление на ноль);
  • стек выполнения: методы и строки, в которых возникло исключение.

Операторы try, catch, finally, throw, throws: обработка исключений и примеры использования

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

try-catch

Для перехвата исключения используется конструкция trycatch. Код, который нужно проверить на исключение, заключается в блок try, а код, обрабатывающий исключение – в блок catch.

Добавим эти блоки в приведенный выше блок кода и дополним его парой переменных для наглядности.

class ExceptionTest{  
    // Метод принимает два целых числа
    // и возвращает результат деления
    // первого на второе
    static float divide(int x, int y){
        float result = x / y;
        return result;
    }

    public static void main(String args[]){
        int x = 4;
        int y = 2;
        try {
            // Эта строка будет выполнена
            System.out.println(divide(x, y));

            x = 0;
            // Эта тоже
            System.out.println(divide(x, y));

            x = 4;
            y = 0;
            // Эта выбросит исключение
            System.out.println(divide(x, y));
        } catch (ArithmeticException e) {
            System.out.println("Ошибка при делении " + x + " на " + y);
        }
    }
}

Получим более удобочитаемый вывод:

2.0
0.0
Ошибка при делении 4 на 0

finally

Блок finally выполняется после try-catch, независимо от того, возникло ли исключение. Это необязательный блок, но если нет блока catch, то блок finally необходим.

В этом блоке можно, например, закрыть файл, открытый в блоке try, как в приведенном ниже коде.

import java.io.FileWriter;
import java.io.IOException;

public class FinallyTest {
    public static void main(String[] args) {
        FileWriter writer = null;
        try {
            writer = new FileWriter("out.txt");
            writer.write("Writing to the file!");
            System.out.println("Файл записан успешно.");
        } catch (IOException e) {
            System.out.println("Ошибка записи в файл.");
            e.printStackTrace();
        } finally {
            if ( writer != null ){
                try{
                  writer.close();
                } catch (IOException e) {
                  System. out.println("Ошибка закрытия файла.");
                  e.printStackTrace();
                }
            }
        }
    }
}

throw

В некоторых случаях требуется выбросить исключение самостоятельно. Это делается при помощи ключевого слова throw.

В данном примере метод PrintMe выбрасывает исключение, если его аргумент равен null.

import java.util.LinkedList;

public class ThrowTest
{
  public static void main(String[] args) {
        LinkedList<String> fruits = new LinkedList<String>();
        fruits.add("apple");
        fruits.add("banana");
        fruits.add("orange");
        fruits.add("mango");

        // Печатает список
        ThrowTest.PrintMe(fruits);

        // Выбрасывает исключение
        ThrowTest.PrintMe(null);
  }

    public static void PrintMe(LinkedList<String> fruits){
        if (fruits == null){
            throw new NullPointerException("Аргумент не инициализирован");
        }
        System.out.println(fruits);
    }
}

throws

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

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ThrowsTest{
    public static void readFromFile() throws IOException {
        // Указываем несуществующий файл, чтобы проверить работу исключения
        try (BufferedReader reader = new BufferedReader(new FileReader("out.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line + "n");
            }
        }
    }

    public static void main(String[] args) {
    try {
            readFromFile();
        } catch (IOException ioe) {
            System.out.println("Файл не найден");
        }
    }
}

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

Исключения в Java делятся на две основные категории: встроенные и пользовательские.

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

Встроенные исключения – это исключения, которые заранее определены в Java.

Когда возникает встроенное исключение, виртуальная машина Java (JVM) создает объект, принадлежащий классу встроенного исключения. Все исключения происходят от класса java.lang.Throwable, но они определены в нескольких пакетах.

Класс Throwable происходит непосредственно об класса Object и является корневым классом дерева классов исключений. От него происходят два подкласса: Error и Exception. Ошибки и исключения, которые встречаются в программах на Java, являются объектами этих классов.

С использованием класса Throwable можно создавать собственные исключения.

Класс Error является надклассом для всех классов ошибок времени выполнения. Он завершает выполнение программы, если происходит ошибка, связанная с системой или ресурсами (JVM).

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

Примеры ошибок: AssertionError, LinkageError, OutOfMmeoryError, StackOverFlowError, VirtualMachineError.

Класс Exception представляет ошибки, вызванные программой или внешними факторами. Это надкласс для все классов исключений.

Для этого класса существует два конструктора:

  • public Exception() (по умолчанию)
  • public Exception(String message) (принимает строковое сообщение как аргумент)

Эти конструкторы наследуются всеми подклассами исключений. Сам по себе класс Exception не предоставляет собственных методов. Он наследует методы класса Throwable.

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

Проверяемые исключения в Java

Проверяемые исключения проверяются компилятором Java во время компиляции и не являются подклассами RuntimeException (исключения времени выполнения).

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

Проверяемые исключения обрабатываются либо в блоке try-catch, либо в объявлении метода с ключевым словом throws. Если исключение не обработано, происходит ошибка компиляции.

Проверяемыми исключениями являются все исключения, кроме RuntimeException, Error и их подклассов.

Примеры проверяемых исключений: ClassNotFoundException, IOException, SQLException, IllegalAccessException, FileNotFoundException.

Непроверяемые исключения (исключения времени выполнения) в Java

Непроверяемые исключения в Java – это исключения, которые проверяются JVM, а не компилятором Java. Они возникают во время выполнения программы.

Все подклассы RuntimeException называются непроверяемыми исключениями или исключениями времени выполнения в Java.

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

Компилятор Java не проверяет исключения времени выполнения во время компиляции, независимо от того, обрабатывает ли их программа.

Если в методе возникает исключение времени выполнения, а программист не обрабатывает его, JVM прекращает выполнение программы, не выполняя остаток кода.

Примеры непроверяемых исключений: ArithmeticException, ArrayIndexOutOfBoundsException, ClassCastException, NegativeArraySizeException, NullPointerException.

В Java определено множество встроенных исключений. Ниже приведены описания некоторых из них.

Исключение Описание
ArithmeticException Выбрасывается, когда возникает исключительная арифметическая ситуация.
ArrayIndexOutOfBoundsException Выбрасывается при попытке обратиться к массиву по недействительному индексу.
ArrayStoreException Выбрасывается при попытке сохранить объект несоответствующего типа в массиве объектов.
ClassCastException Выбрасывается, когда код совершает попытку привести тип объекта к подклассу, экземпляром которого он не является.
ClassNotFoundException Выбрасывается, когда приложение совершает попытку загрузить класс по его имени в строковом представлении с использованием метода forName в классе Class.
CloneNotSupportedException Выбрасывается, когда для клонирования объекта определенного класса вызван метод clone, но класс этого объекта не реализует интерфейс Cloneable.
EnumConstantNotPresentException Выбрасывается, когда приложение производит попытку обратиться к константе из перечисления по имени, но тип этого перечисления не содержит константу с указанным именем.
Exception Класс Exception и его подклассы являются подклассами Throwable и указывают на ситуации, которые могут быть перехвачены приложением.
IllegalAccessException Выбрасывается, когда приложение совершает попытку применить рефлексию, чтобы создать экземпляр (отличный от массива), присвоить полю значение или получить значение поля, вызвать метод, но текущий выполняемый метод не имеет доступа к определению указанного класса, поля, метода или конструктора.
IllegalArgumentException Выбрасывается, когда методу передан недопустимый или неприемлемый аргумент.
IllegalMonitorStateException Выбрасывается, когда нить пытается ожидать монитор объекта или отправить оповещение другим нитям, ожидающим монитор объекта, но указанный монитор не принадлежит ей.
IllegalStateException Выбрасывается, когда метод вызван в недопустимое или неприемлемое время.
IllegalThreadStateException Выбрасывается, когда нить находится в неприемлемом состоянии для выполнения запрошенной операции.
IndexOutOfBoundsException Выбрасывается, когда индекс определенного типа (например, для массива, строки или вектора) находится вне допустимого диапазона.
InstantiationException Выбрасывается, когда приложение совершает попытку создать экземпляр класса с использованием метода newInstance класса Class, но создать экземпляр указанного класса невозможно.
InterruptedException Выбрасывается, когда нить находится в состоянии ожидания, сна или занята каким-либо другим образом, но ее работа прервана либо перед выполнением действия, либо после его выполнения.
NegativeArraySizeException Выбрасывается, когда приложение совершает попытку создать массив отрицательного размера.
NoSuchFieldException Выбрасывается, когда в классе отсутствует поле с указанным именем.
NoSuchMethodException Выбрасывается, когда невозможно найти указанный метод.
NullPointerException Выбрасывается, когда приложение совершает попытку использовать значение null, но требуется указать объект.
NumberFormatException Выбрасывается, когда приложение совершает попытку преобразовать строку в один из числовых типов, но строка имеет недопустимый формат.
RuntimeException Это родительский класс для тех исключений, которые могут быть выброшены при нормальной работе виртуальной машины Java.
SecurityException Выбрасывается менеджером безопасности при нарушении безопасности.
StringIndexOutOfBoundsException Выбрасывается методами класса String при попытке использовать отрицательный индекс или индекс, превышающий размер строки.
TypeNotPresentException Выбрасывается, когда приложение совершает попытку доступа к типу с указанием его имени в виде строки, но не удается найти определение типа с указанным именем.
UnsupportedOperationException Выбрасывается, когда запрошенная операция не поддерживается.

Пользовательские исключения

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

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

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

  • Определить класс, расширяющий класс Exception.
  • Определить конструктор. Если не требуется хранить сведения об исключении, определяется конструктор по умолчанию. Если требуется сохранить сведения об исключении в виде строки, определяется конструктор с параметром.
  • Создать объект пользовательского исключения и выбросить его с помощью ключевого слова throw.

Пример исключения с конструктором по умолчанию:

class MyException extends Exception{
    // Конструктор по умолчанию
    MyException(){}
}

class Main{
    public static void main(String[] args){
        try{
            MyException e = new MyException();
            throw e;
        }
        catch(MyException ex){
             System.out.println("Перехвачено пользовательское исключение"); 
        }
    }
}

Этот код выведет следующий текст: “Перехвачено пользовательское исключение”.

Пример исключения с параметром в конструкторе:

class MyException extends Exception{
    MyException(String msg){
        super(msg);
    }
}

class Main{
    public static void main(String[] args){
        try{
            MyException e = new MyException("Перехвачено пользовательское исключение с информацией");
            throw e;
        }
        catch(MyException ex){
             System.out.println(ex.getMessage()); 
        }
    }
}

Вывод будет таким:   “Перехвачено пользовательское исключение с информацией”

Заключение

Исключения в Java позволяют указать пути обхода проблем и исправления их последствий. С их помощью можно поместить логику обработки исключительных ситуаций в отдельные блоки кода (catch), оставив основную логику в блоке try, а логику завершающих действий по обработке исключения – в блоке finally. Исключения могут быть обработаны в методах, где они возникают, или переданы далее с помощью ключевых throw и throws.

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

Исключения

Причиной появления исключений, на мой взгляд, стало очень простое предположение — во время исполнения программы вряд ли можно быть уверенным, что все функции будут делать свою работу без сбоев. Например, если ваш код должен прочитать файл с каким-то именем, а файла такого просто нет ? Или вы хотите по сети обратиться к другой программе, но добрая старушка-уборщица выдернула шнур из компьютера ? В конце концов ваш калькулятор при вычислении дроби получил в знаменателе ноль. Да много ли каких еще препятствий может встретиться на пути.
Конечно, не все методы/функции обязательно будут оканчиваться крахом, но это обычное дело. И что в этом случае необходимо предпринять ? В старое доброе время в языке Си было принято возвращать какое-то значение. Причем в разных случаях тот же ноль мог означать что все хорошо, но мог означать что все плохо. В общем стандарта как не было, так до сих пор и нет — что должна возвращать функция в случае возникновения ошибки. Определенно можно сказать только одно — функция должна возвращать:

а) признак того, что произошла ошибка
б) информацию о том, а что собственно плохого произошло

С пунктом б) как раз достаточно просто — у нас же ООП. Значит удобно всю необходимую информацию поместить в какой-либо объект какого-то класса и вернуть. В этом объекте мы можем развернуться по полной — и сообщение написать и код ошибки выдать и много чего еще.
Что касается пункта а), то несложно догадаться, что возвращать ошибку как результат выполнения функции — плохая идея. Если мы ожидаем целое число, а ошибка будет описываться пусть даже просто строкой, то вряд ли кто-то сможет предложить что-то более-менее приемлемое. Но идея возвращать объект-описание ошибки — красивая и благодарная идея.
Отсюда родилась следующее решение — метод может генерировать исключение (объект-ошибку) и завершаться не вызовом return в случае ошибки, а иначе — метод будет «бросать» исключение.
И тогда код, вызывающий такой метод может получить ответ двумя способами:

  1. Обычным возвратом значения из метода, если все хорошо
  2. Будет «ловить» исключение

Соответственно метод, который хочет возвращать ошибку, тоже должен уметь возвращать какое-то значение (или ничего не возвращать в случае void) когда все хорошо и создавать и «бросать» исключения, если что-то пошло не так. Я уже дважды использовал термин «бросать», причем не без умысла — в языке Java используется глагол throw (бросать, кидать), посему больше не буду брать его в кавычки.
Мы получаем три момента для рассмотрения:

  1. Описание объекта-исключения — его же надо уметь создавать
  2. Описание метода, который умеет бросать исключения
  3. Как правильно ловить исключение

Итак, еще раз — надо создать исключение, надо правильно бросить исключение, надо правильно поймать исключение.

Класс для исключения

Научимся создавать исключения. Для таких классов существует специальная иерархия классов, которая начинается с класса java.lang.Throwable. (Надеюсь вы помните, что java.lang означет пакет, в котором находится описание класса. По сути директория). Если перевести это название получится что-то вроде «готовый к бросанию» — с литературным переводом возможно у меня не очень красиво получилось, но идея именно такая: объекты этого класса (и всех потомков) могут быть брошены. Этот класс редко используется для прямого наследования, чаще используется его потомок, класс java.lang.Exception. А уж у этого класса «детишек» очень много. Как вы наверно уже догадались, количество готовых классов для Java огромно. Вряд ли конечно на каждого человека на земле приходится один класс, хотя возможно я не так уж и далек от истины. В общем, уже готовых классов-исключений среди других много. Так что будет что изучать. Но о них мы поговорим несколько позже, а пока все-таки создадим наш класс-исключение.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

package edu.javacourse.exception;

public class SimpleException extends Exception

{

    // Это наше поле для хранения информации, присущей данному

    // классу-исключению. Поле немножко надуманное, но здесь может быть

    // и достаточно важная информация

    private int errorCode;

    // переопределяем конструктор

    public SimpleException(String message)

    {

        this(0, message);

    }

    // Создаем свой конструктор

    public SimpleException(int errorCode, String message)

    {

        // Вызываем конструктор предка

        super(message);

        // Добавляем инициализацию своего поля

        this.errorCode = errorCode;

    }

    // Метод для получения кода ошибки

    public int getErrorCode()

    {

        return errorCode;

    }

}

Как вы можете видеть ничего особенного в описании нет — создали класс, унаследовали его от класса Exception, определили свой конструктор, переопределили уже существующий — в общем ничего неординарного и загадочного. Единственное, что хотелось бы отметить — это наличие у класса Throwable (предка Exception) нескольких достаточно востребованных методов.

getMessage() — получить сообщение об ошибке, которое обычно имеет смысл читать
printStackTrace() — распечатать полный стек вызовов. Стек вызовов — это полный список всех методов внутри которых случилась ошибка. Т.е. если вызывался method1, внутри него method2, потом method3 и в нем случилось исключение, то вы увидите все три метода. Чуть позже мы с вами посмотрим пример использования этого метода. Не пренебрегайте им — он очень удобный и информативный

Метод для генерации исключения

Генерировать исключение можно в методе и этот метод может объявить, что он кидает исключения определенного класса. Делается это достаточно просто — после списка аргументов (в скобках) пишется ключевое слово throws и через запятую перечисляются классы исключений, который может порождать данный метод. Потом открывается фигурная скобка и мы пишем тело метода. Давайте посмотрим пример такого описания — наш класс Generator включает метод helloMessage который принимает в качестве строки имя, чтобы отдать строку «Hello, <имя>«. Но если имя не указано (указатель на строку равен null), то метод не возвращает например пустую строку — он кидает исключение, которое можно использовать в дальнейшей логике.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package edu.javacourse.exception;

public class Generator

{

    // Данный метод описан с указанием того, что он способен кинуть

    // исключение типа SimpleException

    public String helloMessage(String name) throws SimpleException

    {

        if (name == null) {

            // Мы должны сначала создать объект-исключение

            SimpleException se = new SimpleException(10, «Message is null»);

            // Теперь мы можем «кинуть» это исключение — это другой способ выйти

            // из метода — отличный от варианта с return

            throw se;

            // Можно совместить создание и кидание — можете закомментировать

            // предыдущие строки и использовать нижеприведенную

            // throw new SimpleException(10, «Message is null»);

        }

        return «Hello, « + name;

    }

}

Опять смотрим код и видим, что после проверки на null мы создаем объект-исключение (обратите внимание — мы просто создаем нужный нам объект, заполняем его нужными значениями (мы в нем описываем реальную проблему, которая произошла) и кидаем. Опять же обращаю ваше внимание на то, что мы не делаем вызов return — этот вызов делается в случае если все хорошо. Мы кидаем исключение — пишем специальную конструкцию throw (не перепутайте — в описании метода пишем глагол в третьем лице единственного числа по английской грамматике с окончанием s — throws — «бросает», а в коде используем повелительное наклонение — throw — «бросай»).
Несколько позже мы рассмотрим еще несколько моментов, которые касаются описания методов, которые бросают исключения. Пока же перейдем к третьему шагу — как поймать исключение.

Ловим исключение

Для того, чтобы поймать исключение используется конструкция try … catch. Перед блоком кода, который порождает исключение пишем слово try и открываем фигурные скобки. После окончания блока закрываем скобки, пишем слово catch, в скобках указываем переменную класса-исключения, открываем скобки и там пишем код, который будет вызываться ТОЛЬКО в случае если метод/методы внутри try породили исключение. Смотрим код:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        // создаем наш класс для генерации исключений

        Generator generator = new Generator();

        // Данный блок будет обрабатывать исключение

        // и оно там действительно возникнет — мы же передали null

        try {

            String answer = generator.helloMessage(null);

            System.out.println(«Answer 1:» + answer);

        } catch (SimpleException ex) {

            // Здесь мы можем обработать объект-исключение,

            // получить некоторую информаицию

            System.out.println(«Error code:» + ex.getErrorCode());

            System.out.println(«Error message:» + ex.getMessage());

        }

        // Данный блок будет обрабатывать исключение

        // но его не будет — мы передали корректный параметр

        try {

            String answer = generator.helloMessage(«Yoda»);

            System.out.println(«Answer 2:» + answer);

        } catch (SimpleException ex) {

            // Здесь мы можем обработать объект-исключение,

            // получить некоторую информаицию

            System.out.println(«Error:» + ex.getMessage());

        }

    }

}

Полный текст проекта можно скачать тут — SimpleException. При запуске можно увидеть, что первый кусочек try … catch выкинет исключение и мы увидим текст об ошибках

Error code:10
Error message:Message is null

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

Answer 2:Hello, Yoda

Обратите внимание, что в первом блоке try … catch строка «Answer 1:» не выводится. Т.е. поведение при генерации исключения следующее: сразу после создания исключения все остальные строки внутри блока уже не выполняются и вы сразу перемещаетесь в блок catch. Думаю, что это достаточно очевидно.
Итак, мы рассмотрели простой вариант использования исключений, но это конечно же не все. Так что продолжим.

Блок finally

Как мы видели чуть выше, если во время исполнения внутри блока try … catch случается исключение, то все оставшиеся строки НЕ ВЫПОЛНЯЮТСЯ. Но это не всегда полностью соответствует нашим желаниям. Существует немалое количество ситуаций, когда, чтобы не случилось, какие-то строки кода должны выполняться вне зависимости от того, каков результат. Именно для этого существует секция finally.
Например, вы открыли файл на запись и долгое время что-то туда записывали. Но наступил момент, когда какие-то данные по определенным причинам туда не попали и запиcь надо прекращать. Но даже в этой ситуации файл нужно сохранить (закрыть). Или вы открыли сетевое соединение, которое в любом случае надо закрыть. Наконец вам просто надо всегда обнулить какой-то счетчик в конце. Давайте посмотрим структуру с finally

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        // создаем наш класс для генерации исключений

        Generator generator = new Generator();

        // Данный блок будет обрабатывать исключение

        // и оно там действительно возникнет — мы же передали null

        try {

            String answer = generator.helloMessage(null);

            System.out.println(«Answer 1:» + answer);

        } catch (SimpleException ex) {

            // Здесь мы можем обработать объект-исключение,

            // получить некоторую информаицию

            System.out.println(«Error code:» + ex.getErrorCode());

            System.out.println(«Error message:» + ex.getMessage());

        } finally {

            // Этот блок будет вызываться всегда, независимо от результата

            System.out.println(«Этот блок вызываетя всегда»);

        }

        // Данный блок будет обрабатывать исключение

        // но его не будет — мы передали корректный параметр

        try {

            String answer = generator.helloMessage(«Yoda»);

            System.out.println(«Answer 2:» + answer);

        } catch (SimpleException ex) {

            // Здесь мы можем обработать объект-исключение,

            // получить некоторую информаицию

            System.out.println(«Error:» + ex.getMessage());

        } finally {

            // Этот блок будет вызываться всегда, независиом от результата

            System.out.println(«Этот блок вызываетя всегда»);

        }

    }

}

Если вы запустите наш пример, то сможете увидеть, что вывод на печать в секции finally вызывается в обоих случаях.

Множество исключений

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

try {

  ....

} catch(Exception1 ex) {

  // Обработка исключения класса Exception1

} catch(Exception2 ex) {

  // Обработка исключения класса Exception2

} catch(Exception3 ex) {

  // Обработка исключения класса Exception3

}

Рассмотрим несложный пример — класс Generator в методе helloMessage порождает два исключения, а класс Starter будет ловить эти два исключения.

package edu.javacourse.exception;

public class Generator

{

    public String helloMessage(String name) throws FirstException, SecondException

    {

        if («FIRST».equals(name)) {

            throw new FirstException(«FirstException occured»);

        }

        if(«SECOND».equals(name)) {

            throw new SecondException(«SecondException occured»);

        }

        return «Hello, « + name;

    }

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        Generator generator = new Generator();

        try {

            String answer = generator.helloMessage(«FIRST»);

            //String answer = generator.helloMessage(«SECOND»);

            //String answer = generator.helloMessage(«OTHER»);

            System.out.println(«Answer 1:» + answer);

        } catch (FirstException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        } catch (SecondException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        }

    }

}

Ниже код для двух классов исключений.

package edu.javacourse.exception;

public class FirstException extends Exception

{

    public FirstException(String message)

    {

        super(message);

    }

}

package edu.javacourse.exception;

public class SecondException extends Exception

{

    public SecondException(String message) {

        super(message);

    }

}

Как видим ничего особенно сложного нет. В Java 1.7 появилась более компактная конструкция, в которой классы исключений перечисляются в одном catch через знак «|». Вот так:

catch (FirstException | SecondException ex) {
System.out.println(«Error message:» + ex.getMessage());
}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        Generator generator = new Generator();

        try {

            String answer = generator.helloMessage(«FIRST»);

            //String answer = generator.helloMessage(«SECOND»);

            //String answer = generator.helloMessage(«OTHER»);

            System.out.println(«Answer 1:» + answer);

        } catch (FirstException | SecondException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        }

    }

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        Generator generator = new Generator();

        try {

            String answer = generator.helloMessage(«FIRST»);

            //String answer = generator.helloMessage(«SECOND»);

            //String answer = generator.helloMessage(«OTHER»);

            System.out.println(«Answer 1:» + answer);

        } catch (Exception ex) {

            System.out.println(«Error message:» + ex.getMessage());

        } catch (FirstException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        } catch (SecondException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        }

    }

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package edu.javacourse.exception;

public class Starter

{

    public static void main(String[] args)

    {

        Generator generator = new Generator();

        try {

            String answer = generator.helloMessage(«FIRST»);

            //String answer = generator.helloMessage(«SECOND»);

            //String answer = generator.helloMessage(«OTHER»);

            System.out.println(«Answer 1:» + answer);

        } catch (FirstException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        } catch (SecondException ex) {

            System.out.println(«Error message:» + ex.getMessage());

        } catch (Exception ex) {

            System.out.println(«Error message:» + ex.getMessage());

        }

    }

}

Этим приемом нередко пользуется — учитесь строить, читать и распознавать такие конструкции.

Класс RuntimeException

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

Пожелания

Самое главное пожелание — не оставляйте блоки catch без информации. Очень неплохим подспорьем будет вызов метода printStackTrace. Очень важно понимать. что случилось, почему. И если блок catch не будет информативным, то выяснение причин будет крайне сложным занятием.
Ужасным кодом будет нечто вроде этого:

try {

  ...

} catch(Exception ex) {

  System.out.println(«Произошло исключение»);

}

Что здесь можно понять в случае возникновения исключения ? НИЧЕГО ? Уважайте свой труд — выводите подробную информацию.

Что дальше ?

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

И теперь нас ждет следующая статья: Решения на основе классов.

Java_Deep_7.4-5020-83cb21.png

JavaSpec_Welcome_970x90-1801-439a19.png

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

Что называют исключением. Исключения в мире программирования

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

При этом в отличие от «человеческого мира», программное приложение должно чётко понимать, как поступать в подобной ситуации. И вот как раз для этого в Java и существует механизм исключений (exception).

Используемые ключевые слова

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

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

Кроме того:
1. Для возбуждения исключения используем throw.
2. Для предупреждения в сигнатуре методов о том, что метод может выбросить исключение, применяем throws.

Давайте на примере посмотрим, как используются ключевые слова в Java-программе:

//метод считывает строку с клавиатуры

public String input() throws MyException {//предупреждаем с помощью throws,
// что метод может выбросить исключение MyException
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String s = null;
//в блок try заключаем код, в котором может произойти исключение, в данном
// случае компилятор нам подсказывает, что метод readLine() класса
// BufferedReader может выбросить исключение ввода/вывода
    try {
        s = reader.readLine();
// в блок  catch заключаем код по обработке исключения IOException
    } catch (IOException e) {
        System.out.println(e.getMessage());
// в блоке finally закрываем поток чтения
    } finally {
// при закрытии потока тоже возможно исключение, например, если он не был открыт, поэтому “оборачиваем” код в блок try
        try {
            reader.close();
// пишем обработку исключения при закрытии потока чтения
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    if (s.equals("")) {
// мы решили, что пустая строка может нарушить в дальнейшем работу нашей программы, например, на результате этого метода нам надо вызывать метод substring(1,2), поэтому мы вынуждены прервать выполнение программы с генерацией своего типа исключения MyException с помощью throw
        throw new MyException("String can not be empty!");
    }
    return s;
}

Зачем нам механизм исключений?

Для понимания опять приведём пример из обычного мира. Представьте, что на какой-нибудь автодороге имеется участок с аварийным мостом, на котором ограничена грузоподъёмность. И если по такому мосту проедет грузовик со слишком большой массой, мост разрушится, а в момент этого ЧП ситуация для шофёра станет, мягко говоря, исключительной. И вот, дабы такого не произошло, дорожные службы заранее устанавливают на дороге соответствующие предупреждающие знаки. И тогда водитель, посмотрев на знак, сравнит массу своего авто со значением разрешённой грузоподъёмности и примет соответствующее решение, например, поедет по другой дороге.

То есть мы видим, что из-за правильных действий дорожной службы шоферы крупногабаритных транспортных средств:
1) получили возможность заранее изменить свой путь;
2) были предупреждены об опасности;
3) были предупреждены о невозможности проезжать по мосту при определённых условиях.

Вот как наш жизненный пример соотносится с применением исключения на Java:

1-20219-9bd18c.jpg

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

Что же, давайте ещё раз побудем дорожной службой. Чтобы установить знак, мы ведь должны знать места, где водителей ТС могут ждать различные неприятности. Это первое. Далее, нам ведь надо заготовить и установить знаки. Это второе. И, наконец, надо предусмотреть маршруты объезда, позволяющие избежать опасности.

В общем, механизм исключений в Java работает схожим образом. На стадии разработки программы мы выполняем «ограждение» опасных участков кода в отношении наших исключений, используя блок try{}. Чтобы предусмотреть запасные пути, применяем блок catch{}. Код, выполняемый в программе при любом исходе, пишем в блоке finally{}.

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

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

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

Предупреждаем о неприятностях

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

После упоминания ключевого слова throws мы указываем тип исключения. Как правило, речь идёт о наследниках класса Exception Java. Но так как Java — это объектно-ориентированный язык программирования, все исключения представляют собой объекты.

2-20219-ee1e82.jpg

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

Когда возникают ошибки при выполнении программы, исполняющая среда Java Virtual Machine обеспечивает создание объекта нужного типа, используя иерархию исключений Java — речь идёт о множестве возможных исключительных ситуаций, которые унаследованы от класса Throwable — общего предка. При этом исключительные ситуации, которые возникают в программе, делят на 2 группы:
1. Ситуации, при которых восстановление нормальной дальнейшей работы невозможно.
2. Ситуации с возможностью восстановления.

К первой группе можно отнести случаи, при которых возникают исключения, которые унаследованы из класса Error. Это ошибки, возникающие во время выполнения программы при сбое работы Java Virtual Machine, переполнении памяти либо сбое системы. Как правило, такие ошибки говорят о серьёзных проблемах, устранение которых программными средствами невозможно. Данный вид исключений в Java относят к неконтролируемым исключениям на стадии компиляции (unchecked). К этой же группе относятся и исключения-наследники класса Exception, генерируемые Java Virtual Machine в процессе выполнения программы — RuntimeException. Данные исключения тоже считаются unchecked на стадии компиляции, а значит, написание кода по их обработке необязательно.

Что касается второй группы, то к ней относят ситуации, которые можно предвидеть ещё на стадии написания приложения, поэтому для них код обработки должен быть написан. Это контролируемые исключения (checked). И в большинстве случаев Java-разработчики работают именно с этими исключениями, выполняя их обработку.

Создание исключения

В процессе исполнения программы исключение генерируется Java Virtual Machine либо вручную посредством оператора throw. В таком случае в памяти происходит создание объекта исключения, выполнение основного кода прерывается, а встроенный в JVM обработчик исключений пробует найти способ обработать это самое исключение.

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

Обработка исключений в Java подразумевает создание блоков кода и производится в программе посредством конструкций try{}finally{}, try{}catch, try{}catch{}finally.

3-20219-4ec690.jpg

В процессе возбуждения исключения в try обработчик исключения ищется в блоке catch, который следует за try. При этом если в catch присутствует обработчик данного вида исключения, происходит передача управления ему. Если же нет, JVM осуществляет поиск обработчика данного типа исключения, используя для этого цепочку вызова методов. И так происходит до тех пор, пока не находится подходящий catch. После того, как блок catch выполнится, управление переходит в необязательный блок finally. Если подходящий блок catch найден не будет, Java Virtual Machine остановит выполнение программы, выведя стек вызовов методов под названием stack trace. Причём перед этим выполнится код блока finally при наличии такового.

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

public class Print {

     void print(String s) {
        if (s == null) {
            throw new NullPointerException("Exception: s is null!");
        }
        System.out.println("Inside method print: " + s);
    }

    public static void main(String[] args) {
        Print print = new Print();
        List list= Arrays.asList("first step", null, "second step");

        for (String s:list) {
            try {
                print.print(s);
            }
            catch (NullPointerException e) {
                System.out.println(e.getMessage());
                System.out.println("Exception was processed. Program continues");
            }
            finally {
                System.out.println("Inside bloсk finally");
            }
            System.out.println("Go program....");
            System.out.println("-----------------");
        }

    }
    }

А теперь глянем на результаты работы метода main:

Inside method print: first step
Inside bloсk finally
Go program....
-----------------
Exception: s is null!
Exception was processed. Program continues
Inside bloсk finally
Go program....
-----------------
Inside method print: second step
Inside bloсk finally
Go program....
-----------------

Блок finally чаще всего используют, чтобы закрыть открытые в try потоки либо освободить ресурсы. Но при написании программы уследить за закрытием всех ресурсов возможно не всегда. Чтобы облегчить жизнь разработчикам Java, была предложена конструкция try-with-resources, автоматически закрывающая ресурсы, открытые в try. Используя try-with-resources, мы можем переписать наш первый пример следующим образом:

public String input() throws MyException {
    String s = null;
    try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
        s = reader.readLine();
   } catch (IOException e) {
       System.out.println(e.getMessage());
   }
    if (s.equals("")){
        throw new MyException ("String can not be empty!");
    }
    return s;
}

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

public String input() {
    String s = null;
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        s = reader.readLine();
        if (s.equals("")) {
            throw new MyException("String can not be empty!");
        }
    } catch (IOException | MyException e) {
        System.out.println(e.getMessage());
    }
    return s;
}

Итоги

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

Основные вопросы об исключениях в Java

1.Что такое проверяемые и непроверяемые исключения?
Если говорить коротко, то первые должны быть явно пойманы в теле метода либо объявлены в секции throws метода. Вторые вызываются проблемами, которые не могут быть решены. Например, это нулевой указатель или деление на ноль. Проверяемые исключения очень важны, ведь от других программистов, использующих ваш API, вы ожидаете, что они знают, как обращаться с исключениями. К примеру, наиболее часто встречаемое проверяемое исключение — IOException, непроверяемое — RuntimeException.
2.Почему переменные, определённые в try, нельзя использовать в catch либо finally?
Давайте посмотрим на нижеследующий код. Обратите внимание, что строку s, которая объявлена в блоке try, нельзя применять в блоке catch. То есть данный код не скомпилируется.

try {
    File file = new File("path");
    FileInputStream fis = new FileInputStream(file);
    String s = "inside";
} catch (FileNotFoundException e) {
    e.printStackTrace();
    System.out.println(s);
}

А всё потому, что неизвестно, где конкретно в try могло быть вызвано исключение. Вполне вероятно, что оно было вызвано до объявления объекта.
3.Почему Integer.parseInt(null) и Double.parseDouble(null) вызывают разные исключения?
Это проблема JDK. Так как они были разработаны разными людьми, то заморачиваться вам над этим не стоит:

Integer.parseInt(null);
// вызывает java.lang.NumberFormatException: null

Double.parseDouble(null);
// вызывает java.lang.NullPointerException

4.Каковы основные runtime exceptions в Java?
Вот лишь некоторые из них:

IllegalArgumentException
ArrayIndexOutOfBoundsException

Их можно задействовать в операторе if, если условие не выполняется:

if (obj == null) {
   throw new IllegalArgumentException("obj не может быть равно null");

5.Возможно ли поймать в одном блоке catch несколько исключений?
Вполне. Пока классы данных исключений можно отследить вверх по иерархии наследования классов до одного и того же суперкласса, возможно применение только этого суперкласса.
6.Способен ли конструктор вызывать исключения?
Способен, ведь конструктор — это лишь особый вид метода.

class FileReader{
    public FileInputStream fis = null;

    public FileReader() throws IOException{
        File dir = new File(".");//get current directory
        File fin = new File(dir.getCanonicalPath() + File.separator + "not-existing-file.txt");
        fis = new FileInputStream(fin);
    }
}

7.Возможен ли вызов исключений в final?
В принципе, можете сделать таким образом:

public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            FileInputStream fis = new FileInputStream(file2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Но если желаете сохранить читабельность, объявите вложенный блок try-catch в качестве нового метода и вставьте вызов данного метода в блок finally.

finally. 
public static void main(String[] args) {
    File file1 = new File("path1");
    File file2 = new File("path2");
    try {

        FileInputStream fis = new FileInputStream(file1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        methodThrowException();
    }
}

JavaSpec_Welcome_970x90-1801-439a19.png

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Елочка работа над ошибками
  • Елочка переводчик ошибка formocr