Java

重学Java异常体系

2023-04-14  本文已影响0人  消失er

Java异常类的层次结构

Throwable是所有异常类的基类。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

Throwable 分为两种:Error和Exception。
1.Error:Error是系统级别的错误,无法通过程序处理;此类错误一般表示代码运行时 JVM 出现问题,例如OutOfMemoryError、StackOverflowError、VirtualMachineError等。

2.Exception是应用程序级别的异常,可以通过程序处理;通常会分为Checked Exception和Unchecked Exception。

受检异常 vs 非受检异常

受检异常

在java.lang.Exception类注释上,说明了Exception及其子类(排除RuntimeException及其子类),都是checked exceptions受检异常。

/**
 * The class Exception and its subclasses are a form of
 * {@code Throwable} that indicates conditions that a reasonable
 * application might want to catch.
 *
 * The class Exception and any subclasses that are not also
 * subclasses of {@link RuntimeException} are checked
 * exceptions  Checked exceptions need to be declared in a
 * method or constructor's {@code throws} clause if they can be thrown
 * by the execution of the method or constructor and propagate outside
 * the method or constructor boundary.
 */
public class Exception extends Throwable {...}
非受检异常

在java.lang.RuntimeException类注释上,说明了RuntimeException及其子类,都是Unchecked Exception非受检异常。

/**
 * RuntimeException is the superclass of those
 * exceptions that can be thrown during the normal operation of the
 * Java Virtual Machine.
 *
 * RuntimeException and its subclasses are unchecked exceptions.  
 *  Unchecked exceptions do not need to be
 * declared in a method or constructor's {@code throws} clause if they
 * can be thrown by the execution of the method or constructor and
 * propagate outside the method or constructor boundary.
 *
 * @author  Frank Yellin
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since   JDK1.0
 */
public class RuntimeException extends Exception {...}

Java异常处理机制

Java 的异常处理,主要是通过 5 个关键字来实现:try、catch、throw、throws 和 finally。
通过捕获和处理异常,能够在程序出现异常情况时提供更好的容错机制,保证程序的稳定性和可靠性。

throws与throw

throw 关键字的作用是抛出一个异常,但是它的作用不仅仅局限于抛出异常。在一些情况下,throw 关键字还可以实现异常类型的转换。这是因为 Java 中的异常继承关系,子类异常对象可以转换为父类异常对象,而 throw 关键字可以将子类异常对象强制转换为父类异常对象。

public void doSomething() throws Exception {
    try {
        // do something
    } catch (ChildException e) { // 子类异常
        throw (Exception)e; // 异常类型转换为父类异常
    }
}

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

关于finally特殊问题和Java异常处理的最佳实践,后面用单独的文章讨论。

覆盖父类方法时的异常声明

从父类继承,并且子类重写/覆盖的方法签名,如何声明异常?
子类重写父类方法的时候,如何确定异常抛出声明的类型。主要有4个原则:

1.子类重写父类方法时,要抛出与父类一致的异常,或者不抛出异常

2.子类重写父类方法时,子类抛出的异常不能超过父类的受检异常类型范围
即如果父类的方法声明了受检异常T,则子类在重写该方法的时候声明的异常不能是 T的父类;只能是T及T的子类。

3.子类重写的方法可以抛出任意非受检异常

4.子类在重写父类的具有异常声明的方法的同时,又去实现了具有相同方法名称的接口且该接口中的方法也具有异常声明,则子类中的重写的方法,要么不抛出异常,要么抛出父类中方法声明异常与接口中方法声明的异常的交集。

自定义异常

在 Java 中,通常情况下已有的内置异常类能够满足应用的基础使用需求。但有些情况下,需要自定义异常来满足特别的需求。
最常见的就是用精确的命名描述异常

如何自定义异常就不多描述了,这里着重说一点关于自定义异常时需要注意的地方。

重写fillInStackTrace方法

Java 异常在程序运行时发生,会导致程序的执行流程被中断并进行一系列的异常处理操作,这会对程序的性能产生一定的影响。其中性能开销最大的是填充堆栈。
默认情况下,是由Throwable的fillInStackTrace方法完成,它是一个native方法;作用是获取当前线程的堆栈信息,填充到跟踪元素中。

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null || backtrace != null ) {// 如果没填充过……
        fillInStackTrace(0);                // 获取当前线程的堆栈跟踪信息
        stackTrace = UNASSIGNED_STACK;      // 填充堆栈跟踪信息
    }
    return this;// 如果已经填充过,直接返回当前异常对象
}

一般来说自定义的业务异常如果确实不需要stacktrace的话,可以覆写该方法,返回this,提高性能。

@Override
public synchronized Throwable fillInStackTrace() {
    // return super.fillInStackTrace();
    return this;
}

当前还有其他实现方式:

上一篇下一篇

猜你喜欢

热点阅读