Java异常

标签: java 异常 | 发表时间:2014-03-23 18:09 | 作者:Vurteon
出处:http://blog.csdn.net

“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序。”

                                                                                                                         ----《Java编程思想》

         “充分发挥异常的优点,可以提高程序的可读性、可靠性和可维护性。如果使用不当,它会带来诸多的负面影响,甚至使程序编写无法继续。”

                                                                                                                         ----《EffectiveJava》

 

         异常是一把双刃剑,而且更多的时候会带来负面的影响。在编程中,程序员很多的时候不愿意去处理异常情况或者那些异常发生的时候已经对程序造成了不可挽回的错误,异常处理可能已经不能解决这个问题了,另外在业务处理中添加这异常处理代码,不可避免的影响了这个类本身的意义,降低了程序的可读性和可维护性。

         Java提供了可检查异常,目前还没有其他的编程语言采用此机制,而这种机制带来的更多的是不必要的麻烦,但考虑到诸多的原因,这一机制在未来被去除的可能性十分渺茫,如何才能更加优雅的在编程中使用异常确实是一个值得探讨的问题。

         首先来看下Java异常的整体关系:

 

         Java的所有异常都是继承自Throwable(这么看都感觉这是个接口),异常分为两大类:普通异常和错误。

         普通异常分为检查异常和非检查异常,所谓检查异常就是“在编译器强制检查”,平时编程的时候强制出现的异常就是检查异常,而非检查异常“又叫运行时异常,在运行的时候出现的异常”,比如空指针或者数组越界等等都归为此类。

         错误基本就是由于出现了不可挽回的错误从而导致整个程序的失败,这里就不作描述了,因为我们无能为力。

 

1.捕获异常和抛出异常

         这是对异常处理的两种方式,何时抛出?何时捕获?举一个简单的例子:除数是有可能为0的,所以进行检查时很有必要的。但除数为0代表的意义究竟是什么?通过当前正在解决问题的环境,或许能够清楚如何处理除数为0的情况。但是如果不能,就应该抛出异常。这是一个简单通用的原则,但并不是所有情况都适合。异常抛出之后会在堆中创建一个异常对象,从当前环境中弹出异常对象的引用,同时异常处理机制接管程序,并寻找一个恰当的位置来继续执行程序,这个恰当的位置便是异常处理程序。

         由于抛出和捕获使得我们可以把业务处理当作一个事务来进行,而异常可以看作是维护这些事务的底线,一旦事务出现问题,那么异常就有可能可以保障事务“恢复”到某个稳定点上。所以如果对出现的异常有“恢复”能力,那么捕获异常就是不错的选择。

 

 

2.检查异常和未受检查异常

         对于可恢复的情况适用受检查异常,对于编程错误适用未受检查异常。选择决定使用哪种异常的主要原则是:如果期望调用者能够适当地恢复,对于这种情况的就应该使用受检查的异常。因为它会强迫调用者在catch子句中处理该异常或者是将其传播出去。因此,方法中声明要抛出的每个受检查的异常,都是对API用户的一种潜在提示:这个异常是有可能被挽回的。

         未受检查异常往往标识编程错误,如果程序抛出未受检查异常,继续执行下去有益无害。如果程序没有捕捉到这样的可抛出结结构,将会导致当前线程停止,并出现适当的错误消息。由于Error往往被JVM保留用于表示资源不足、约束失败,或者其他无法继续执行的条件。而且这个已经是被普遍接受的管理,所以一般使用运行时异常来表示编程错误。

         大多数运行时异常都表示前提违例,即API调用者没有遵守API所建立的约定,比如传入了空值、数组访问越界等等,因此,你实现的所有未受检查的抛出结构应该是RuntimeException的子类。

         总而言之,对于可恢复的情况适用受检查异常;对于程序错误,则适用运行时异常。当然,黑白并不是那么分明,具体的编程应该具体分析,比如造成运行时异常有可能是由于编程错误引起的,是临时的错误,那么API设计者就需要考虑资源的枯竭是否允许恢复。

 

3.更好的捕获异常

         如果方法抛出的异常与它所执行的任务没有明显的联系,高层的处理需要此信息却并不是和此方法直接关联,这在分层的系统中经常出现。对于此类的问题,可以一层一层的抛出,但是这样不仅仅会让层次之间的耦合变高,而且还有可能由于底层的实现细节从而污染更高层的API。所以,一个更加好的方式是中间层捕获异常后抛出对高层处理更加适合的异常,即抛出于抽象对应的异常,这种做法被称为-----异常转义。从这里也可以看出,虽然捕获了异常,但是实际却没有去实现处理异常的细节。

         上面更好的做法也会导致一个问题----异常丢失,由于是抛出了自己新的对高层更加合适的异常,底层的异常便对高层不可见,这对问题的发现(特别是调试)是不利的。所以,高层应该同时能够捕获底层的异常,在JDK1.4以前,是使用编程的方式来实现这种需求,现在有更加适合的技术-----异常链。

 

4.更好的抛出异常

         抛出的异常对象中应该在细节消息中包含详细的失败信息。在异常出现的时候,可以打印堆栈信息,此方法可以得到和失败的一些消息,但是那个只是针对程序员的调试信息,在业务中处理异常的时候需要更加详细和适用的失败信息。异常的细节信息应该包含对处理该异常有贡献的参数和域的值,对异常处理的方法可以通过获取到这些信息。虽然目前Java平台还没有对此特定的支持,但是利用编程(比如使用getMessage())方法来得到相关的失败信息。

         提供这样的信息或者访问方法,相对于不受检查异常,对受检查异常更加的重要,因为受检查的异常一般是想从错误中恢复,所以提供细节信息是相当有必要的。不受检查异常想要获得这些信息一般是少见的(如果你遵守了前面的编程规则)。

 

5.失败原子性

         当由于失败抛出异常后,通常期望这个对象仍然保持在一种定义良好的状态之中,即使失败是发生在执行过程的中间。对于受检查异常而言,这显得尤为重要,因为调用者有可能期望能够从这种异常中恢复。一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性。

         这种思想与事务类似,具体可以实现的途径有以下四种:

                   1.final化属性

                2.调整计算处理过程的顺序,使得任何可能会失败的计算部分都在对象状态呗修改之前发生。(由于各种原因,这个在稍微复杂的编程中基本不能实现)

                   3.编写一段恢复代码。

                   4.临时拷贝对象,完成处理后在复制值到原始对象。

         实际的编程中很有可能还会考虑同步问题,所以,实现失败原子性其实并不是如人们所期望的那样。所以,一般而言,作为方法规范的一部分,产生的任何异常都应该让对象保持在之前的状态。如果违法这条规则,API文档就应该清楚的指明对象将会处于什么状态。

 

6.每个方法抛出的异常都要有文档

         异常的抛出意味着程序出现了问题,想要使程序恢复执行,需要格外的用心。所以,为每个方法抛出的异常编写详细的文档是十分有必要的。

         前提条件、状态变化、异常信息等等都应该详细的记录,抛出异常的方法应该使用Javadoc的@throws标记。

         这一块不要怕花时间,文档编写的价值是相当的高的。

 

7.优先使用标准异常

         虽然自定义异常更加的灵活,但是有可能使得代码的阅读性变差,很明了,阅读的人多了一道理解你定义的异常的步骤。但是,标准异常并不总是那么精确。使用自定义异常还是标准异常完全取决你的问题。

 

8.其它的一些建议

         1.只针对异常的情况才使用异常。

                   正常的控制流程中应该避免使用异常来解决问题。比如如下代码

                  

                   Try{

                            IntI = 0;

                            While(true){

                                     Range[i++].climb(); 

                            }

                   }catch(ArrayIndexOutOfBoundsExceptione){

                   }

                   很明显,相对于foreach语法,这种方式不但显得累赘,而且其效率还十分的底下,       而对于数组越界这种运行时异常,是不可恢复异常,属于编程错误(上面所述),应该  抛出而不是捕获。

         2.不要忽略异常

                   有时候由于不想处理异常,从而将catch后面的代码块置空,这样的做法就相当于  一个定时炸弹,而且当它爆炸的时候你根本不知道是它炸的!

         3.使用详细的异常

                   异常匹配不会像switch那样精确,使用详细的异常将是你日后的一笔财富!

9.闲话

         错误在编译的时候发现时最好的发现时间,一旦运行起来,就属于运行时错误。Java的异常机制是对使用C语言那些约定俗成的编程注意事项的包装。而运行时异常往往是无法挽回的,对于Java的异常到底对Java编程有多大好处还是相反,这个要辩证的看待,确实,很多时候即使是那些检查异常也无法挽回,失败原子性的要求就更加的高。

         异常的处理和业务处理往往其关联程度是不大的,如果放在一个类中进行处理,对于以后来说可能将会是nightmare!所以,将异常处理和业务处理分离是义不容辞的责任,具体的如何处理可以参考Spring的异常处理方式。

         对于目前的我来说,异常最大的好处就是“报告”,在程序开发的时候会带来许多的方便。Java的异常就像其泛型一样备受争议,到底如何优雅的使用异常是本文讨论的一个话题,但是,具体的如何写出优雅的异常,还是具体问题具体分析和经验的问题,这里,所能做的,只能给你提供前人所总结的经验。

作者:Vurteon 发表于2014-3-23 18:09:41 原文链接
阅读:64 评论:0 查看评论

相关 [java 异常] 推荐:

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

浅谈java异常

- - 移动开发 - ITeye博客
在《java编程思想》中这样定义 异常:阻止当前方法或作用域继续执行的问题. 虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常. 绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败. 之所以java要提出异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意.

Java异常处理策略

- - 研发管理 - ITeye博客
任务与预先设定的规则不相符的情况都可以称之为异常. 但凡业务逻辑操作,都会划定一些边界或规则,但是往往事与愿违,总会有调皮鬼来挑战系统的健壮性. 用户并不都是知道潜规则的,比如用户的银行账户中只有100块钱,但是用户不查询就直接取200块. 码农有时候太过自信了,比如你在编写文件下载功能时忽略了文件有可能不存在这个分支流程.

Java异常处理的陋习展播

- - 博客 - 伯乐在线
注:本文来自志军(@ _Zhijun )在微博推荐的一篇转载于2007年09月27的旧文,他说“没法找到原作者”. 的确,我也花了半个多小时在找原作者. 先是找到了一个标注“2005年6月18日转字Java研究组织”的文章(“Java研究组织”的域名已过期),后来找到是大连理工大学碧海青天BBS上一个发于的2003年5月中旬的 合集帖子,其中提到一条来自 CSDN 的链接,可惜该链接已挂,否则应该能找到作者的.

Java异常的性能分析

- - Java译站
在Java中抛异常的性能是非常差的. 通常来说,抛一个异常大概会消耗100到1000个时钟节拍. 通常是出现了意想不到的错误,我们才会往外抛异常. 也就是说,我们肯定不希望一个进程一秒钟就抛出上千个异常. 不过有时候你确实会碰到有些方法把异常当作事件一样往外抛. 我们在 这篇文章中已经看到一个这样的典范):sun.misc.BASE64Decoder之所以性能很差就是因为它通过抛异常来对外请求道,”我还需要更多的数据“:.

Java Socket常见异常处理

- - BlogJava-qileilove
java网络编程Socket通信中,通常会遇到以下异常情况:.   第1个异常是 java.net.BindException:Address already in use: JVM_Bind.   该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时.

捕获Java线程池执行任务抛出的异常

- - BlogJava-首页技术区
Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常,. 那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了. 通常java.lang.Thread对象运行设置一个默认的异常处理方法:. 而这个默认的静态全局的异常捕获方法时输出堆栈.

Java EE项目中异常设计及处理总结

- - Java - 编程语言 - ITeye博客
异常,为我们处理非正常的业务流程提供了很好的解决方案,如果你有过dbase、c、pascal等过程式语言开发的经历,你一定会深刻体会到,异常机制给你的代码可读行、可维护性带来的好处,同时,程序的健壮性也得到了增强. 在 java项目中,异常设计要注意下面的几点. A、自定义异常的父类,可以选择为RuntimeException或Exception.

深入理解java异常处理机制

- - 编程语言 - ITeye博客
       try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解. 不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话. 那你看看下面的代码,“猜猜”它执行后的结果会是什么. 不要往后看答案、也不许执行代码看真正答案哦.

Java 异常处理的误区和经验总结

- - 编程语言 - ITeye博客
原文:http://www.ibm.com/developerworks/cn/java/j-lo-exception-misdirection/index.html. 本文着重介绍了 Java 异常选择和使用中的一些误区,希望各位读者能够熟练掌握异常处理的一些注意点和原则,注意总结和归纳. 只有处理好了异常,才能提升开发人员的基本素养,提高系统的健壮性,提升用户体验,提高产品的价值.