В конструкции try catch после выполнения инструкции catch

Это первая часть статьи, посвященной такому языковому механизму Java как исключения (вторая (checked/unchecked) вот). Она имеет вводный характер и рассчитана на начинающих разработчиков или тех, кто только приступает к изучению языка.

Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).

1. Ключевые слова: try, catch, finally, throw, throws
2. Почему используем System.err, а не System.out
3. Компилятор требует вернуть результат (или требует молчать)
4. Нелокальная передача управления (nonlocal control transfer)
5. try + catch (catch — полиморфен)
6. try + catch + catch + …
7. try + finally
8. try + catch + finally
9. Вложенные try + catch + finally

1. Ключевые слова: try, catch, finally, throw, throws

Механизм исключительных ситуаций в Java поддерживается пятью ключевыми словами

  • try
  • catch
  • finally
  • throw
  • throws

«Магия» (т.е. некоторое поведение никак не отраженное в исходном коде и потому неповторяемое пользователем) исключений #1 заключается в том, что catch, throw, throws можно использовать исключительно с java.lang.Throwable или его потомками.

throws:
Годится

public class App {
    public static void main(String[] args) throws Throwable {}
}

Не годится

public class App {
    public static void main(String[] args) throws String {}
}

>> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String'

catch:
Годится

public class App {
    public static void main(String[] args) {
        try {
        } catch (Throwable t) {}
    }
}

Не годится

public class App {
    public static void main(String[] args) {
        try {
        } catch (String s) {}
    }
}

>> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String'

throw:
Годится

public class App {
    public static void main(String[] args) {
        // Error - потомок Throwable
        throw new Error();
    }
}

Не годится

public class App {
    public static void main(String[] args) {
        throw new String("Hello!");
    }
}

>> COMPILATION ERROR: Incompatible types: required 'java.lang.Throwable', found: 'java.lang.String'

Кроме того, throw требуется не-null аргумент, иначе NullPointerException в момент выполнения

public class App {
    public static void main(String[] args) {
        throw null;
    }
}

>> RUNTIME ERROR: Exception in thread "main" java.lang.NullPointerException

throw и new — это две независимых операции. В следующем коде мы независимо создаем объект исключения и «бросаем» его

public class App {
    public static void main(String[] args) {
        Error ref = new Error(); // создаем экземпляр
        throw ref;               // "бросаем" его
    }
}

>> RUNTIME ERROR: Exception in thread "main" java.lang.Error

Однако, попробуйте проанализировать вот это

public class App {
    public static void main(String[] args) {
        f(null);
    }
    public static void f(NullPointerException e) {
        try {
            throw e;
        } catch (NullPointerException npe) {
            f(npe);
        }
    }
}

>> RUNTIME ERROR: Exception in thread "main" java.lang.StackOverflowError

2. Почему используем System.err, а не System.out

System.out — buffered-поток вывода, а System.err — нет. Таким образом вывод может быть как таким

public class App {
    public static void main(String[] args) {
        System.out.println("sout");
        throw new Error();
    }
}
>> RUNTIME ERROR: Exception in thread "main" java.lang.Error
>> sout

Так и вот таким (err обогнало out при выводе в консоль)

public class App {
    public static void main(String[] args) {
        System.out.println("sout");
        throw new Error();
    }
}
>> sout
>> RUNTIME ERROR: Exception in thread "main" java.lang.Error

Давайте это нарисуем

                      буфер сообщений
                    +----------------+
                 +->| msg2 msg1 msg0 | --> out 
                /   +----------------+        
               /                                 +-> +--------+
ВАШЕ ПРИЛОЖЕНИЕ                                      | КОНСОЛЬ|
                                                +-> +--------+
                                               /
                 +------------------------> err
                 нет буфера, сразу печатаем

когда Вы пишете в System.err — ваше сообщение тут же выводится на консоль, но когда пишете в System.out, то оно может на какое-то время быть буферизированно. Stacktrace необработанного исключение выводится через System.err, что позволяет им обгонять «обычные» сообщения.

3. Компилятор требует вернуть результат (или требует молчать)

Если в объявлении метода сказано, что он возвращает НЕ void, то компилятор зорко следит, что бы мы вернули экземпляр требуемого типа или экземпляр типа, который можно неявно привести к требуемому

public class App { 
    public double sqr(double arg) { // надо double
        return arg * arg;           // double * double - это double  
    }
}

public class App { 
    public double sqr(double arg) { // надо double
        int k = 1;                  // есть int
        return k;                   // можно неявно преобразовать int в double
    }
}

// на самом деле, компилятор сгенерирует байт-код для следующих исходников 
public class App { 
    public double sqr(double arg) { // надо double
        int k = 1;                  // есть int
        return (double) k;          // явное преобразование int в double
    }
}

вот так не пройдет (другой тип)

public class App {
    public static double sqr(double arg) {
        return "hello!";
    }
}

>> COMPILATION ERROR: Incompatible types. Required: double. Found: java.lang.String

Вот так не выйдет — нет возврата

public class App {
    public static double sqr(double arg) {
    }
}

>> COMPILATION ERROR: Missing return statement

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

public class App {
    public static double sqr(double arg) {
        if (System.currentTimeMillis() % 2 == 0) {
            return arg * arg; // если currentTimeMillis() - четное число, то все ОК
        }
        // а если нечетное, что нам возвращать?
    }
}

>> COMPILATION ERROR: Missing return statement

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

public class App {
    public static void main(String[] args) {
        double d = sqr(10.0); // ну, и чему равно d?
        System.out.println(d);
    }    
    public static double sqr(double arg) {
        // nothing
    }
}

>> COMPILATION ERROR: Missing return statement

Из-забавного, можно ничего не возвращать, а «повесить метод»

public class App {
    public static double sqr(double arg) {
        while (true); // Удивительно, но КОМПИЛИРУЕТСЯ!
    }
}

Тут в d никогда ничего не будет присвоено, так как метод sqr повисает

public class App {
    public static void main(String[] args) {
        double d = sqr(10.0);  // sqr - навсегда "повиснет", и 
        System.out.println(d); // d - НИКОГДА НИЧЕГО НЕ БУДЕТ ПРИСВОЕНО!
    }    
    public static double sqr(double arg) {
        while (true); // Вот тут мы на века "повисли"
    }
}

Компилятор пропустит «вилку» (таки берем в квадрат ИЛИ висим)

public class App {
    public static double sqr(double arg) {
        if (System.currentTimeMillis() % 2 == 0) {
            return arg * arg; // ну ладно, вот твой double
        } else {
            while (true);     // а тут "виснем" навсегда
        }
    }
}

Но механизм исключений позволяет НИЧЕГО НЕ ВОЗВРАЩАТЬ!

public class App {
    public static double sqr(double arg) {
        throw new RuntimeException();
    }
}

Итак, у нас есть ТРИ варианта для компилятора

public class App {
    public static double sqr(double arg) {// согласно объявлению метода ты должен вернуть double
        long time = System.currentTimeMillis();
        if (time % 2 == 0) {
            return arg * arg;             // ок, вот твой double
        } else if (time % 2 == 1) { {
            while (true);                 // не, я решил "повиснуть"
        } else {
            throw new RuntimeException(); // или бросить исключение
        }
    }
}

Но КАКОЙ ЖЕ double вернет функция, бросающая RuntimeException?
А НИКАКОЙ!

public class App {
    public static void main(String[] args) {
        // sqr - "сломается" (из него "выскочит" исключение),  
        double d = sqr(10.0);  // выполнение метода main() прервется в этой строчке и
                               // d - НИКОГДА НИЧЕГО НЕ БУДЕТ ПРИСВОЕНО!
        System.out.println(d); // и печатать нам ничего не придется!
    }    
    public static double sqr(double arg) {
        throw new RuntimeException(); // "бросаем" исключение
    }
}

>> RUNTIME ERROR: Exception in thread "main" java.lang.RuntimeException

Подытожим: бросаемое исключение — это дополнительный возвращаемый тип. Если ваш метод объявил, что возвращает double, но у вас нет double — можете бросить исключение. Если ваш метод объявил, что ничего не возвращает (void), но у вам таки есть что сказать — можете бросить исключение.

Давайте рассмотрим некоторый пример из практики.

Задача: реализовать функцию, вычисляющую площадь прямоугольника

public static int area(int width, int height) {...}

важно, что задание звучит именно так, в терминах предметной области — «вычислить площадь прямоугольника», а не в терминах решения «перемножить два числа»:

public static int area(int width, int height) {
    return width * height; // тут просто перемножаем
}

Вопрос: что делать, если мы обнаружили, что хотя бы один из аргументов — отрицательное число?
Если просто умножить, то мы пропустили ошибочные данные дальше. Что еще хуже, возможно, мы «исправили ситуацию» — сказали что площадь прямоугольника с двумя отрицательными сторонами -10 и -20 = 200.

Мы не можем ничего не вернуть

public static int area(int width, int height) {
    if (width < 0 || height < 0) {
        // у вас плохие аргументы, извините
    } else {
        return width * height;
    }
}

>> COMPILATION ERROR: Missing return statement

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

public static int area(int width, int height) {
    if (width < 0 || height < 0) {
        System.out.println("Bad ...");
    }
    return width * height;
}

Можно вернуть специальное значение, показывающее, что что-то не так (error code), но кто гарантирует, что его прочитают, а не просто воспользуются им?

public static int area(int width, int height) {
    if (width < 0 || height < 0) {
        return -1; // специальное "неправильное" значение площади
    }
    return width * height;
}

Можем, конечно, целиком остановить виртуальную машину

public static int area(int width, int height) {
    if (width < 0 || height < 0) {
        System.exit(0);
    }
    return width * height;
}

Но «правильный путь» таков: если обнаружили возможное некорректное поведение, то
1. Вычисления остановить, сгенерировать сообщение-поломку, которое трудно игнорировать, предоставить пользователю информацию о причине, предоставить пользователю возможность все починить (загрузить белье назад и повторно нажать кнопку старт)

public static int area(int width, int height) {
    if (width < 0 || height < 0) {
        throw new IllegalArgumentException("Negative sizes: w = " + width + ", h = " + height);
    }
    return width * height;
}

4. Нелокальная передача управления (nonlocal control transfer)

Механизм исключительных ситуация (исключений) — это механизм НЕЛОКАЛЬНОЙ ПЕРЕДАЧИ УПРАВЛЕНИЯ.
Что под этим имеется в виду?
Программа, в ходе своего выполнения (точнее исполнения инструкций в рамках отдельного потока), оперирует стеком («стопкой») фреймов. Передача управления осуществляется либо в рамках одного фрейма

public class App {
    public static void main(String[] args) {
        // Пример: ОПЕРАТОР ПОСЛЕДОВАТЕЛЬНОСТИ
        int x = 42;    // первый шаг
        int y = x * x; // второй шаг
        x = x * y;     // третий шаг
        ...
    }
}

public class App {
    public static void main(String[] args) {
        // Пример: ОПЕРАТОР ВЕТВЛЕНИЯ
        if (args.length > 2) { первый шаг
            // второй шаг или тут
            ...
        } else {
            // или тут
            ...
        }
        // третий шаг
        ...
    }
}

public class App {
    public static void main(String[] args) {
        // Пример: ОПЕРАТОР ЦИКЛА do..while
        int x = 1;      
        do {
            ...
        } while (x++ < 10);
        ...
    }
}

и другие операторы.

Либо передача управления происходит в «стопке» фреймов между СОСЕДНИМИ фреймами

  • вызов метода: создаем новый фрейм, помещаем его на верхушку стека и переходим в него
  • выход из метода: возвращаемся к предыдущему фрейму (через return или просто кончились инструкции в методе)

return — выходим из ОДНОГО фрейма (из фрейма #4(метод h()))

public class App {
    public static void main(String[] args) {
        System.err.println("#1.in");
        f(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println("#1.out"); // вернулись
    } // выходим из текущего фрейма, кончились инструкции

    public static void f() {
        System.err.println(".   #2.in");
        g(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   #2.out");  //вернулись
    } // выходим из текущего фрейма, кончились инструкции

    public static void g() {
        System.err.println(".   .   #3.in");
        h(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   .   #3.out"); // вернулись
    } // выходим из текущего фрейма, кончились инструкции

    public static void h() {
        System.err.println(".   .   .   #4.in");
        if (true) {
            System.err.println(".   .   .   #4.RETURN");
            return; // выходим из текущего фрейма по 'return'
        }
        System.err.println(".   .   .   #4.out"); // ПРОПУСКАЕМ
    }
}

>> #1.in
>> .   #2.in
>> .   .   #3.in
>> .   .   .   #4.in
>> .   .   .   #4.RETURN
>> .   .   #3.out
>> .   #2.out
>> #1.out

throw — выходим из ВСЕХ фреймов

public class App {
    public static void main(String[] args) {
        System.err.println("#1.in");
        f(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println("#1.out"); // ПРОПУСТИЛИ!
    }

    public static void f() {
        System.err.println(".   #2.in");
        g(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   #2.out"); // ПРОПУСТИЛИ!
    }

    public static void g() {
        System.err.println(".   .   #3.in");
        h(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   .   #3.out"); // ПРОПУСТИЛИ!
    }

    public static void h() {
        System.err.println(".   .   .   #4.in");
        if (true) {
            System.err.println(".   .   .   #4.THROW");
            throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw'
        }
        System.err.println(".   .   .   #4.out"); // ПРОПУСТИЛИ!
    }
}

>> #1.in
>> .   #2.in
>> .   .   #3.in
>> .   .   .   #4.in
>> .   .   .   #4.THROW
>> RUNTIME ERROR: Exception in thread "main" java.lang.Error

При помощи catch мы можем остановить летящее исключение (причина, по которой мы автоматически покидаем фреймы).
Останавливаем через 3 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g()) + фрейм #2(метод f())

public class App {
    public static void main(String[] args) {
        System.err.println("#1.in");
        try {
            f(); // создаем фрейм, помещаем в стек, передаем в него управление
        } catch (Error e) { // "перехватили" "летящее" исключение
            System.err.println("#1.CATCH");  // и работаем
        }
        System.err.println("#1.out");  // работаем дальше
    }

    public static void f() {
        System.err.println(".   #2.in");
        g(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   #2.out"); // ПРОПУСТИЛИ!
    }

    public static void g() {
        System.err.println(".   .   #3.in");
        h(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   .   #3.out"); // ПРОПУСТИЛИ!
    }

    public static void h() {
        System.err.println(".   .   .   #4.in");
        if (true) {
            System.err.println(".   .   .   #4.THROW");
            throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw'
        }
        System.err.println(".   .   .   #4.out"); // ПРОПУСТИЛИ!
    }
}

>> #1.in
>> .   #2.in
>> .   .   #3.in
>> .   .   .   #4.in
>> .   .   .   #4.THROW
>> #1.CATCH
>> #1.out

Обратите внимание, стандартный сценарий работы был восстановлен в методе main() (фрейм #1)

Останавливаем через 2 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g())

public class App {
    public static void main(String[] args) {
        System.err.println("#1.in");
        f(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println("#1.out"); // вернулись и работаем
    }

    public static void f() {
        System.err.println(".   #2.in");
        try {
            g(); // создаем фрейм, помещаем в стек, передаем в него управление
        } catch (Error e) { // "перехватили" "летящее" исключение
            System.err.println(".   #2.CATCH");  // и работаем
        }
        System.err.println(".   #2.out");  // работаем дальше
    }

    public static void g() {
        System.err.println(".   .   #3.in");
        h(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   .   #3.out"); // ПРОПУСТИЛИ!
    }

    public static void h() {
        System.err.println(".   .   .   #4.in");
        if (true) {
            System.err.println(".   .   .   #4.THROW");
            throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw'
        }
        System.err.println(".   .   .   #4.out"); // ПРОПУСТИЛИ!
    }
}

>> #1.in
>> .   #2.in
>> .   .   #3.in
>> .   .   .   #4.in
>> .   .   .   #4.THROW
>> .   #2.CATCH
>> .   #2.out
>> #1.out

Останавливаем через 1 фрейм (фактически аналог return, просто покинули фрейм «другим образом»)

public class App {
    public static void main(String[] args) {
        System.err.println("#1.in");
        f(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println("#1.out"); // вернулись и работаем
    }

    public static void f() {
        System.err.println(".   #2.in");
        g(); // создаем фрейм, помещаем в стек, передаем в него управление
        System.err.println(".   #2.out"); // вернулись и работаем
    }

    public static void g() {
        System.err.println(".   .   #3.in");
        try {
            h(); // создаем фрейм, помещаем в стек, передаем в него управление
        } catch (Error e) { // "перехватили" "летящее" исключение
            System.err.println(".   .   #3.CATCH");  // и работаем
        }
        System.err.println(".   .   #3.out");  // работаем дальше
    }

    public static void h() {
        System.err.println(".   .   .   #4.in");
        if (true) {
            System.err.println(".   .   .   #4.THROW");
            throw new Error(); // выходим со всей пачки фреймов ("раскрутка стека") по 'throw'
        }
        System.err.println(".   .   .   #4.out"); // ПРОПУСТИЛИ!
    }
}

>> #1.in
>> .   #2.in
>> .   .   #3.in
>> .   .   .   #4.in
>> .   .   .   #4.THROW
>> .   .   #3.CATCH
>> .   .   #3.out
>> .   #2.out
>> #1.out

Итак, давайте сведем все на одну картинку

// ---Используем RETURN--- // ---Используем THROW---
// Выход из 1-го фрейма    // Выход из ВСЕХ (из 4) фреймов
#1.in                        #1.in
.   #2.in                    .   #2.in
.   .   #3.in                .   .   #3.in
.   .   .   #4.in            .   .   .   #4.in
.   .   .   #4.RETURN        .   .   .   #4.THROW
.   .   #3.out               RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error
.   #2.out                            
#1.out                              

// ---Используем THROW+CATCH---
// Выход из 3-х фреймов      // Выход из 2-х фреймов      // Выход из 1-го фрейма
#1.in                        #1.in                        #1.in
.   #2.in                    .   #2.in                    .   #2.in
.   .   #3.in                .   .   #3.in                .   .   #3.in
.   .   .   #4.in            .   .   .   #4.in            .   .   .   #4.in
.   .   .   #4.THROW         .   .   .   #4.THROW         .   .   .   #4.THROW
#1.CATCH                     .   #2.CATCH                 .   .   #3.CATCH
#1.out                       .   #2.out                   .   .   #3.out
                             #1.out                       . #2.out
                                                          #1.out

5. try + catch (catch — полиморфен)

Напомним иерархию исключений

                    Object
                      |
                  Throwable
                  /      
              Error     Exception
                            |
                    RuntimeException

То, что исключения являются объектами важно для нас в двух моментах
1. Они образуют иерархию с корнем java.lang.Throwable (java.lang.Object — предок java.lang.Throwable, но Object — уже не исключение)
2. Они могут иметь поля и методы (в этой статье это не будем использовать)

По первому пункту: catch — полиморфная конструкция, т.е. catch по типу Parent перехватывает летящие экземпляры любого типа, который является Parent-ом (т.е. экземпляры непосредственно Parent-а или любого потомка Parent-а)

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch (Exception e) { // catch по Exception ПЕРЕХВАТЫВАЕТ RuntimeException
            System.err.print(" 2");
        }
        System.err.println(" 3");
    }
}

>> 0 2 3

Даже так: в блоке catch мы будем иметь ссылку типа Exception на объект типа RuntimeException

public class App {
    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                RuntimeException re = (RuntimeException) e;
                System.err.print("Это RuntimeException на самом деле!!!");              
            } else {
                System.err.print("В каком смысле не RuntimeException???");              
            }            
        }
    }
}

>> Это RuntimeException на самом деле!!!

catch по потомку не может поймать предка

public class App {
    public static void main(String[] args) throws Exception { // пока игнорируйте 'throws'
        try {
            System.err.print(" 0");
            if (true) {throw new Exception();}
            System.err.print(" 1");
        } catch (RuntimeException e) {
            System.err.print(" 2");              
        }
        System.err.print(" 3");              
    }
}

>> 0 
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Exception

catch по одному «брату» не может поймать другого «брата» (Error и Exception не находятся в отношении предок-потомок, они из параллельных веток наследования от Throwable)

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new Error();}
            System.err.print(" 1");
        } catch (Exception e) {
            System.err.print(" 2");              
        }
        System.err.print(" 3");              
    }
}

>> 0 
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error

По предыдущим примерам — надеюсь вы обратили внимание, что если исключение перехвачено, то JVM выполняет операторы идущие ПОСЛЕ последних скобок try+catch.
Но если не перехвачено, то мы
1. не заходим в блок catch
2. покидаем фрейм метода с летящим исключением

А что будет, если мы зашли в catch, и потом бросили исключение ИЗ catch?

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch (RuntimeException e) {     // перехватили RuntimeException
            System.err.print(" 2");
            if (true) {throw new Error();} // но бросили Error
        }
        System.err.println(" 3");          // пропускаем - уже летит Error
    }
}

>> 0 2
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error

В таком случае выполнение метода тоже прерывается (не печатаем «3»). Новое исключение не имеет никакого отношения к try-catch

Мы можем даже кинуть тот объект, что у нас есть «на руках»

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch (RuntimeException e) { // перехватили RuntimeException
            System.err.print(" 2");
            if (true) {throw e;}       // и бросили ВТОРОЙ раз ЕГО ЖЕ
        }
        System.err.println(" 3");      // пропускаем - опять летит RuntimeException
    }
}

>> 0 2
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.RuntimeException

И мы не попадем в другие секции catch, если они есть

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch (RuntimeException e) {     // перехватили RuntimeException
            System.err.print(" 2");
            if (true) {throw new Error();} // и бросили новый Error
        } catch (Error e) { // хотя есть cath по Error "ниже", но мы в него не попадаем
            System.err.print(" 3");
        }
        System.err.println(" 4");
    }
}

>> 0 2
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error 

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

Как покажем ниже — можно строить вложенные конструкции, но вот пример, «исправляющий» эту ситуацию

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch (RuntimeException e) { // перехватили RuntimeException
            System.err.print(" 2.1");
            try {
                System.err.print(" 2.2");
                if (true) {throw new Error();} // и бросили новый Error
                System.err.print(" 2.3");
            } catch (Throwable t) {            // перехватили Error
                System.err.print(" 2.4");                 
            }
            System.err.print(" 2.5");
        } catch (Error e) { // хотя есть cath по Error "ниже", но мы в него не попадаем
            System.err.print(" 3");
        }
        System.err.println(" 4");
    }
}

>> 0 2.1 2.2 2.4 2.5 4

6. try + catch + catch + …

Как вы видели, мы можем расположить несколько catch после одного try.

Но есть такое правило — нельзя ставить потомка после предка! (RuntimeException после Exception)

public class App {
    public static void main(String[] args) {
        try {
        } catch (Exception e) {
        } catch (RuntimeException e) {
        }
    }
}

>> COMPILATION ERROR: Exception 'java.lang.RuntimeException' has alredy been caught

Ставить брата после брата — можно (RuntimeException после Error)

public class App {
    public static void main(String[] args) {
        try {
        } catch (Error e) {
        } catch (RuntimeException e) {
        }
    }
}

Как происходит выбор «правильного» catch? Да очень просто — JVM идет сверху-вниз до тех пор, пока не найдет такой catch что в нем указано ваше исключение или его предок — туда и заходит. Ниже — не идет.

public class App {
    public static void main(String[] args) {
        try {
            throw new Exception();
        } catch (RuntimeException e) {
            System.err.println("catch RuntimeException");
        } catch (Exception e) {
            System.err.println("catch Exception");
        } catch (Throwable e) {
            System.err.println("catch Throwable");
        }
        System.err.println("next statement");
    }
}

>> catch Exception
>> next statement

Выбор catch осуществляется в runtime (а не в compile-time), значит учитывается не тип ССЫЛКИ (Throwable), а тип ССЫЛАЕМОГО (Exception)

public class App {
    public static void main(String[] args) {
        try {
            Throwable t = new Exception(); // ссылка типа Throwable указывает на объект типа Exception
            throw t;
        } catch (RuntimeException e) {
            System.err.println("catch RuntimeException");
        } catch (Exception e) {
            System.err.println("catch Exception");
        } catch (Throwable e) {
            System.err.println("catch Throwable");
        }
        System.err.println("next statement");
    }
}

>> catch Exception
>> next statement

7. try + finally

finally-секция получает управление, если try-блок завершился успешно

public class App {
    public static void main(String[] args) {
        try {
            System.err.println("try");
        } finally {
            System.err.println("finally");
        }
    }
}

>> try
>> finally

finally-секция получает управление, даже если try-блок завершился исключением

public class App {
    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        } finally {
            System.err.println("finally");
        }
    }
}

>> finally
>> Exception in thread "main" java.lang.RuntimeException

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

public class App {
    public static void main(String[] args) {
        try {
            return;
        } finally {
            System.err.println("finally");
        }
    }
}

>> finally

finally-секция НЕ вызывается только если мы «прибили» JVM

public class App {
    public static void main(String[] args) {
        try {
            System.exit(42);
        } finally {
            System.err.println("finally");
        }
    }
}

>> Process finished with exit code 42

System.exit(42) и Runtime.getRuntime().exit(42) — это синонимы

public class App {
    public static void main(String[] args) {
        try {
            Runtime.getRuntime().exit(42);
        } finally {
            System.err.println("finally");
        }
    }
}

>> Process finished with exit code 42

И при Runtime.getRuntime().halt(42) — тоже не успевает зайти в finally

public class App {
    public static void main(String[] args) {
        try {
            Runtime.getRuntime().halt(42);
        } finally {
            System.err.println("finally");
        }
    }
}

>> Process finished with exit code 42

exit() vs halt()
javadoc: java.lang.Runtime#halt(int status)
… Unlike the exit method, this method does not cause shutdown hooks to be started and does not run uninvoked finalizers if finalization-on-exit has been enabled. If the shutdown sequence has already been initiated then this method does not wait for any running shutdown hooks or finalizers to finish their work.

Однако finally-секция не может «починить» try-блок завершившийся исключение (заметьте, «more» — не выводится в консоль)

public class App {
    public static void main(String[] args) {
        try {
            System.err.println("try");
            if (true) {throw new RuntimeException();}
        } finally {
            System.err.println("finally");
        }
        System.err.println("more");
    }
}

>> try
>> finally
>> Exception in thread "main" java.lang.RuntimeException

Трюк с «if (true) {…}» требуется, так как иначе компилятор обнаруживает недостижимый код (последняя строка) и отказывается его компилировать

public class App {
    public static void main(String[] args) {
        try {
            System.err.println("try");
            throw new RuntimeException();
        } finally {
            System.err.println("finally");
        }
        System.err.println("more");
    }
}

>> COMPILER ERROR: Unrechable statement 

И finally-секция не может «предотвратить» выход из метода, если try-блок вызвал return («more» — не выводится в консоль)

public class App {
    public static void main(String[] args) {
        try {
            System.err.println("try");
            if (true) {return;}
        } finally {
            System.err.println("finally");
        }
        System.err.println("more");
    }
}

>> try
>> finally

Однако finally-секция может «перебить» throw/return при помощи другого throw/return

public class App {
    public static void main(String[] args) {
        System.err.println(f());
    }
    public static int f() {
        try {
            return 0;
        } finally {
            return 1;
        }
    }
}

>> 1
public class App {
    public static void main(String[] args) {
        System.err.println(f());
    }
    public static int f() {
        try {
            throw new RuntimeException();
        } finally {
            return 1;
        }
    }
}

>> 1
public class App {
    public static void main(String[] args) {
        System.err.println(f());
    }
    public static int f() {
        try {
            return 0;
        } finally {
            throw new RuntimeException();
        }
    }
}

>> Exception in thread "main" java.lang.RuntimeException
public class App {
    public static void main(String[] args) {
        System.err.println(f());
    }
    public static int f() {
        try {
            throw new Error();
        } finally {
            throw new RuntimeException();
        }
    }
}

>> Exception in thread "main" java.lang.RuntimeException

finally-секция может быть использована для завершающего действия, которое гарантированно будет вызвано (даже если было брошено исключение или автор использовал return) по окончании работы

// open some resource
try {
    // use resource
} finally {
    // close resource
}

Например для освобождения захваченной блокировки

Lock lock = new ReentrantLock();
...
lock.lock();
try {
    // some code
} finally {
    lock.unlock();
}

Или для закрытия открытого файлового потока

InputStream input = new FileInputStream("...");
try {
    // some code
} finally {
    input.close();
}

Специально для этих целей в Java 7 появилась конструкция try-with-resources, ее мы изучим позже.

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

public class App {
    public static void main(String[] args) {
        System.err.println(f());
    }
    public static int f() {
        long rnd = System.currenttimeMillis();
        boolean finished = false;
        try {
            if (rnd % 3 == 0) {
                throw new Error();
            } else if (rnd % 3 == 1) {
                throw new RuntimeException();
            } else {
                // nothing
            }
            finished = true;
        } finally {
            if (finished) {
                // не было исключений
            } else {
                // было исключение, но какое?
            }
        }
    }
}

Не рекомендуемые практики
— return из finally-секции (можем затереть исключение из try-блока)
— действия в finally-секции, которые могут бросить исключение (можем затереть исключение из try-блока)

8. try + catch + finally

Нет исключения

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            // nothing
            System.err.print(" 1");
        } catch(Error e) {
            System.err.print(" 2");
        } finally {
            System.err.print(" 3");
        }
        System.err.print(" 4");
    }
}

>> 0 1 3 4

Не заходим в catch, заходим в finally, продолжаем после оператора

Есть исключение и есть подходящий catch

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new Error();}
            System.err.print(" 1");
        } catch(Error e) {
            System.err.print(" 2");
        } finally {
            System.err.print(" 3");
        }
        System.err.print(" 4");
    }
}

>> 0 2 3 4

Заходим в catch, заходим в finally, продолжаем после оператора

Есть исключение но нет подходящего catch

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            if (true) {throw new RuntimeException();}
            System.err.print(" 1");
        } catch(Error e) {
            System.err.print(" 2");
        } finally {
            System.err.print(" 3");
        }
        System.err.print(" 4");
    }
}

>> 0 3
>> RUNTIME ERROR: Exception in thread "main" java.lang.RuntimeException

Не заходим в catch, заходим в finally, не продолжаем после оператора — вылетаем с неперехваченным исключением

9. Вложенные try + catch + finally

Операторы обычно допускают неограниченное вложение.
Пример с if

public class App {
    public static void main(String[] args) {
        if (args.length > 1) {
            if (args.length > 2) {
                if (args.length > 3) {
                    ...
                }
            }
        }
    }
}

Пример с for

public class App {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; i++) {
                for (int k = 0; k < 10; k++) {
                    ...
                }
            }
        }
    }
}

Суть в том, что try-cacth-finally тоже допускает неограниченное вложение.
Например вот так

public class App {
    public static void main(String[] args) {
        try {
            try {
                try {
                    ...
                } catch (Exception e) {
                } finally {}
            } catch (Exception e) {
            } finally {}
        } catch (Exception e) {
        } finally {}
    }
}

Или даже вот так

public class App {
    public static void main(String[] args) {
        try {
            try {
                ...
            } catch (Exception e) {
                ...
            } finally {
                ...
            }
        } catch (Exception e) {
            try {
                ...
            } catch (Exception e) {
                ...
            } finally {
                ...
            }
        } finally {
            try {
                ...
            } catch (Exception e) {
                ...
            } finally {
                ...
            }
        }
    }
}

Ну что же, давайте исследуем как это работает.

Вложенный try-catch-finally без исключения

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            try {
                System.err.print(" 1");
                // НИЧЕГО
                System.err.print(" 2");
            } catch (RuntimeException e) {
                System.err.print(" 3"); // НЕ заходим - нет исключения
            } finally {                 
                System.err.print(" 4"); // заходим всегда
            }
            System.err.print(" 5");     // заходим - выполнение в норме
        } catch (Exception e) {
            System.err.print(" 6");     // НЕ заходим - нет исключения
        } finally {
            System.err.print(" 7");     // заходим всегда
        }
        System.err.print(" 8");         // заходим - выполнение в норме
    }
}

>> 0 1 2 4 5 7 8

Мы НЕ заходим в обе catch-секции (нет исключения), заходим в обе finally-секции и выполняем обе строки ПОСЛЕ finally.

Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНУТРЕННИЙ catch

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            try {
                System.err.print(" 1");
                if (true) {throw new RuntimeException();}
                System.err.print(" 2");
            } catch (RuntimeException e) {
                System.err.print(" 3"); // ЗАХОДИМ - есть исключение
            } finally {                 
                System.err.print(" 4"); // заходим всегда
            }
            System.err.print(" 5");     // заходим - выполнение УЖЕ в норме
        } catch (Exception e) {
            System.err.print(" 6");     // не заходим - нет исключения, УЖЕ перехвачено
        } finally {
            System.err.print(" 7");     // заходим всегда
        }
        System.err.print(" 8");         // заходим - выполнение УЖЕ в норме
    }
}

>> 0 1 3 4 5 7 8

Мы заходим в ПЕРВУЮ catch-секцию (печатаем «3»), но НЕ заходим во ВТОРУЮ catch-секцию (НЕ печатаем «6», так как исключение УЖЕ перехвачено первым catch), заходим в обе finally-секции (печатаем «4» и «7»), в обоих случаях выполняем код после finally (печатаем «5»и «8», так как исключение остановлено еще первым catch).

Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНЕШНИЙ catch

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            try {
                System.err.print(" 1");
                if (true) {throw new Exception();}
                System.err.print(" 2");
            } catch (RuntimeException e) {
                System.err.print(" 3"); // НЕ заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА
            } finally {                 
                System.err.print(" 4"); // заходим всегда
            }
            System.err.print(" 5");     // не заходим - выполнение НЕ в норме
        } catch (Exception e) {
            System.err.print(" 6");     // ЗАХОДИМ - есть подходящее исключение
        } finally {
            System.err.print(" 7");     // заходим всегда
        }
        System.err.print(" 8");         // заходим - выполнение УЖЕ в норме
    }
}

>> 0 1 4 6 7 8

Мы НЕ заходим в ПЕРВУЮ catch-секцию (не печатаем «3»), но заходим в ВТОРУЮ catch-секцию (печатаем «6»), заходим в обе finally-секции (печатаем «4» и «7»), в ПЕРВОМ случае НЕ выполняем код ПОСЛЕ finally (не печатаем «5», так как исключение НЕ остановлено), во ВТОРОМ случае выполняем код после finally (печатаем «8», так как исключение остановлено).

Вложенный try-catch-finally с исключением, которое НИКТО НЕ ПЕРЕХВАТИТ

public class App {
    public static void main(String[] args) {
        try {
            System.err.print(" 0");
            try {
                System.err.print(" 1");
                if (true) {throw new Error();}
                System.err.print(" 2");
            } catch (RuntimeException e) {
                System.err.print(" 3"); // НЕ заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА
            } finally {                 
                System.err.print(" 4"); // заходим всегда
            }
            System.err.print(" 5");     // НЕ заходим - выполнение НЕ в норме
        } catch (Exception e) {
            System.err.print(" 6");     // не заходим - есть исключение, но НЕПОДХОДЯЩЕГО ТИПА
        } finally {
            System.err.print(" 7");     // заходим всегда
        }
        System.err.print(" 8");         // не заходим - выполнение НЕ в норме
    }
}

>> 0 1 4 7
>> RUNTIME EXCEPTION: Exception in thread "main" java.lang.Error

Мы НЕ заходим в ОБЕ catch-секции (не печатаем «3» и «6»), заходим в обе finally-секции (печатаем «4» и «7») и в обоих случаях НЕ выполняем код ПОСЛЕ finally (не печатаем «5» и «8», так как исключение НЕ остановлено), выполнение метода прерывается по исключению.

Контакты

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

Мой метод обучения состоит в том, что я

  1. показываю различные варианты применения
  2. строю усложняющуюся последовательность примеров по каждому варианту
  3. объясняю логику двигавшую авторами (по мере возможности)
  4. даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
  5. даю лабораторные для самостоятельной работы

Данная статье следует пунктам #1 (различные варианты) и #2(последовательность примеров по каждому варианту).

skype: GolovachCourses
email: GolovachCourses@gmail.com

Обработка ошибок

Последнее обновление: 30.08.2021

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

Простейшая ситуация — вызов функции, которой не существует:

callSomeFunc();

console.log("Остальные инструкции");

Здесь вызывается функция callSomeFunc(), которая нигде не определена. Соответственно при вызове этой функции мы столкнемся с ошибкой:

Uncaught ReferenceError: callSomeFunc is not defined

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

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

Для обработки подобных ситуаций JavaScript предоставляет конструкцию try…catch…finally, которая имеет следующее формальное определение:

try {
  инструкции блока try
}
catch (error) {
  инструкции блока catch
}
finally {
  инструкции блока finally
}

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

Затем идет оператор catch. После этого оператора в круглых скобках указывается название объекта, который будет содержать информацию об ошибке. И далее идет
блок catch. Этот блок выполняется только при возникновении ошибки в блоке try.

После блока catch идет оператор finally со своим блоком инструкций. Этот блок выполняется в конце после блока try и catch вне
зависимости, возникла ошибка или нет.

Стоит отметить, что только блок try является обязательным. А один из остальных блоков — catch или
finally мы можем опустить. Однако один из этих блоков (не важно catch или finally) обязательно должен присуствовать. То есть мы можем использовать
следующие варианты этой конструкции:

  • try…catch

  • try…finally

  • try…catch…finally

Например, обработаем с помощью этой конструкцию предыдущую ситуацию с несуществующей функцией:

try{
	callSomeFunc();
	console.log("Конец блока try");
}
catch{
	console.log("Возникла ошибка!");
}
console.log("Остальные инструкции");

Итак, сначала выполняется блок try. Однако при выполнении первой же инструкции — вызова функции callSomeFunc() возникает ошибка. Это приведет к тому, что все последующие инструкции в
блоке try НЕ будут выполняться. А управление перейдет к блоку catch. В этом блоке выводится сообщение, что возникла ошибка.
После выполнения блока catch выполняются остальные инструкции программы. Таким образом, программа не прерывает свою работу при возникновении ошибки и продолжает свою работу.
И в данном случае консольный вывод будет следующим:

Возникла ошибка!
Остальные инструкции

Рассмотим другой пример:

function callSomeFunc(){console.log("Функция callSomeFunc");}
try{
	callSomeFunc();
	console.log("Конец блока try");
}
catch(error){
	console.log("Возникла ошибка!");
}

console.log("Остальные инструкции");

Теперь функция callSomeFunc() определена в прогамме, поэтому при вызове функции ошибки не произойдет, и блок try доработает до конца. А блок catch при отсутствии ошибки
не будет выолняться. И консольный вывод будет следующим:

Функция callSomeFunc
Конец блока try
Остальные инструкции

Получение ошибки в блоке catch

В качестве в качестве параметра в блок catch передается объект с информацией об ошибке:

try{
	callSomeFunc();
	console.log("Конец блока try");
}
catch(error){
	console.log("Возникла ошибка!");
	console.log(error);
}

В этом случае мы получим консольный вывод на подобие следующего:

Возникла ошибка!
ReferenceError: callSomeFunc is not defined
    at index.html:35

Блок finally

Конструкция try также может содержать блок finally. Мы можем использовать этот блок вместе с блоком catch или вместо него.
Блок finally выполняется вне зависимости, произошла ошибка или нет. Например:

try{
	callSomeFunc();
	console.log("Конец блока try");
}
catch{
	console.log("Произошла ошибка");
}
finally{
	console.log("Блок finally")
}

console.log("Остальные инструкции");

Консольный вывод программы:

Произошла ошибка
Блок finally
Остальные инструкции

I recently came across code written by a fellow programmer in which he had a try-catch statement inside a catch!

Please forgive my inability to paste the actual code, but what he did was something similar to this:

try
{
 //ABC Operation
}
catch (ArgumentException ae)
{
   try
   {
      //XYZ Operation
   }
   catch (IndexOutOfRangeException ioe)
   {
      //Something
   }
}

I personally feel that it is some of the poorest code I have ever seen!
On a scale of 1 to 10, how soon do you think I should go and give him a piece of my mind, or am I over-reacting?

EDIT:
What he is actually doing in the catch, he is performing some operations which can/should be done when the initial try fails. My problem is with having a clean code and maintainability. Delegating the exception from the first catch to a different function or the calling function would be OK, but adding more code which may or may not throw an exception into the first catch, is what I felt was not good. I try to avoid multiple stacked «if-loop» statements, I found this equally bad.

#База знаний

  • 20 янв 2022

  • 0

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

Иллюстрация: Dana Moskvina / Skillbox Media

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

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

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

Конструкция try-catch в языке Java помогает обрабатывать исключения. В блоке catch мы указываем класс исключений, которые «ловим». Взгляните на код ниже — если в try случится ArithmeticException, управление перейдёт к блоку catch и программа выдаст ошибку: Что ты делаешь, говорили же не делить на ноль!.

private static void hereWillBeTrouble(Integer a, Integer b) {
    int oops;
    try {
        oops = a / b;
    } catch (ArithmeticException e) {
        System.out.println("Что ты делаешь, говорили же не делить на ноль!");
        oops = 0;
    }
}

Но что будет, если в коде случится другое исключение, которое мы не предусматривали?

Зайдём издалека: в Java ссылочная переменная может ссылаться на любой объект, созданный из её дочернего класса. Например, если класс Child наследуется от класса Parent, то ссылка на переменную типа Child может храниться в переменной типа Parent: Parent man = new Child();.

Это правило работает везде — catch (ArithmeticException e) обработает любое исключение, если оно наследуется от ArithmeticException. Но если в блоке случится событие, которое не относится ни к ArithmeticException, ни к его классам-наследникам, try-catch для него не сработает.

Кроме ArithmeticException есть и другие исключения, одно из них — NullPointerException. Оно возникает, когда ссылка на объект хранит null и по ней пытаются получить значение поля объекта или вызвать его метод.

Пример:

Cat cat = null;
cat.getWeight();

На практике NullPointerException по сравнению с другими встроенными в Java исключениями возникает очень часто, поэтому у него есть сокращённое название: NPE.

Желательно писать код так, чтобы NPE не возникали. Для этого нужно, чтобы null никогда не передавался в качестве аргумента и не возвращался в результате метода null. Тогда и обрабатывать NPE не придётся.

Но если мы хотим перехватывать ArithmeticException и NullPointerException одновременно, после try можно написать сколько угодно catch и в каждом из них обработать нужное исключение:

private static void hereWillBeTrouble(Integer a, Integer b) {
    int oops;
    try {
        oops = a / b;
    } catch (ArithmeticException e) {
        System.out.println("Что ты делаешь, говорили же не делить на ноль!");
        oops = 0;
    } catch (NullPointerException e) {
        System.out.println("Кто-то из входящих аргументов равен NULL. Возможно, оба.");
        oops = 0;
    }
}

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

В нашем примере это произойдёт так: если в try произошло исключение, Java проверит — это ArithmeticException? Если да, то запустится код из первого catch. Если нет, Java пойдёт дальше. Возникло NPE? Тогда сработает второй catch. Если ничего не совпало, то ни один из catch не подошёл — программа «выбросит» исключение, как будто его никто и не «ловил».

Важный момент: управление программы либо попадёт в один из блоков catch, либо не попадёт никуда.

Взглянем на этот пример:

try {
    //Здесь какой-то код
} catch (ArithmeticException e) {
    System.out.println("Что ты делаешь, говорили же не делить на ноль!");
} catch (NullPointerException e) {
    System.out.println("Кто-то из входящих аргументов равен NULL. Возможно, оба.");
} catch (Exception e) {
    System.out.println("Что-то пошло не так. Но это точно не ArithmeticException и не NPE.");
}

Если в блоке try случится ArithmeticException или NullPointerException, сработает первый или второй catch. Если это будет другое исключение, Java проверит, не Exception ли это, и выполнит третий catch. Всё потому, что любое исключение наследуется от Exception. Мы писали об этом в первой статье.

ArithmeticException и NullPointerException — подклассы Exception, но такие исключения не попадут в третий catch, потому что их обработали в коде выше.

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

try {
    //Здесь какой-то код
} catch (Exception e) {
    System.out.println("Что-то пошло не так. Но это точно не ArithmeticException и не NPE.");
} catch (NullPointerException e) {
    System.out.println("Кто-то из входящих аргументов равен NULL. Возможно, оба.");
} catch (ArithmeticException e) {
    System.out.println("Что ты делаешь, говорили же не делить на ноль!");
}

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

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

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

Это называется multi-catch — многократный перехват. В нём можно перечислить несколько исключений, разделив их знаком |:

try {
    //Здесь какой-то код
} catch (ArithmeticException | NullPointerException e) {
    System.out.println("Единый код на случай ArithmeticException и NPE");
} catch (Exception e) {
    System.out.println("Что-то пошло не так. Но это точно не ArithmeticException и не NPE.");
}

У конструкции try-catch есть ещё один блок, он называется finally. Исполняемая программа попадает в него всегда — неважно, произошло ли исключение в try и сработал ли какой-то catch.

try {
    //Здесь какой-то код
} catch (ArithmeticException | NullPointerException e) {
    System.out.println("Единый код на случай ArithmeticException и NPE");
} catch (Exception e) {
    System.out.println("Что-то пошло не так. Но это точно не ArithmeticException и не NPE.");
} finally {
    System.out.println("А напоследок я скажу...");
}

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

Единственный случай, когда finally не срабатывает, — критическая ошибка, когда программа вылетела или у неё закончилась выделенная память.

Приведём пример ситуации, в которой полезен finally. Представим, что нужно открыть файл, получить данные и закрыть его. Java работает с файлами через потоки ввода-вывода. Не страшно, если вы с ними не знакомы. Важно то, что для работы с потоком его нужно открыть, а в конце работы закрыть, чтобы в операционной системе не было утечки ресурсов.

Звучит несложно: открыть поток, поработать с ним и закрыть его. Но что будет, если в процессе произойдёт исключение и поток не закроется? Спасает finally: в блок try нужно поместить весь код для работы с потоком, в catch — для обработки возможных ошибок, а в finally — для закрытия потоков.

Похоже, с такой целью finally использовали так часто, что в Java 7 придумали расширенную форму try и назвали её try-with-resources.

Теперь, когда вы знаете про finally, делимся занимательным фактом: в конструкции try-catch может не быть блока catch. Но для этого там должен быть finally.

Пары статей не хватит, чтобы полностью разобраться с try-catch, но теперь вы уже знаете:

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

try…catch

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

Try it

Syntax

try {
  tryStatements
} catch (exceptionVar) {
  catchStatements
} finally {
  finallyStatements
}
tryStatements

Заявления,которые должны быть выполнены.

catchStatements

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

exceptionVarOptional

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

finallyStatements

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

Description

Оператор try всегда начинается с блока try .Затем должен присутствовать блок catch , блок finally или оба. Это дает нам три формы для оператора try :

  • try...catch
  • try...finally
  • try...catch...finally

В отличие от других конструкций, таких как if или for , блоки try , catch и finally должны быть блоками , а не отдельными операторами.

try doSomething(); 
catch (e) console.log(e);

Блок catch содержит инструкции, которые указывают, что делать, если в блоке try возникает исключение . Если какой-либо оператор в try -block (или в функции, вызываемой из try -block) вызывает исключение, управление немедленно передается блоку catch . Если в try -block не возникло исключение , catch -block пропускается.

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

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

Вы также можете использовать оператор try для обработки исключений JavaScript. См. Руководство по JavaScript для получения дополнительной информации об исключениях JavaScript.

Unconditional catch-block

Когда используется catch -block, catch -block выполняется при возникновении любого исключения из try -block. Например, когда в следующем коде возникает исключение, управление передается блоку catch .

try {
  throw 'myException'; 
} catch (e) {
  
  logMyErrors(e); 
}

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

Conditional catch-blocks

Вы можете создать «условные блоки catch », объединив блоки try...catch со структурами if...else if...else , например:

try {
  myroutine(); 
} catch (e) {
  if (e instanceof TypeError) {
    
  } else if (e instanceof RangeError) {
    
  } else if (e instanceof EvalError) {
    
  } else {
    
    logMyErrors(e); 
  }
}

Обычно это делается для того,чтобы перехватывать (и замалчивать)только небольшое подмножество ожидаемых ошибок,а в остальных случаях повторно выбрасывать ошибку:

try {
  myRoutine();
} catch (e) {
  if (e instanceof RangeError) {
    
  } else {
    throw e;  
  }
}

Идентификатор исключения

Когда в блоке try генерируется исключение, exception_var (т. е. e в catch (e) ) содержит значение исключения. Вы можете использовать этот идентификатор для получения информации о выброшенном исключении. Этот идентификатор доступен только в области видимости блока catch . Если вам не нужно значение исключения, его можно опустить.

function isValidJSON(text) {
  try {
    JSON.parse(text);
    return true;
  } catch {
    return false;
  }
}

The finally-block

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

  • Непосредственно перед тем, как блок try нормально завершит выполнение (и никаких исключений не возникло);
  • Непосредственно перед тем, как блок catch нормально завершит выполнение;
  • Непосредственно перед выполнением оператора управления потоком ( return , throw , break , continue ) в блоке try или блоке catch .

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

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

openMyFile();
try {
  
  writeMyFile(theData);
} finally {
  closeMyFile(); 
}

Операторы управления потоком ( return , throw , break , continue ) в блоке finally будут «маскировать» любое значение завершения блока try или блока catch . В этом примере блок try пытается вернуть 1, но перед возвратом поток управления сначала передается блоку finally , поэтому вместо этого возвращается возвращаемое значение блока finally .

function doIt() {
  try {
    return 1;
  } finally {
    return 2;
  }
}

doIt(); 

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

Examples

Nested try-blocks

Сначала посмотрим,что с этим будет:

try {
  try {
    throw new Error('oops');
  } finally {
    console.log('finally');
  }
} catch (ex) {
  console.error('outer', ex.message);
}




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

try {
  try {
    throw new Error('oops');
  } catch (ex) {
    console.error('inner', ex.message);
  } finally {
    console.log('finally');
  }
} catch (ex) {
  console.error('outer', ex.message);
}




А теперь давайте повторим ошибку.

try {
  try {
    throw new Error('oops');
  } catch (ex) {
    console.error('inner', ex.message);
    throw ex;
  } finally {
    console.log('finally');
  }
} catch (ex) {
  console.error('outer', ex.message);
}





Любое данное исключение будет перехвачено ближайшим закрывающим блоком catch только один раз, если оно не будет создано повторно. Конечно, любые новые исключения, возникающие во «внутреннем» блоке (потому что код в catch -block может делать что-то, что вызывает выбросы), будут перехвачены «внешним» блоком.

Возвращение из finally-блока

Если finally -block возвращает значение, это значение становится возвращаемым значением всего оператора try-catch-finally , независимо от любых операторов return в блоках try и catch . Сюда входят исключения, созданные внутри блока catch :

(() => {
  try {
    try {
      throw new Error('oops');
    } catch (ex) {
      console.error('inner', ex.message);
      throw ex;
    } finally {
      console.log('finally');
      return;
    }
  } catch (ex) {
    console.error('outer', ex.message);
  }
})();




Внешний «oops» не выбрасывается из-за возврата в блоке finally . То же самое применимо к любому значению, возвращаемому из catch -block.

Specifications

Browser compatibility

Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox для Android Opera Android Safari на IOS Samsung Internet Deno Node.js
try...catch

1

12

1

5

4

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

optional_catch_binding

66

79

58

No

53

11.1

66

66

58

47

11.3

9.0

1.0

10.0.0

See also

  • Error
  • throw


JavaScript

  • switch

    Оператор switch оценивает выражение, сопоставляя значение выражения с серией предложений case, и выполняет операторы после первого с до тех пор, пока

  • throw

    Бросок выбрасывает определенное пользователем исключение.

  • var

    var объявляет переменную,скопированную на функцию или глобально скопированную,по желанию инициализируемую значением.

  • while

    Оператор while создает цикл,который будет выполняться до тех пор,пока условие теста не станет истинным.

Понравилась статья? Поделить с друзьями:
  • Пцр экспресс тест на ковид инструкция
  • Токсивенол таблетки инструкция по применению цена
  • Инструкция по сборке кровати икеа сварта
  • Флебодиа 600 цена в пензе инструкция
  • Прочесть руководство по ремонту