Java异常处理-原理及优化建议

1 异常层次结构

异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。 Java异常类层次结构图: Java异常类层次结构图

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。 Throwable: 有两个重要的子类:*Exception(异常)和 Error(错误)*,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

Exception(异常):是程序本身可以处理的异常。 Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

2 JVM字节码分析异常处理机制

我们都知道 try、catch、finally语句块的执行顺序: try-catch-finally 控制流

接下来我们从字节码的角度加深对异常机制的理解, 我们先反编译如下代码:

public class FileDemo {

    private static void divideFun(int a, int b) {
        b = a - b;
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 3;
        try {
            b = a + b;
            FilterInputStream filterInputStream = new BufferedInputStream(new FileInputStream("d:/a"));
        } catch (FileNotFoundException e) {
            System.out.println("file error");
            divideFun(a, b);
        } catch (RuntimeException e) {
            b = a * b;
            throw e;
        } finally {
            a = 0;
        }

    }

}

通过命令javap -c .\FileDemo.class 反编译.class文件得到一下输出:

PS E:\totalpalce\ContentTest\target\classes\exception> javap -c .\FileDemo.class
Compiled from "FileDemo.java"
public class exception.FileDemo {
  public exception.FileDemo();
    Code:
       0: aload_0  //从局部变量0中装载引用类型值
       1: invokespecial #1  // Method java/lang/Object."<init>":()V 根据编译时类型来调用实例方法
       4: return   //从方法中返回,返回值为void

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1    //将int类型常量1压入栈
       1: istore_1    //将int类型值存入局部变量1  a = 1
       2: iconst_3    //将int类型常量3压入栈
       3: istore_2    //将int类型值存入局部变量2  b = 3

       4: iload_1     //从局部变量1中装载int类型值  异常语句块 start
       5: iload_2     //从局部变量2中装载int类型值
       6: iadd        // 执行int类型的加法    a + b
       7: istore_2    //将int类型值存入局部变量2  b = a + b
       8: new           #2   //创建一个新对象  class java/io/BufferedInputStream
      11: dup         //复制栈顶部一个字长内容
      12: new           #3   // 创建一个新对象 class java/io/FileInputStream
      15: dup         //复制栈顶部一个字长内容
      16: ldc           #4   //把常量池中的 "d:/a" 压入栈
      18: invokespecial #5   //根据编译时类型来调用实例方法 Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
      21: invokespecial #6   //根据编译时类型来调用实例方法 Method java/io/BufferedInputStream."<init>":(Ljava/io/InputStream;)V
      24: astore_3    //将引用类型值存入局部变量3
      25: iconst_0    //将int类型常量0压入栈  异常语句块 end

      26: istore_1    //将int类型值存入局部变量1  finally语句块 a = 0
      27: goto          63   //无条件跳转至63

      //碰到 FileNotFoundException时,跳到 30 号指令
      30: astore_3    //将引用类型值存入局部变量3
      31: getstatic     #8   //从类中获取静态字段  Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #9   // 把常量池中的"file error"压入栈
      36: invokevirtual #10  //运行时按照对象的类来调用实例方法 Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: iload_1     //从局部变量1中装载int类型值
      40: iload_2     //从局部变量2中装载int类型值
      41: invokestatic  #11  // 调用类(静态)方法  Method divideFun:(II)V
      44: iconst_0    //将int类型常量0压入栈
      45: istore_1    //将int类型值存入局部变量1 finally语句块 a = 0
      46: goto          63   //无条件跳转至63

      //碰到 RuntimeException时,跳到 49 号指令
      49: astore_3    //将引用类型值存入局部变量3
      50: iload_1     //从局部变量1中装载int类型值
      51: iload_2     //从局部变量2中装载int类型值
      52: imul        //执行int类型的乘法 a*b
      53: istore_2    //将int类型值存入局部变量2  b = a * b
      54: aload_3     //从局部变量3中装载引用类型值
      55: athrow      //抛出异常或错误 throw e

      //其他未捕获异常时候,跳到 56 号指令
      56: astore        4  // 将引用类型或returnAddress类型值存入局部变量
      58: iconst_0    //将int类型常量0压入栈
      59: istore_1    //将int类型值存入局部变量1 finally语句块 a = 0
      60: aload         4  //从局部变量中装载引用类型值(refernce)
      62: athrow      //抛出异常或错误
      63: return      //从方法中返回,返回值为void
    Exception table:  // 异常表
       from    to  target type
           4    25    30   Class java/io/FileNotFoundException //4-25号指令中,碰到 FileNotFoundException时,跳到 30 号指令
           4    25    49   Class java/lang/RuntimeException
           4    25    56   any
          30    44    56   any
          49    58    56   any

通过以上指令,我们能够理解,生成字节码指令的时候,会有一张异常表,记录发生异常情况,跳转到哪一条指令,捕获的异常会在字节码指令中跟踪 try语句块中的代码,当出现该异常的时候跳转到相应catch内部的语句块和最后的finally语句块,如果出现的异常是我们未捕获的,则会走finally的逻辑,并抛出异常错误返回

为了更好的理解我们可以看下面字节码指令结构图: 异常字节码指令图

3 Java异常处理的一般性建议

try-catch-finally 规则 异常处理语句的语法规则: 1) 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。 2) 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。 3) catch 块与相应的异常类的类型相关。 4) 一个 try 块可能有多个 catch 块。若如此,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块 5) 可嵌套 try-catch-finally 结构。 6) 在 try-catch-finally 结构中,可重新抛出异常。 7) 除了下列情况,总将执行 finally 做为结束:JVM 过早终止(调用 System.exit(int));在 finally 块中抛出一个未处理的异常;计算机断电、失火、或遭遇病毒攻击。

Throws抛出异常的规则: 1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。 2) 必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误 3) 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。 4) 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

4 异常处理优化

4. 1 Java7中IO的异常处理 try-with-resource

try-with-resources语句是一种声明了一种或多种资源的try语句。资源是指在程序用完了之后必须要关闭的对象。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。任何实现了java.lang.AutoCloseable接口的对象,和实现了java.io.Closeable接口的对象,都可以当做资源使用。

public class FileHandlerOptimize {

    private static void printFile() throws IOException {
        InputStream input = null;

        try {
            input = new FileInputStream("D:/user.txt");

            int data = input.read();
            while (data != -1) {
                System.out.print((char) data);
                data = input.read();
            }
        } finally {
            if (input != null) {
                input.close();
            }
        }
    }

    //单个流关闭
    private static void printFileJava7() throws IOException {

        try (FileInputStream input = new FileInputStream("D:/user.txt")) {

            int data = input.read();
            while (data != -1) {
                System.out.print((char) data);
                data = input.read();
            }
        }
    }

    //如果需要对多个流自动关闭
    private static void printFileJava7Multiple() throws IOException {
//你可以在一个try-with-resources语句里面声明多个资源
        try (FileInputStream input = new FileInputStream("D:/user.txt");
                BufferedInputStream bufferedInput = new BufferedInputStream(input)) {

            int data = bufferedInput.read();
            while (data != -1) {
                System.out.print((char) data);
                data = bufferedInput.read();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        //单个流处理
        printFileJava7();
        //多个流处理
        printFileJava7Multiple();
    }
}

这是java7的新特性比较简单,但是它做了一定的优化,你反编译.class文件会发现新增了一行代码如下:

    private static void printFileJava7() throws IOException {
        FileInputStream input = new FileInputStream("D:/user.txt");
        Throwable var1 = null;
        try {
            for(int data = input.read(); data != -1; data = input.read()) {
                System.out.print((char)data);
            }
        } catch (Throwable var10) {
            var1 = var10;
            throw var10;
        } finally {
            if (input != null) {
                if (var1 != null) {
                    try {
                        input.close();
                    } catch (Throwable var9) {
                        var1.addSuppressed(var9);
                    }
                } else {
                    input.close();
                }
            }
        }
    }

多了 var1.addSuppressed(var9); 这行代码,从Java 1.7开始,大佬们为Throwable类新增了addSuppressed方法,支持将一个异常附加到另一个异常身上,从而当出现两个以上的异常时,避免异常被屏蔽覆盖

4.2 Java异常处理模板

对如下代码进行优化:

    public static void readFile() throws IOException {
        byte[] buff = new byte[1024];
        FileInputStream input = null;
        try {
            input = new FileInputStream(new File("D:/user.txt"));
            while (-1 != input.read(buff)) {
                System.out.println(new String(buff));
            }
        } catch (IOException e) {
            e.getMessage();
            throw e;
        } finally {
            if (null != input) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.getMessage();
                    throw e;
                }
            }
        }
    }

注意: 上面的例子实际上存在异常丢失的隐患, 如果第一个try中出现异常, 接着在执行finally中的input.close()也出现异常, 这时main 方法只能接收到input.close的异常信息, 第一个异常会被覆盖, 导致异常信息丢失

所以正确的写法如下:

public class FileReadDemo {  
    public void readFile() throws ApplicationException {  
        byte[] buff = new byte[1024];  
        IOException processException = null;  
        FileInputStream input = null;  
        try {  
            input = new FileInputStream(new File("D:/user.txt"));  
            while (-1 != input.read(buff)) {  
                System.out.println(new String(buff));  
            }  
        } catch (IOException e) {  
            processException = e;  
        } finally {  
            try {  
                if(null != input){  
                    input.close();  
                }  
            } catch (IOException e) {  
                if(null == processException){  
                    throw new ApplicationException(e);  
                }else{  
                    throw new MyException("FileInputStream close exception", processException);  
                }  
            }  
            if(processException !=null){  
                throw new MyException(processException);  
            }  
        }  
          
    }  
  
    public static void main(String[] args){  
        try {  
            readFile();  
        } catch (ApplicationException e) {  
            e.printStackTrace();  
        }  
          
    }  
}  

这种写法是不是很糟糕, 我们实际关注的代码就只是第一个try中的四行代码, 这样的缺陷很明显, 一旦系统中有多处地方要用到类似的文件处理操作时, 就需要重复的做try-catch-finally, 很明显, 这就违背了程序开发中的一个重要原则: DRY(Don’t repeat yourself), 代码重复, 不容易阅读和维护, 同时也存在一个隐患, 忘记关闭, 异常没正确处理等, 引入模板方法就可以很好的解决这个问题.

public class MyException extends RuntimeException {

    public MyException() {
        super();
    }

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

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

    public MyException(Throwable cause, String message) {
        super(message, cause);

    }

    public MyException(Throwable suppressed, Throwable throwable, String message) {
        super(message, throwable);
//多异常覆盖问题
        throwable.addSuppressed(suppressed);
    }

    public MyException(Throwable cause) {
        super(cause);
    }

    protected MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

提取重复代码

public abstract class InputStreamProcessingTemplate {

    public void process(String fileName) {
        //防止异常信息丢失
        IOException processException = null;
        InputStream input = null;
        try {
            input = new FileInputStream(fileName);

            doProcess(input);
        } catch (IOException e) {
            processException = e;
        } finally {
            processExceptionHandle(fileName, processException, input);
        }
    }

    //override this method in a subclass, to process the stream.
    public abstract void doProcess(InputStream input) throws IOException;

    private static void processExceptionHandle(String fileName, IOException processException, InputStream input) {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                if (processException != null) {
                    //防止异常信息丢失
                    throw new MyException(processException, e, "Error message..." + fileName);
                }
                throw new MyException(e, "Error closing InputStream for file " + fileName);
            }
        }
        if (processException != null) {
            throw new MyException(processException, "Error processing InputStream for file " + fileName);
        }
    }
}

通过这种方式,我们发现我们需要处理逻辑,不需要考虑流的关闭问题

public class Test {

    public static void main(String[] args) {
        new InputStreamProcessingTemplate(){
            @Override
            public void doProcess(InputStream input) throws IOException {
                byte[] buff = new byte[1024];
                while(input.read(buff) != -1){
                    //do something with the chars...
                    System.out.println(new String(buff));
                }
            }
        }.process("D:\\user.txt");
    }
}

或者使用静态模板方法

public interface InputStreamProcessor {

    public void process(InputStream input) throws IOException;

}

静态方法

public class InputStreamProcessingTemplate {

    public static void process(String fileName, InputStreamProcessor processor) {
        //防止异常信息丢失
        IOException processException = null;
        InputStream input = null;
        try {
            input = new FileInputStream(fileName);
            processor.process(input);
        } catch (IOException e) {
            processException = e;
        } finally {
            processExceptionHandle(fileName, processException, input);

        }
    }

    private static void processExceptionHandle(String fileName, IOException processException, InputStream input) {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                if (processException != null) {
                    //防止异常信息丢失
                    throw new MyException(processException, e, "Error message..." + fileName);
                }
                throw new MyException(e, "Error closing InputStream for file " + fileName);
            }
        }
        if (processException != null) {
            throw new MyException(processException, "Error processing InputStream for file " + fileName);
        }
    }
}

这样看上出代码更加简洁优雅

public class Test {

    public static void main(String[] args) {
        InputStreamProcessingTemplate.process("D:\\user.txt", input -> {
            byte[] buff = new byte[1024];
            while(input.read(buff) != -1){
                //do something with the chars...
                System.out.println(new String(buff));
            }
        });
    }
}
4.3 java8中的异常简化

实现 Supplier 提供型接口

public class MyException extends RuntimeException implements Supplier<RuntimeException> {

    public MyException() {
        super();
    }

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

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

    public MyException(Throwable cause, String message) {
        super(message, cause);

    }

    public MyException(Throwable suppressed, Throwable throwable, String message) {
        super(message, throwable);
        throwable.addSuppressed(suppressed);
    }

    public MyException(Throwable cause) {
        super(cause);
    }

    protected MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    @Override public RuntimeException get() {
        return this;
    }

如此我们可以简化我们的NPE判断 原先我们都是这样

if (user == null) {
            throw new MyException();
        }
if (user.getAge() == null) {
            throw new MyException();
        }

现在可以这样

Optional.ofNullable(Optional.ofNullable(user).orElseThrow(MyException::new).getAge()).orElseThrow(MyException::new);
4.4 异常增强

我们在平时的软件开发中一般都是进行异常包装,包括其中的 错误码 和 错误信息, 来提示我们程序错误的原因,例如下面例子:

public void method3() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("An error occurred when trying to ...");
        throw e;
     }
  }

  public void method2() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("An error occurred when trying to ...");
        throw e;
     }
  }
  
  public void method1() throws EnrichableException {
     if(...) throw new EnrichableException(
        "ERROR1", "Original error message");   
  }

方法method1抛出一个异常,通过唯一的错误码标识“ERROR1”。请注意,method1()被method2()和method3()调用。尽管在method1()方法中,进行了异常信息记录,但是无论在方法method2和method3中运行的时候method1出错,封装的错误结果都是相同的,但是对于开发人员来说,确定是具体是哪一个方法出的问题这可能很重要。错误码“ERROR1”足以确定错误发生在哪里,但不知道在哪个上下文中出现的情况。

解决这个问题的方法是在异常中添加唯一的上下文错误代码,就像添加其他上下文信息一样。这里有一个例子,addInfo()方法已经被更改以适应以下情况

public void method3() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("METHOD3", "ERROR1",
            "An error occurred when trying to ...");
        throw e;
     }
  }

  public void method2() throws EnrichableException{
     try{
        method1(); 
     } catch(EnrichableException e){
        e.addInfo("METHOD2", "ERROR1",
            "An error occurred when trying to ...");
        throw e;
     }
  }
  
  public void method1() throws EnrichableException {
     if(...) throw new EnrichableException(
        "METHOD1", "ERROR1", "Original error message");   
  }

这样当method1()从method3()调用时,错误标识将如下所显示:

[METHOD3:ERROR1][METHOD1:ERROR1]

我们就可以确定方法出错的位置和调用链

而异常增强,则是有这种需求场景的前提下,通过添加异常调用链信息,追踪问题 代码如下: 异常句柄

public interface ExceptionHandler {

    void handle(String contextCode, String errorCode,
            String errorText, Throwable t);

    void raise(String contextCode, String errorCode,
            String errorText);
}

异常类

public class EnrichableException  extends RuntimeException {

    public static final long serialVersionUID = -1;

    private List<InfoItem> infoItems = new ArrayList<>();

    private class InfoItem {

        private String errorContext;

        private String errorCode;

        private String errorText;

        private InfoItem(String contextCode, String errorCode, String errorText) {

            this.errorContext = contextCode;
            this.errorCode = errorCode;
            this.errorText = errorText;
        }
    }

    public EnrichableException(String errorContext, String errorCode, String errorMessage) {

        addInfo(errorContext, errorCode, errorMessage);
    }

    public EnrichableException(String errorContext, String errorCode, String errorMessage, Throwable cause) {
        super(cause);
        addInfo(errorContext, errorCode, errorMessage);
    }

    public EnrichableException addInfo(String errorContext, String errorCode, String errorText) {

        this.infoItems.add(new InfoItem(errorContext, errorCode, errorText));
        return this;
    }

    public String getCode() {
        StringBuilder builder = new StringBuilder();

        for (int i = this.infoItems.size() - 1; i >= 0; i--) {
            InfoItem info = this.infoItems.get(i);
            builder.append('[');
            builder.append(info.errorContext);
            builder.append(':');
            builder.append(info.errorCode);
            builder.append(']');
        }

        return builder.toString();
    }

    @Override public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append(getCode());
        builder.append('\n');

        //append additional context information.
        for (int i = this.infoItems.size() - 1; i >= 0; i--) {
            InfoItem info = this.infoItems.get(i);
            builder.append('[');
            builder.append(info.errorContext);
            builder.append(':');
            builder.append(info.errorCode);
            builder.append(']');
            builder.append(info.errorText);
            if (i > 0) {
                builder.append('\n');
            }
        }

        //append root causes and text from this exception first.
        if (getMessage() != null) {
            builder.append('\n');
            if (getCause() == null) {
                builder.append(getMessage());
            } else if (!getMessage().equals(getCause().toString())) {
                builder.append(getMessage());
            }
        }
        appendException(builder, getCause());

        return builder.toString();
    }

    private void appendException(StringBuilder builder, Throwable throwable) {
        if (throwable == null) {
            return;
        }
        appendException(builder, throwable.getCause());
        builder.append(throwable.toString());
        builder.append('\n');
    }

}

测试类

public class ExceptionTest {
    protected ExceptionHandler exceptionHandler = new ExceptionHandler(){
        @Override
        public void handle(String errorContext, String errorCode,
                String errorText, Throwable t){

            if(! (t instanceof EnrichableException)){
                throw new EnrichableException(
                        errorContext, errorCode, errorText, t);
            } else {
                ((EnrichableException) t).addInfo(
                        errorContext, errorCode, errorText);
            }
        }

        @Override
        public void raise(String errorContext, String errorCode,
                String errorText){
            throw new EnrichableException(
                    errorContext, errorCode, errorText);
        }
    };

    public static void main(String[] args){
        ExceptionTest test = new ExceptionTest();
        try{
            test.level1();
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    public void level1(){
        try{
            level2();
        } catch (EnrichableException e){
            this.exceptionHandler.handle(
                    "L1", "E1", "Error in level 1, calling level 2", e);
            throw e;
        }
    }

    public void level2(){
        try{
            level3();
        } catch (EnrichableException e){
            this.exceptionHandler.handle(
                    "L2", "E2", "Error in level 2, calling level 3", e);
            throw e;
        }
    }

    public void level3(){
        try{
            level4();
        } catch(Exception e){
            this.exceptionHandler.handle(
                    "L3", "E3", "Error at level 3", e);
        }
    }

    public void level4(){
        throw new IllegalArgumentException("incorrect argument passed");
    }
}

运行main方法结果如下 >[L1:E1][L2:E2][L3:E3] [L1:E1]Error in level 1, calling level 2 [L2:E2]Error in level 2, calling level 3 [L3:E3]Error at level 3 java.lang.IllegalArgumentException: incorrect argument passed

我们可以清晰的定位在哪个流程中函数调用出现了异常

Reference

Java Exception Handling JVM 对 Java 异常的处理原理 JAVA异常处理 异常管理 - try-catch-finally异常信息丢失 深入理解Java try-with-resource

文章目录
-----------------------
最新评论

[评论][COMMENTS]