【JavaSE进阶】异常的定义、使用和处理
这里写下我对 Java 中异常机制的理解,有些知识是来自书本和实践,有些则完全是我的个人观点,对不对的欢迎大家讨论指正 :-)(博客原文链接: http://blog.csdn.net/raistlic/article/details/15807781)
0 - 异常的处理
=====
异常的处理应该尽量集中在某一层处理,处理异常的地方应该知道足够的上下文信息,知道应该如何处理异常。
对于异常的处理,用户代码(Client Code)掌握上面的简单原则就行了,这不是重点,——重点是异常的定义与抛出(使用)。
1 - 异常前后,各相关部分的状态
某个异常何时发生?
因为什么发生?
一旦异常发生,各相关的对象(数据)都将处于何种状态?(所谓相关对象,就是说方法调用路径所经过的每一层的对象,从 new 异常的那一层一直到 catch 异常的那一层)
或者你无法预料这些对象、数据将会处于什么状态?
程序还能不能继续运行?
如果相关对象、数据在异常发生后都处于可以预料的稳定状态并且程序能继续运行,要不要提示信息给用户?
提示什么信息给用户?
异常有没有可能是用户操作错误导致的?
用户有没有办法通过更正操作等方式调用同一个功能,成功避免这个异常?
如果异常不是用户操作导致的,那么程序继续运行,用户做同样的操作,会不会总是发生这个异常?
=====
-> 如果异常发生后,你不能预料相关对象或数据的状态,或者你预料相关的对象或数据将会处于一种不稳定(或非法)的状态,比如对象内部的状态不再遵从其应有的约束(constraints),如果对对象进行进一步的操作,将会导致不可预料的行为………… 那么这种异常不可恢复,属于应该导致程序崩溃退出的异常。这时,显示友好的信息给用户,比如 “为了防止(对数据产生)更多的损害,程序将停止运行………………”,然后尽最大努力安全结束程序。
这样的异常,应该设计成非检查异常(unchecked exception),一般即 extends RuntimeException 或其子类型。
-> 如果异常是因为非法的用户输入导致的,那么应该在程序中避免,即非法的用户输入应该在其导致异常之前就检验出其不合法,然后不去调用功能,而是反馈合适的信息给用户。
如果检查输入的合法性代价非常昂贵,或者在调用功能之前无法检查其合法性:一种可能是这个功能的设计有问题;如果没有问题,就是无法在调用功能之前检查,或者检查太昂贵,那么就只能 try - catch,这时要明确一旦异常发生,整个调用路径的每一层都将出于什么状态,如果要确保异常发生之后程序能继续运行,那么每一层都应该处于原本调用之前的状态,就像调用没有发生一样。然后你就可以放心给用户提示信息,然后让用户重试了。
(在调用功能之前无法检查其合法性,这种情况大概多存在于多线程环境下,当某接口对外保证线程安全的时候)
这里的异常,可以设计成检查异常(checked exception),也可以设计成非检查异常。这是设计上的取舍问题,如果调用路径很长而你不愿意污染每一层方法的签名(throws),——那么设计成非检查异常。这里的核心问题是注明异常发生后的状态。
2 - 在一个方法被调用以前,有没有办法事先检查这个调用是不是一定会发生异常?
=====
-> 即有没有办法来检查:这个调用到底是一定会发生异常,还是不一定会发生异常?如果有办法检查,——提供这个检查的方法。这样用户代码就不会易做图使用 try - catch 来做流程控制,而是可以先调用这个检查的方法,再决定要不要调用功能方法本身了。要注意的是,这样的设计,会让接口失去“保证线程安全”的可能性,即这样设计的接口不可能向用户代码保证线程安全,——因为两个方法的调用之间产生了逻辑关系。
曾经看到有人问 “怎样得到一个线程安全的List?”,——不存在 “线程安全的List” 这种东西,因为 size() 方法与 get(index) 方法是否会抛出 IndexOutOfBoundsException 之间存在逻辑关系,接口本身无法为这两个相关的调用提供原子性保护。
能够这样先验检查的异常,应该定义成非检查异常,否则用户代码调用了检查方法之后还得再写 try - catch,那个理论上不应该发生的catch 里面你让用户代码写什么呢?
3 - 小结
现在咱们来看看,什么样的异常可以设计成检查异常。
=====
如果一个异常可以定义成检查异常,它需要满足下面这些条件:
- 异常无法在调用方法之前预料
- 异常发生以后,相关的对象、数据将仍然处于一种稳定的状态,即异常被catch处理之后,程序可以(confidently)继续运行
- 异常如此重要,以至于整个调用路径上所有的方法代码都应该意识到它的存在,并且把 throws 写进它们的方法签名
这样的异常,可以定义成检查异常。注意是可以 :-)
—— You can never be wrong to define an exception unchecked. 任何一个异常,你把它定义成非检查异常总不会错。(但是要注意把异常写进 Java DOC,即方法的协议)
事实上 “检查异常” 存在的必要性本身就是有争论的。
对于一个懂得异常处理的程序员,他们对非检查异常也会小心仔细的去处理和设计;对于一个不懂得异常处理的程序员,提供“检查异常”这种机制只会导致大量的滥用,然后大量的接口签名被污染。
如果你发现自己在代码中频繁的 catch 一个检查异常,然后转换抛出一个非检查异常,那这个检查异常的设计多半有问题。(java.io.IOException是不是中枪了?)
--------------------编程问答-------------------- —— You can never be wrong to define an exception unchecked. 任何一个异常,你把它定义成非检查异常总不会错。
这句话我不太赞同,特别对于一些对java接触不是太深的人来说,使用检查异常很多时候能够避免异常不被处理。 --------------------编程问答-------------------- 这个异常可以写这么多子字。。。
对于异常,简单注意几点。
1、捕捉到得异常是否立即处理。项目会规定好的。
2、该怎样处理,直接down掉服务还是继续提供服务
3、写异常日志,便于bug定位。
--------------------编程问答--------------------
在我看来,【异常不被处理】这件事,责任在于用户代码,而不在于异常本身的设计。
打个比方,现在我做这样的声明:
当一个路口的行人交通灯是红灯的时候,人行道两头应该升起两面墙,因为过马路的新手可能会无视红灯过马路。
这样的设计合适吗?我认为不合适。
——行人闯红灯,是行人的责任,不能因为有人说:红灯不够明显竖个墙才够明显,就竖个墙。
——竖的墙会有别的损害,会遮挡路口汽车的视线,墙升起的时候可能会有安全问题,会让新手行人对墙产生依赖而在某个只有红灯没有墙的路口闯红灯…………
——如果一个行人就是要闯红灯,他可以翻墙闯红灯。
对应到异常的问题:
——异常不处理,是用户代码的责任,不能因为有人说:写在Java DOC里不够明显,就用检查异常。
——检查异常会有别的损害,会污染从抛出到处理每一层方法的方法签名,取决于你具体的组合设计,中间的某些层的方法也许本来不应该跟这个类型的异常紧耦合的,其方法签名中本来不应该出现这个异常的;检查异常帮助培养出一些不出编译错误就不去考虑异常的新手,当他们遇到非检查异常的时候他们会完全意识不到然后忽略问题
——很多新手会 try - catch 然后 catch 里什么都不做,如果真的发生异常那么就是 silent fail --------------------编程问答--------------------
这里的1、2、3归结起来都是说异常的处理,即文中的第0条;本文主要讨论异常的设计和抛出。 --------------------编程问答-------------------- 楼主对异常理解好深啊。我还看不太明白,帮顶! --------------------编程问答--------------------
谢谢支持~ --------------------编程问答-------------------- 贴个你设计的异常给大家分析下不是清晰明了很多,还有能不能不用用那些大作里面的专业词汇?
补充:Java , Java SE