java CheckedException和UncheckException

本文简单地讨论CheckedException和UncheckException的使用选择。

在平时,如果要抛出一个错误,我总是习惯性地new一个RuntimeException。那么问题来了,既然RuntimeException已经可以满足我的需求,为什么需要Exception?为什么FileNotFoundException是Exception而不是RuntimeException?

 

一、一些概念

(1)CheckedException和UncheckException的声明

CheckedException继承于java.lang.Exception类,UncheckException继承于java.lang.RuntimeException类,所以两者的声明方式有所不同(继承自不同的类)。

如果要声明一个CheckedException,那么就extends Exception。如果要声明一个UncheckedException,那么就extends RuntimeException。

(2)对CheckedException和UncheckException的处理

1.CheckedException

CheckedException必须被显式地捕获或者传递。我们可以用try catch捕获CheckedException,马上将其处理掉,也可以throw出来,在某个上层函数中处理(可以理解为甩锅)。

这里要注意一点,如果抛出了一个CheckedException,就一定要有人接锅(如果你不甩锅,就要接锅)。无论被throw了几次,这个CheckedException总会在某处被try catch处理掉。

TestUnchecked.java

在TestUnchecked.java中,CheckedDataError这个CheckedException最先在testException1方法中抛出,等待调用他的方法接锅。doSomething1方法调用了testException1方法,但是却没有在此方法中用try catch进行处理(当然这是不推荐的),选择了再次甩锅。最后,这个CheckedException终于在main方法中被捕获了。

2.UncheckException

相比CheckedException,UncheckException的处理没有那么严格。但是,如果愿意,我们同样可以用try catch捕获/用throw抛出一个UncheckException,但是这些处理不带有强制性,通常我们只需要简单地throw出来即可。

(这里要强调一点,从功能上来说,CheckedException能做到的,UncheckException也能做到,所以我认为CheckedException和UncheckException的最大区别并不在于功能)

(3)使用哪一种异常?

到这里,我们可以发现一个问题:CheckedException和UncheckException的最大区别在于“是否需要强制处理”。

如果你抛出一个CheckedException,那么你一定要对他负责。如果你抛出一个UncheckException,那么你可以不用对他负责(当然也可以负责,但是这就和CheckedException没有任何区别了,为什么不干脆使用CheckedException呢)。

那么问题来了,既然CheckedException和UncheckException实现的效果可以完全相同,那么为什么要把两者区分开来?想要抛出异常时,我们选择哪一种?

二、CheckedException和UncheckException的使用选择

我在StackOverflow上看了很多讨论,有不少高人认为CheckedException和UncheckException有不同的设计思想,在不同情况下应该有不同的使用倾向。

个人认为,在选择CheckedException和UncheckException时,我们应该从设计的角度出发,看看抛出的Exception会对程序本身造成什么影响。

(1)CheckedException

1.“提醒”

CheckedException在编译时会被检查,如果CheckedException没有被处理,那么程序无法通过编译。所以CheckedException可以起到“提醒”的作用,告诉我们这里可能(“常常”)出问题,需要“特别注意”,强制开发人员考虑到可能出现问题的状况。

举个例子,FileNotFoundException是经典的CheckedException,常常出现在“读取文件”的状况之下。“读取文件”最容易出现的问题就是“该文件不存在”,所以编译器“善意”地提醒你:“你要读取文件是吗,那你要确保文件存在哦,否则我就出错啦,你自己想想看怎么处理这个问题吧!”。

现在我们能够回答这个问题了:FileNotFoundException之所以是一个CheckedException,是因为我们在编程时经常遇到“文件找不到”的异常。为了让我们少犯这种低级错误,java每次都提醒我们要考虑到这种情况,强制处理这个问题。

综上,如果你写的某个方法“若使用不善,很容易出问题”,需要提醒使用者特别注意,就可以选择抛出CheckedException。

2.“对可恢复的情况使用CheckedException”

此观点来自《Effective Java》第58条,认为“如果期望调用者能够适当地恢复,对于此种情况就应该使用CheckedException”。

举个例子,我们尝试往某个文件中写入一段数据,但是这个文件根本不存在,怎么办?

如果使用UncheckedException(RuntimeException,一般不配合try catch使用),就表示如果文件不存在,这段程序无法工作,运行可以结束了。

综上,如果我们在某种程度上“希望程序能够恢复”(不要就这样挂掉,去找别的办法),就应该使用CheckedException。这时候我们可以捕获一个FileNotFoundException,然后自己创建一个新文件,把数据写入进去。

(2)UncheckedException

1.“无法预料”、“无法恢复”

NullPointerException是最常见的UncheckedException。在一般情况下,我们使用某个对象,都是在“该对象确实存在”的前提下出发的,结果运行时该对象为null,这是编译器“无法预料”的。而且,就算我们检查出来了对象为null,我们也没有办法恢复程序,总不可能随便new个对象给他执行吧…在这种情况下,最好的处理方法就是抛出一个UncheckedException,让程序自己挂掉(反正都无法继续执行了)。

ArithmeticException是运算错误。由于编译器不会实际执行代码,所以运算问题在编译期根本体现不出来,把ArithmeticException设计为CheckedException没有任何意义。如果程序中存在运算错误,我们除了更正程序以外,还有什么补救的方法吗?所以我们把ArithmeticException设计为UncheckedException。

IndexOutOfBoundsException是下标越界错误(在使用数组时,如果程序在运行时试图指向不存在的下标,就会报错)。程序下标的指向是编译器“无法预料的”,所以IndexOutOfBoundsException也是一个UncheckedException。

综上,如果某异常是“无法预料的”、“发现了也恢复不了的”,应该设计为UncheckedException。

2.编程错误、违反约定

此观点同样来自《Effective Java》第58条,认为“如果使用api的用户没有遵守api规范建立的约定(即编程错误),应该使用UncheckedException”。

例如,数组访问的约定指明了数组的下标值必须在0和数组长度-1之间。如果不遵守此约定,将抛出ArrayIndexOutOfBoundsException。

再举个例子,spring mvc中某接口涉及商品金额,约定金额不可为负数。如果某恶意请求违反约定,就应该抛出一个RuntimeException,表明该请求违反了约定(同时做日志记录,这应该是日常的编程习惯了)。

综上,如果某异常属于“编程错误”,违反了方法、接口的使用规范,应该设计为UncheckedException。

三、总结

个人认为,平时还是RuntimeException用得多。比起简单地使用,我们更应该区分使用的情况,体会设计背后的思想。

发表评论

电子邮件地址不会被公开。 必填项已用*标注