一、Java异常体系
Java程序设计语言提供了3种可抛出的结构(throwable):受检查的异常(checked exception)、运行时异常(runtime exception)和错误(error)。其中,运行时异常和错误及它们的子类,称为非受检查的异常(unchecked exception)。
关于什么时候适合使用哪种可抛出结构,并不总是那么清晰,但有些一般性的原则提出了强有力的指导。
在编译时被强制检查的异常称为受检查的异常(checked exception)。
常用的受检查的异常和非受检查的异常
受检查的异常
非受检查的异常
二、checked异常的利与弊
多年来,Java程序员们一直在争论checked异常的利与弊。
起初,人们认为Java引入checked异常是一个很好的主意。每个方法的签名都列出它可能传递给调用者的异常,而且这些异常就是方法类型的一部分,如果签名与代码实际所做之事不符,代码就无法编译。
现在,争端已经结束。checked异常对于编写健壮性的(robust)软件并非是必须的。
it is clear now that they aren’t necessary for the production of robust software
C#不支持checked异常,C++也尝试过最终也不支持,python和ruby同样也不支持。但我们依然可能使用这些语言来编写出健壮的软件。那么,我们就得决定继续使用checked异常是否值得付出相应的代价。
什么代价呢?代价就是checked异常破坏了开放/闭合原则(Open/Closed Principle)。我们知道,在一个方法中签名中抛出checked异常,那么在该方法的调用链上每个方法就得在方法签名中都要抛出该异常,或者在调用链中某一层catch掉。如果一个调用链中的底层方法签名原本没有声明异常,现添加了checked异常,那么整个调用链都要为止改变(添加checked异常,或者throw,或者catch掉) 。这意味着,软件的较底层的修改都会波及高层级的签名,修改过的模块必须重新构建,发布,即使他们自身并未发生任何更改。最终得到的就是一个从软件底层贯穿高层的修改链,封装被打破了。
《clean code》中直言checked异常以这种方式破坏封装简直就是一种耻辱。原句如下:
Given that the purpose of exceptions is to allow you to handle errors at a distance, it is a shame that checked excep-tions break encapsulation in this way
翻译:异常原本旨在让我们能在较远处(较高层级方法中)处理错误,但checked异常以这种方式破坏了封装简直就是一种耻辱。
三、Spring将checked异常转换为unchecked异常
很多正统的API或框架中都大量使用checked异常,以至于在使用API时,代码里充斥着大量try/catch语句。通常,除了在try/catch中记录异常信息外,并没有做多少实质性的工作。引发异常的问题往往是不可恢复的,如数据库连接失败,SQL语句语法错误等。强制捕获checked异常除了限制开发人员外,并没有提供什么有价值的东西。因此Spring的异常体系都是建立在运行时异常(runtime/unchecked异常)的基础上的。
JDK的很多API之所以难用,一个很大的原因就是checked异常的泛滥,如JavaMail,JDBC等。使用这些API,大量异常处理代码喧宾夺主地侵入业务代码,破坏了代码的整洁和优雅。
Spring在org.springframework.dao包中提供了一套完备优雅的DAO异常体系,这些异常都继承于DataAccessException,DataAccessException又继承于NestedRuntimeException,NestedRuntimeException以嵌套的方式封装了源异常。因此,虽然不同的持久化技术的特定异常被转换到Spring的DAO异常体系中,但原始的异常信息并不会丢失。用户可以方便的通过getCause()方法获取原始的异常信息。
参考书籍:
《clean code》(对应中文版《代码整洁之道》) 7.3节
《精通Spring4.x企业应用开发实战》10.2.1节
面试题:
1.什么是异常?
2.error和exception有什么区别?
3.检查型异常(checked exception)和非检查型异常(RuntimeException)有什么区别?
4.说出几种常见的异常?
5.throw,throws和Throwable的区别?
6.什么是“异常链”?
7.final,finalize,finally关键字的区别?
8.try{}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?
9.在子类方法中不应该抛出比父类范围更广的异常
10.平时在项目中是怎样处理异常的?