введение
Когда дело доходит до исключений, первой реакцией в уме каждого должна быть фиксированная комбинация, такая как попытка-поймать-наконец-то. Действительно, это основная парадигма обработки исключений Java.Давайте поговорим о механизме исключений Java и посмотрим, какие детали мы игнорируем за этим.
Введение в исключения Java
Что ненормально? Это относится к проблеме, которая препятствует продолжению выполнения текущего метода или области действия.Когда во время работы программы возникает исключение, система автоматически генерирует объект исключения, чтобы уведомить программу о необходимости обработки его соответствующим образом. Существует много типов исключений Java.Давайте воспользуемся диаграммой, чтобы посмотреть на иерархию наследования исключений Java:
На картинке мы видим, что базовым классом исключений Java является тип Throwable, далее у него есть два производных класса Error и Exception type, а далее класс Exception делится на проверенные исключения и RuntimeException (исключения времени выполнения). Ниже мы представим их один за другим.Ошибка в исключении Java
Ошибка обычно представляет собой ошибки времени компиляции или системные ошибки, такие как ошибки, связанные с виртуальной машиной, системные сбои (например, OutOfMemoryError, с которыми мы иногда сталкиваемся при разработке) и т. д. Такие ошибки являются неисправимыми или неотлавливаемыми и могут привести к поломке приложения.Обычно приложение не может обработать эти ошибки, поэтому не следует пытаться их отловить с помощью перехвата.
Исключение в Java Исключение
Как мы представили выше, исключения Exception в Java делятся на проверенные исключения и исключения времени выполнения (непроверенные исключения). Ниже мы расширяем введение.
Проверенные исключения в Java
Я считаю, что когда вы пишете код для операций ввода-вывода, у вас должны быть такие воспоминания.Когда вы работаете с файлом или потоком, вы должны использовать try-catch для его обертки, иначе компиляция завершится ошибкой, потому что эти типы исключений проверяются. . Когда компилятор компилирует, для проверенного исключения необходимо выполнить try...catch или throws, иначе компиляция завершится ошибкой. Общие проверенные исключения включают: операции ввода-вывода, ClassNotFoundException, операции с потоками и т. д.
Непроверенные исключения (исключения времени выполнения) в Java
RuntimeException и его подклассы в совокупности называются непроверенными исключениями, такими как NullPointExecrption, NumberFormatException (строка в число), ArrayIndexOutOfBoundsException (массив выходит за границы), ClassCastException (ошибка преобразования типа), ArithmeticException (арифметическая ошибка) и т. д.
Обработка исключений в Java
Общий формат обработки исключений в Java таков:
try{
///可能会抛出异常的代码
}catch(Type1 id1){
//处理Type1类型异常的代码
}catch(Type2 id2){
//处理Type2类型异常的代码
}
Код, в котором могут возникать исключения (но мы точно не знаем, какие исключения произойдут), помещается в блок try. При возникновении исключения блок try выбрасывает автоматически сгенерированный системой объект исключения, после чего механизм обработки исключений будет отвечать за поиск первого обработчика, параметры которого соответствуют типу исключения, а затем выполнить оператор catch (он не будет смотреть вниз). Если наш оператор catch не совпадает, виртуальная машина JVM все равно выдаст исключение.
бросает ключевое слово в Java
Если текущий метод не знает, как обрабатывать исключение, вы можете использовать броски, чтобы передать исключение вызывающей стороне для обработки или JVM. JVM обрабатывает исключения, распечатывая информацию о стеке трассировки исключений и завершая программу. throws следует использовать после подписи метода при его использовании.Можно создавать несколько исключений и разделять их запятыми ','. Ниже приведен пример:
public void f() throws ClassNotFoundException,IOException{}
Таким образом, когда мы вызываем метод f(), мы должны перехватывать два исключения catch-ClassNotFoundException и IOException или базовый класс catch-Exception.
Уведомление:
Этот способ использования бросков - это только то, что мы должны делать во время компиляции Java. Мы можем генерировать только связанные исключения в объявлении метода, но не генерировать никаких исключений в методе, чтобы его также можно было скомпилировать. Мы используем это метод Косвенно обходит проверки времени компиляции Java. Преимущество этого подхода заключается в наличии места для исключений, которые можно генерировать позже без изменения существующего кода. Этот дизайн важен при определении абстрактных классов и интерфейсов, чтобы производные классы или реализации интерфейса могли генерировать эти предварительно объявленные исключения.
распечатать информацию об исключении
Базовый класс Exception класса исключений предоставляет набор методов для получения некоторой информации об исключении.Поэтому, если мы получаем объект исключения, мы можем распечатать некоторую полезную информацию, наиболее часто используемый метод - это void printStackTrace() , это метод возвращает массив элементов в трассировке стека, где каждый элемент представляет кадр в стеке.Элемент 0 является верхним элементом стека и является последним вызовом метода в последовательности вызовов (это исключение вызывается путем создания и создания) ; у него есть несколько разных перегруженных версий, которые могут выводить информацию в разные потоки. Следующий код показывает, как распечатать базовую информацию об исключении:
public void f() throws IOException{
System.out.println("Throws SimpleException from f()");
throw new IOException("Crash");
}
public static void main(String[] agrs) {
try {
new B().f();
} catch (IOException e) {
System.out.println("Caught Exception");
System.out.println("getMessage(): "+e.getMessage());
System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage());
System.out.println("toString(): "+e.toString());
System.out.println("printStackTrace(): ");
e.printStackTrace(System.out);
}
}
Посмотрим на вывод:
Throws SimpleException from f()
Caught Exception
getMessage(): Crash
getLocalizedMessage(): Crash
toString(): java.io.IOException: Crash
printStackTrace():
java.io.IOException: Crash
at com.learn.example.B.f(RunMain.java:19)
at com.learn.example.RunMain.main(RunMain.java:26)
Используйте, наконец, для очистки
Причина введения оператора finally заключается в том, что мы хотим, чтобы некоторый код всегда выполнялся, независимо от того, генерируется ли исключение в блоке try.Таким образом, основной формат обработки исключений становится следующим:
try{
//可能会抛出异常的代码
}
catch(Type1 id1){
//处理Type1类型异常的代码
}
catch(Type2 id2){
//处理Type2类型异常的代码
}
finally{
//总是会执行的代码
}
Оператор finally используется в Java, когда вы хотите, чтобы ресурсы, отличные от памяти, были восстановлены в исходное состояние. Например, открытые файлы или сетевые подключения, нарисованные изображения на экране и т. д. Рассмотрим случай ниже:
public class FinallyException {
static int count = 0;
public static void main(String[] args) {
while (true){
try {
if (count++ == 0){
throw new ThreeException();
}
System.out.println("no Exception");
}catch (ThreeException e){
System.out.println("ThreeException");
}finally {
System.out.println("in finally cause");
if(count == 2)
break;
}
}
}
}
class ThreeException extends Exception{}
Посмотрим на вывод:
ThreeException
in finally cause
no Exception
in finally cause
Если у нас есть оператор return внутри блока try или блока catch, будет ли выполняться оператор finally? Давайте посмотрим на следующий пример:
public class MultipleReturns {
public static void f(int i){
System.out.println("start.......");
try {
System.out.println("1");
if(i == 1)
return;
System.out.println("2");
if (i == 2)
return;
System.out.println("3");
if(i == 3)
return;
System.out.println("else");
return;
}finally {
System.out.println("end");
}
}
public static void main(String[] args) {
for (int i = 1; i<4; i++){
f(i);
}
}
}
Посмотрим на результаты работы:
start.......
1
end
start.......
1
2
end
start.......
1
2
3
end
Мы видим, что предложение finally выполняется, даже если мы используем оператор return в блоке try или catch. Итак, какова ситуация, когда предложение finally не будет выполнено?
Есть две ситуации, которые приведут к потере исключений Java.
- Переопределение в finally выдает исключение (переопределение в finally выдает другое исключение, которое перезапишет первоначально пойманное исключение)
- return в предложении finally (т.е. return)
Стек исключений Java
Чуть ранее мы упоминали соответствующее содержимое стека исключений Java, а в этом разделе мы используем простой пример, чтобы более интуитивно понять соответствующее содержимое стека исключений. Когда мы посмотрим на исключение Exception, мы обнаружим, что метод, в котором возникает исключение, будет на верхнем уровне, основной метод будет на нижнем уровне, а посередине есть другие уровни вызова. Это на самом деле структура стека, первый пришел последним. Давайте рассмотрим это на примере:
public class WhoCalled {
static void f() {
try {
throw new Exception();
} catch (Exception e) {
for (StackTraceElement ste : e.getStackTrace()){
System.out.println(ste.getMethodName());
}
}
}
static void g(){
f();
}
static void h(){
g();
}
public static void main(String[] args) {
f();
System.out.println("---------------------------");
g();
System.out.println("---------------------------");
h();
System.out.println("---------------------------");
}
}
Посмотрим на вывод:
f
main
---------------------------
f
g
main
---------------------------
f
g
h
main
---------------------------
Видно, что информация об исключении идет изнутри наружу, насколько я понимаю, при просмотре исключения следует начинать с первого сообщения об исключении, потому что оно является источником исключения.
Повторная выдача исключений и цепочек исключений
Мы знаем, что каждый раз, когда мы сталкиваемся с сообщением об исключении, нам нужно попытаться... поймать, одно нормально, что, если исключений несколько? Обработка классификации определенно будет более хлопотной, тогда исключение разрешает все исключения. Это действительно возможно, но это неизбежно усложнит последующее обслуживание. Лучший способ — инкапсулировать эту информацию об исключении, а затем захватить наш класс инкапсуляции.
У нас есть два способа обработки исключений: один — генерировать броски и передавать их вышестоящему для обработки, а другой — пытаться… поймать для конкретной обработки. Но какое это имеет отношение к вышесказанному? Нам не нужно выполнять какую-либо обработку в блоке catch команды try...catch, просто используйте ключевое слово throw, чтобы активно выбрасывать нашу инкапсулированную информацию об исключении. Затем продолжайте генерировать исключение метода с помощью ключевого слова throws. Его верхний уровень также может это делать, и так далее будет генерировать цепочку исключений, состоящую из исключений.
Используя цепочки исключений, мы можем улучшить понятность кода, ремонтопригодность и удобство системы.
После того, как мы поймаем исключение, обычно есть две операции
- Выбрасывать исходное исключение после перехвата, надеясь сохранить последнюю точку выдачи исключения - fillStackTrace
- Генерировать новое исключение после перехвата, надеясь сгенерировать полную цепочку исключений - initCause
Повторно выдать исключение после его перехвата
Исключение перехватывается в функции, и дальнейшая обработка в модуле catch не производится, а передается на верхний уровень catch(Exception e){ throw e;}, давайте рассмотрим это на примере:
public class ReThrow {
public static void f()throws Exception{
throw new Exception("Exception: f()");
}
public static void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("inside g()");
throw e;
}
}
public static void main(String[] args){
try{
g();
}
catch(Exception e){
System.out.println("inside main()");
e.printStackTrace(System.out);
}
}
}
Посмотрим на вывод:
inside g()
inside main()
java.lang.Exception: Exception: f()
//异常的抛出点还是最初抛出异常的函数f()
at com.learn.example.ReThrow.f(RunMain.java:5)
at com.learn.example.ReThrow.g(RunMain.java:10)
at com.learn.example.RunMain.main(RunMain.java:21)
fillStackTrace — переопределить предыдущую точку генерирования исключения (получить последнюю точку генерирования исключения)
Установите это, когда выдается исключение catch(Exception e){ (Exception)e.fillInStackTrace();} Давайте посмотрим на это на примере: (все еще пример только что)
public void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("inside g()");
throw (Exception)e.fillInStackTrace();
}
}
Результаты приведены ниже:
inside g()
inside main()
java.lang.Exception: Exception: f()
//显示的就是最新的抛出点
at com.learn.example.ReThrow.g(RunMain.java:13)
at com.learn.example.RunMain.main(RunMain.java:21)
Создать новое исключение после перехвата исключения (сохранить исходную информацию об исключении, в отличие от повторного создания после перехвата исключения)
Если нам нужно сохранить исходную информацию об исключении при генерации исключения, есть два способа:
- Режим 1: Исключение e=new Exception();e.initCause(ex);
- Режим 2: исключение e = новое исключение (ex);
class ReThrow {
public void f(){
try{
g();
}catch(NullPointerException ex){
//方式1
Exception e=new Exception();
//将原始的异常信息保留下来
e.initCause(ex);
//方式2
//Exception e=new Exception(ex);
try {
throw e;
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
public void g() throws NullPointerException{
System.out.println("inside g()");
throw new NullPointerException();
}
}
public class RunMain {
public static void main(String[] agrs) {
try{
new ReThrow().f();
}
catch(Exception e){
System.out.println("inside main()");
e.printStackTrace(System.out);
}
}
}
В этом примере мы сначала перехватываем исключение NullPointerException, а затем выбрасываем исключение. В это время, если мы не используем метод initCause для сохранения исходного исключения (NullPointerException), исключение NullPointerException будет потеряно. Будет отображаться только исключение Eception. Давайте посмотрим на результаты ниже:
//没有调用initCause方法的输出
inside g()
java.lang.Exception
at com.learn.example.ReThrow.f(RunMain.java:9)
at com.learn.example.RunMain.main(RunMain.java:31)
//调用initCasue方法保存原始异常信息的输出
inside g()
java.lang.Exception
at com.learn.example.ReThrow.f(RunMain.java:9)
at com.learn.example.RunMain.main(RunMain.java:31)
Caused by: java.lang.NullPointerException
at com.learn.example.ReThrow.g(RunMain.java:24)
at com.learn.example.ReThrow.f(RunMain.java:6)
... 1 more
Мы видим, что после того, как мы используем метод initCause для сохранения, исходная информация об исключении будет выводиться в виде Caused by.
Ограничения исключений Java
Существуют ограничения, когда исключения Java сталкиваются с наследованием или интерфейсами.Давайте рассмотрим ограничения.
- Правило 1. Когда подкласс переопределяет метод, выбрасывающий исключение в родительском классе, он либо не выдает исключение, либо выдает то же исключение, что и метод родительского класса или подкласс исключения. Если переопределенный метод суперкласса генерирует только проверенные исключения, переопределенный метод подкласса может создавать непроверенные исключения. Например, метод родительского класса выбрасывает проверенное исключение IOException, при переопределении метода нельзя выбрасывать Exception.Для проверенных исключений можно выбрасывать только исключения IOException и его подкласса, а также непроверенные исключения. Давайте рассмотрим это на примере:
class A {
public void fun() throws Exception {}
}
class B extends A {
public void fun() throws IOException, RuntimeException {}
}
Исключение, генерируемое родительским классом, содержит все исключения, и написанное выше правильно.
class A {
public void fun() throws RuntimeException {}
}
class B extends A {
public void fun() throws IOException, RuntimeException {}
}
Подкласс IOException находится за пределами категории исключений родительского класса, и написанное выше неверно.
class A {
public void fun() throws IOException {}
}
class B extends A {
public void fun() throws IOException, RuntimeException, ArithmeticException{}
}
RuntimeException не относится к категории IO и находится за пределами категории исключений родительского класса. Но RuntimeException и ArithmeticException являются исключениями времени выполнения, а методы, переопределяемые подклассами, могут генерировать любое исключение времени выполнения. Так что указанное выше написание верное.
- Правило: когда подкласс переопределяет метод, выбрасывающий исключение в родительском классе, если он реализует интерфейс с той же сигнатурой метода, а метод в интерфейсе также имеет объявление исключения, метод, переопределяемый подклассом, либо не выдает исключение. исключение или Выдает пересечение исключения, объявленного переопределенным методом в суперклассе, и исключения, объявленного реализованным методом в интерфейсе.
class Test {
public Test() throws IOException {}
void test() throws IOException {}
}
interface I1{
void test() throw Exception;
}
class SubTest extends Test implements I1 {
public SubTest() throws Exception,NullPointerException, NoSuchMethodException {}
void test() throws IOException {}
}
В классе SubTest тестовый метод либо не генерирует исключение, либо генерирует исключение IOException или его подклассы (например, InterruptedIOException).
Исключения и конструкторы Java
Если в конструкторе возникает исключение, как мы можем его обработать, чтобы правильно очистить? Может быть, вы бы сказали, используйте, наконец, разве это не гарантировано? Это не обязательно.Если конструктор встречает исключение во время своего выполнения, некоторые части объекта не были должным образом инициализированы в это время, но в это время он будет окончательно очищен, что, очевидно, вызовет проблемы.
в общем:
Для классов, которые могут генерировать исключения на этапе конструктора и требуют очистки, самым безопасным способом является использование вложенных предложений try.
try {
InputFile in=new InpputFile("Cleanup.java");
try {
String string;
int i=1;
while ((string=in.getLine())!=null) {}
}catch (Exception e) {
System.out.println("Cause Exception in main");
e.printStackTrace(System.out);
}finally {
in.dispose();
}
}catch (Exception e) {
System.out.println("InputFile construction failed");
}
Давайте подробнее рассмотрим логику здесь. Конструкция InputFile действительна в первом блоке try. Если конструктор завершается ошибкой и возникает исключение, оно будет перехвачено самым внешним блоком catch. В это время объект InputFile метод не требует выполнения. Если построение прошло успешно, то войдите во второй слой блока try, в это время должен быть вызван блок finally (объект необходимо удалить).
Рекомендации по использованию исключений (используйте исключения в следующих случаях)
- Обрабатывать исключения на соответствующем уровне (перехватывать исключения только тогда, когда вы знаете, как с ними обращаться)
- Приложите все усилия, чтобы исправить проблему, и повторно вызовите метод, вызвавший исключение.
- Немного поработайте, а затем повторите выполнение вокруг исключения
- Делайте как можно больше в текущей операционной среде, а затем повторно вызывайте то же исключение на более высокий уровень.
- Максимально делайте то, что можно сделать в текущей операционной среде, а затем перебрасывайте различные исключения на более высокие уровни.
- Усилия по повышению безопасности библиотек классов и программ.