1. 概述

在这篇文章中,我们将简要介绍什么是异常,并深入探讨Java中的链式异常。

简单来说,异常是中断程序正常执行流程的事件。现在,让我们来看看如何利用链式异常来获取更好的语义。

2. 链式异常

链式异常有助于识别应用程序中一个异常导致另一个异常的情况。

例如,考虑一个方法,由于尝试除以零而抛出一个ArithmeticException,但实际的异常原因是I/O错误导致了除数为零。该方法会将ArithmeticException传递给调用者。调用者可能不知道异常的实际原因。在这种情况下,我们使用链式异常。

这个概念是在JDK 1.4中引入的。

接下来,我们将了解Java中如何支持链式异常。

3. Throwable

Throwable类提供了一些构造函数和方法来支持链式异常。首先,我们来看一下构造函数:

  • Throwable(Throwable cause) - Throwable有一个参数,用于指定异常的实际原因。
  • Throwable(String desc, Throwable cause) - 这个构造函数接受异常描述以及异常的实际原因。

然后,让我们看看这个类提供的方法:

  • getCause()方法 - 这个方法返回当前异常关联的实际原因。
  • initCause()方法 - 它设置底层原因,通过调用异常。

4. 示例

现在,让我们看一个示例,我们将设置自己的异常描述并抛出一个链式异常:

public class MyChainedException {

    public void main(String[] args) {
        try {
            throw new ArithmeticException("Top Level Exception.")
              .initCause(new IOException("IO cause."));
        } catch(ArithmeticException ae) {
            System.out.println("Caught : " + ae);
            System.out.println("Actual cause: "+ ae.getCause());
        }
    }    
}

如预期的那样,这将导致:

Caught: java.lang.ArithmeticException: Top Level Exception.
Actual cause: java.io.IOException: IO cause.

5. 为什么要使用链式异常?

我们需要链接异常来使日志更易读。让我们编写两个示例,一个不链接异常,另一个链接异常。然后,我们将比较两者在日志行为上的差异。

首先,我们将创建一系列的异常:

class NoLeaveGrantedException extends Exception {

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

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

class TeamLeadUpsetException extends Exception {
    // Both Constructors
}

现在,让我们在代码示例中使用这些异常。

5.1. 不链接异常

让我们先写一个不链接自定义异常的示例程序。

public class MainClass {

    public void main(String[] args) throws Exception {
        getLeave();
    }

    void getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        } catch (TeamLeadUpsetException e) {
            e.printStackTrace();
            throw new NoLeaveGrantedException("Leave not sanctioned.");
        }
    }

    void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team Lead Upset");
    }
}

在这个例子中,日志看起来像这样:

com.baeldung.chainedexception.exceptions.TeamLeadUpsetException: 
  Team lead Upset
    at com.baeldung.chainedexception.exceptions.MainClass
      .howIsTeamLead(MainClass.java:46)
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:34)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)
Exception in thread "main" com.baeldung.chainedexception.exceptions.
  NoLeaveGrantedException: Leave not sanctioned.
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:37)
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29)

5.2. 链接异常

接下来,让我们写一个链接自定义异常的示例:

public class MainClass {
    public void main(String[] args) throws Exception {
        getLeave();
    }

    public getLeave() throws NoLeaveGrantedException {
        try {
            howIsTeamLead();
        } catch (TeamLeadUpsetException e) {
             throw new NoLeaveGrantedException("Leave not sanctioned.", e);
        }
    }

    public void howIsTeamLead() throws TeamLeadUpsetException {
        throw new TeamLeadUpsetException("Team lead Upset.");
    }
}

最后,让我们看看带有链式异常的日志:

Exception in thread "main" com.baeldung.chainedexception.exceptions
  .NoLeaveGrantedException: Leave not sanctioned. 
    at com.baeldung.chainedexception.exceptions.MainClass
      .getLeave(MainClass.java:36) 
    at com.baeldung.chainedexception.exceptions.MainClass
      .main(MainClass.java:29) 
Caused by: com.baeldung.chainedexception.exceptions
  .TeamLeadUpsetException: Team lead Upset.
    at com.baeldung.chainedexception.exceptions.MainClass
  .howIsTeamLead(MainClass.java:44) 
    at com.baeldung.chainedexception.exceptions.MainClass
  .getLeave(MainClass.java:34) 
    ... 1 more

我们可以很容易地比较这些日志,并得出结论,链式异常导致了更清晰的日志。

6. 总结

在这篇文章中,我们探讨了链式异常的概念。

所有示例的实现可以在GitHub项目中找到 - 这是一个基于Maven的项目,所以导入并运行它应该非常容易。