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

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.具体分析

相关类图
avatar

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

avatar

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

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

  • finally{}语句块:不管系统有没有抛出异常,都会去执行,一般用来释放资源。除了在之前执行了System.exit(0)。
    avatar

随着 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

   avatar     avatar

  • 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 的堆栈,而不是业务方法调用关系。对于这种情况,你有什么好的办法吗?

猜你喜欢

转载自blog.csdn.net/qq_20160723/article/details/80359557
今日推荐