Механизм обработки исключений Java

Java задняя часть программист переводчик

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

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

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

Исключительная архитектура наследования

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

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

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

image

Наши исключения Exception в основном делятся на две категории: одна — IOException (исключение ввода-вывода и вывода), а другая — RuntimeException (исключение времени выполнения). Среди них IOException и его подклассы также называются «проверяемыми исключениями», а RuntimeException — «непроверенными исключениями».

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

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

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

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

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

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

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

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

Например:

public class MyException extends RuntimeException{

}
public class MyException extends Exception{

}

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

public class MyException extends RuntimeException{
    public MyException(){}

    public MyException(String mess){
        super(mess);
    }

    public MyException(String mess,Throwable cause){
        super(mess,cause);
    }
}

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

И этот класс Throwable определяет поле detailMessage типа String, в котором хранится подробная информация об исключении подкласса, переданном подклассом. Например:

public static void main(String[] args) {
    throw new MyException("hello wrold failed");
}

Выходной результат:

Exception in thread "main" test.exception.MyException: hello wrold failed
	at test.exception.Test.main(Test.java:7)

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

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

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

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

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

Что касается содержимого этого сообщения об исключении, давайте посмотрим на конкретную реализацию метода printStackTrace:

image

image

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

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

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

try{
    //你的程序
}catch(xxxException e){
    //异常处理代码
}catch(xxxException e){
    //异常处理代码
}

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

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

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

public static void main(String[] args){
    try{
        calculate(22,0);
    }catch (Exception e){
        System.out.println("捕获一个异常");
        e.printStackTrace();
    }
}

public static void calculate(int x,int y){
    if (y == 0) 
        throw new MyException("除数为 0");
    int z = x/y;
}

Выходной результат:

捕获一个异常
test.exception.MyException: 除数为 0
	at test.exception.Test_throw.calculate(Test_throw.java:14)
	at test.exception.Test_throw.main(Test_throw.java:6)

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

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

public static void calculate2(int x,int y) throws ArithmeticException{
    int z = x/y;
}

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

По сути, второе по сути то же самое, что и первое: когда виртуальная машина выполняет x/y, когда она обнаруживает, что y равно нулю, она также создаст объект ArithmeticException, после чего программа будет передана в механизм исключений.

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

Например, если наш метод calculate2 здесь не позволяет y быть равным 1, если он равен 1, он выдаст исключение MyException. В данном случае последнее реализовать все равно не получится, потому что делитель на 1 вообще не видит проблемы в глазах виртуальной машины, как бы это ни назвать, чтобы кинуть исключение. Использование первого для ручного создания исключения намного проще.

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

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

Порядок выполнения try-catch-finally

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

Первый вопрос интервью:

public static void main(String[] args){
    int result = test1();
    System.out.println(result);
}

public static int test1(){
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
    }catch(Exception e){
        i--;
        System.out.println("catch block i = "+i);
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
    return i;
}

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

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

try block, i = 2
finally block i = 10
10

Это достаточно простая задача, ямы нет, немного изменим:

public static int test2(){
    int i = 1;
    try{
        i++;
        throw new Exception();
    }catch(Exception e){
        i--;
        System.out.println("catch block i = "+i);
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
    return i;
}

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

catch block i = 1
finally block i = 10
10

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

Второй вопрос интервью:

public static void main(String[] args){
    int result = test3();
    System.out.println(result);
}

public static int test3(){
    //try 语句块中有 return 语句时的整体执行顺序
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
        return i;
    }catch(Exception e){
        i ++;
        System.out.println("catch block i = "+i);
        return i;
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
}

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

try block, i = 2
finally block i = 10
2

У вас есть сомнения? Очевидно, у меня есть оператор return в блоке операторов try, но почему код в блоке finally все еще выполняется?

Давайте декомпилируем этот класс и посмотрим реализацию скомпилированного байт-кода этого метода test3:

0: iconst_1         //将 1 加载进操作数栈
1: istore_0         //将操作数栈 0 位置的元素存进局部变量表
2: iinc          0, 1   //将局部变量表 0 位置的元素直接加一(i=2)
5: getstatic     #3     // 5-27 行执行的 println 方法                
8: new           #5                  
11: dup
12: invokespecial #6                                                     
15: ldc           #7 
17: invokevirtual #8                                                     
20: iload_0         
21: invokevirtual #9                                                     24: invokevirtual #10                
27: invokevirtual #11                 
30: iload_0         //将局部变量表 0 位置的元素加载进操作栈(2)
31: istore_1        //把操作栈顶的元素存入局部变量表位置 1 处
32: bipush        10 //加载一个常量到操作栈(10)
34: istore_0        //将 10 存入局部变量表 0 处
35: getstatic     #3  //35-57 行执行 finally中的println方法             
38: new           #5                  
41: dup
42: invokespecial #6                  
45: ldc           #12                 
47: invokevirtual #8                  
50: iload_0
51: invokevirtual #9                
54: invokevirtual #10                 
57: invokevirtual #11                 
60: iload_1         //将局部变量表 1 位置的元素加载进操作栈(2)
61: ireturn         //将操作栈顶元素返回(2)
-------------------try + finally 结束 ------------
------------------下面是 catch + finally,类似的 ------------
62: astore_1
63: iinc          0, 1
.......
.......

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

Некоторым может быть интересно, изначально наш i хранился в таблице локальных переменных 0, и, наконец, код в окончательно заполненном слоте 0 со значением 10, но почему окончательная программа все еще возвращает значение 2?

Внимательно изучив байт-код, вы обнаружите, что перед возвратом оператора return виртуальная машина помещает возвращаемое значение в стек операндов и ждет возврата, даже если блок операторов finally изменяет i, возвращаемое значение уже существует в стеке операндов, поэтому он не повлияет на результат возврата программы.

Третий вопрос интервью:

public static int test4(){
    //finally 语句块中有 return 语句
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
        return i;
    }catch(Exception e){
        i++;
        System.out.println("catch block i = "+i);
        return i;
    }finally{
        i++;
        System.out.println("finally block i = "+i);
        return i;
    }
}

результат операции:

try block, i = 2
finally block i = 3
3

На самом деле вы смотрите на весь процесс с его инструкций байт-кода, а не просто помните процесс его выполнения.

image

Вы обнаружите, что программа в конце концов вернется с оператором return в блоке finally и проигнорирует инструкцию возврата в блоке try.

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


Весь код, изображения, файлы в статье хранятся в облаке на моем GitHub:

(https://github.com/SingleYam/overview_java)

Добро пожаловать в публичный аккаунт WeChat: Gorky on the code, все статьи будут синхронизированы в публичном аккаунте.

image