欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java核心技术----Exception和Error的区别

程序员文章站 2022-06-05 17:28:07
...

1.概述

(1) Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。

(2) Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。

(3) Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

(4) Exception 又分为受查(checked)异常和非受查(unchecked)异常:

  • 受查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。如果不进行捕获或者抛出声明处理,编译都不会通过。
ClassNotFoundException, NamingException, ServletException, 
SQLException, IOException
  • 非受查异常就是Error类和运行时异常(RuntimeException)及其子类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
OutOfMemoryError, UndeclaredThrowableException, IllegalArgumentException, 
IllegalMonitorStateException, NullPointerException, IllegalStateException, 
IndexOutOfBoundsException

一个函数尽管抛出了多个异常,但是只有一个异常可被传播到调用端。最后被抛出的异常时唯一被调用端接收的异常,其他异常都会被吞没掩盖。如果调用端要知道造成失败的最初原因,程序之中就绝不能掩盖任何异常。

【不要推诿或延迟处理异常,就地解决最好,并且需要实实在在的进行处理,而不是只捕捉,不动作】


2.具体分析

相关类图
Java核心技术----Exception和Error的区别

(1)捕获机制:try-catch-finally

Java核心技术----Exception和Error的区别

  • try{}语句块:里面是要检测的Java代码,可能会抛出异常,也可能会正常运行

  • catch(异常类型){}块:是当Java运行时,系统接收到try块中所抛出异常对象时,会寻找能处理这一异常catch块来进行处理,注意异常类型需要与try{}语句块可能抛出的异常要匹配。可以有多个catch块。不同的异常类型对应不同的处理代码。

  • finally{}语句块:不管系统有没有抛出异常,都会去执行,一般用来释放资源。除了在之前执行了System.exit(0)。
    Java核心技术----Exception和Error的区别

随着 Java 语言的发展,引入了一些更加便利的特性,比如 try-with-resources 和 multiple catch ,具体可以参考下面的代码段。在编译时期,会自动生成相应的处理逻辑,比如,自动按照约定俗成 close 那些扩展了 AutoCloseable 或者 Closeable 的对象。

try (BufferedReader br = new BufferedReader(…);
     BufferedWriter writer = new BufferedWriter(…)) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
   // Handle it
} 
注意点:

1. 不要在finally代码块中处理返回值。
2. 当遇到return语句的时候,执行函数会立刻返回。但是,在Java语言中,如果存在finally就会有例外。除了return语句,try代码块中的break或continue语句也可能使控制权进入finally代码块
3. 请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。
4.对于对象引用,要特别小心,如果在finally代码块中对函数返回的对象成员属性进行了修改,即使不在finally块中显式调用return语句,这个修改也会作用于返回值上。
5.勿将异常用于控制流

(2)抛出机制:throw/throws

   Java核心技术----Exception和Error的区别     Java核心技术----Exception和Error的区别

  • throw:手动抛出异常,一般由程序员在方法内抛出Exception的子类异常

  • throws:声明在方法名之后,告诉调用者,该方法可能会抛出异常,也就是说异常发生后会抛给调用者,由调用者处理异常。

注意点:

当throw的是受查异常时,必须在方法名后加throws

(3)异常处理的原则

1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常
   让自己的代码能够直观地体现出尽量多的信息,而泛泛的 Exception 之类,恰恰隐藏了我们的目的。另外,我们也要保证程序不会捕获到我们不希望捕获的异常。

2. 不要生吞(swallow)异常
   如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。

3. Throw early, catch late 原则

   throw early:

public void readPreferences(String filename) {
    Objects. requireNonNull(filename); //jdk1.7
    //...perform other operations... 
    InputStream in = new FileInputStream(filename);
     //...read the preferences file...
}

   至于“catch late”,其实是我们经常苦恼的问题,捕获异常后,需要怎么处理呢?最差的处理方式,就是我前面提到的“生吞异常”,本质上其实是掩盖问题。如果实在不知道如何处理,可以选择保留原有异常的 cause 信息,直接再抛出或者构建新的异常抛出去。在更高层面,因为有了清晰的(业务)逻辑,往往会更清楚合适的处理方式是什么。

(4)性能角度来审视Java 的异常处理机制

从性能角度来审视一下 Java 的异常处理机制,这里有两个可能会相对昂贵的地方:

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。

  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

【当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。】


3.扩展

NoClassDefFoundError 和 ClassNotFoundException 有什么区别 ?

  • java.lang.NoClassDefFoundError:

    • 说明: 类加载器试图加载类的定义,但是找不到这个类的定义,而实际上这个类文件是存在的。是一种 unchecked exception(也称 runtime exception)

    • 原因: 一般需要检查这个类定义中的初始化部分(如类属性定义、static 块等)的代码是否有抛异常的可能,如果是 static 块,可以考虑在其中将异常捕获并打印堆栈等,或者直接在对类进行初始化调用(如 new Foobar())时作 try catch。

  • java.lang.ClassNotFoundException:

    • 说明: 从规范说明看, java.lang.ClassNotFoundException 异常抛出的根本原因是类文件找不到。是一种 checked exception。

    • 原因: 这个异常产生的原因是缺少了 .class 文件,比如少引了某个 jar,解决方法通常需要检查一下 classpath 下能不能找到包含缺失 .class 文件的 jar。

现在非常火热的反应式编程(Reactive Stream),因为其本身是异步、基于事件机制的,所以出现异常情况,决不能简单抛出去;另外,由于代码堆栈不再是同步调用那种垂直的结构,这里的异常处理和日志需要更加小心,我们看到的往往是特定 executor 的堆栈,而不是业务方法调用关系。对于这种情况,你有什么好的办法吗?