面向Java新手的日志 承 一 异常的使用

不说废话了,直接进入正题。容我吃几个砂糖橘以及旺旺煎饼,呵呵。。。肚子饿。

 

这一篇的主要议题是:异常的使用,the usage of Exception。

 

异常在Java里是再常见不过的东西,表征一段代码遇到了自己搞不定的事情,需要程序员来干预。比如说,数据库连不上啊,文件不存在啊。这种事情发生了,代码是没有权力也没有办法来判断要怎么办的。数据库连不上,是发邮件还是打电话还是咋样,代码怎么会知道?于是Java系统里出现了异常这个概念。

 

在Java的语言规范里,有一些操作是必须要用try catch结构包裹的,比如连接数据库或者是文件读写,coder必须这么做,别无他想。但是这样也带来一个问题,就是程序员们被动无奈的接受了异常的存在,却不去考虑异常究竟应该怎么用,异常究竟有什么用。

简单的说,异常是表示程序发生了问题,学术一点,就是说Java程序违反了JSR规定的语义。当虚拟机执行代码发现异常的时候,会就近寻找catch块。如果找到了,那么判断一下catch的类型和当前异常类型是否匹配,如果是就进入并执行,如果不是就继续找,一直找到main里,如果还没有,那么就丢出去。这里就牵涉到一个入栈出栈的操作。搞过C系列的应该知道,代码顺序执行是最快的,到处瞎跳,使劲判断的话是最慢的。VM发现有异常发生的时候,会不得不停下来去逆向搜索代码,这样的处理方式决定了不管是抛出异常和处理异常都是极其昂贵的操作。

 

异常是不得已而为之的事情,能不用,尽量不要用。

 

下面 ,谈谈异常的几个使用要素。

首先明确一下Java语言里对于违反语义的分类。众所周知,Java里有Error和Exception两类表征违反语义的类。Error表示不应当出现也不应当由程序员来处理的问题,比如内存溢出,虚拟机内部错误。这些东西,不应当由程序员来处理,而是应当由VM的开发商来修复。换言之,它们不应当出现在你我视线范围内。而Exception又分为checked和unchecked两类,也就是须查异常和不须查异常。所谓的须查异常,就是在方法签名上写有throw的。这样的方法签名,编译器会强制要求使用try catch结构包裹。而所谓的不须查异常,则是指方法内部抛出了某些异常,但是没有通过throw的方式强制表征和约束出来。

 

那么,这两类异常应当如何合理使用呢?当然,异常是能不用就不用的。

我们就先来看看这个能不用就不用,是怎么个不用法。String类有个方法,叫做indexOf。在字符串内查找一个子串。比如说, "abc".indexOf("ab") ,结果就是0.表示这个字符串在一开始就出现在了需要遍历的串里。那么,我提一个问题,为什么Sun当时实现这个方法的时候,在两者不匹配的时候,返回的是-1而不是抛出异常呢?

 

这里就牵涉到一个异常的使用原则,也即是说,对于频繁出现的,不属于错误的情况,不要使用异常来表示。字符串里找字符,找不到,这很正常啊。比如用户输入的信息不包含关键字什么的,如果每次都来个异常,那么虚拟机只好累死累活的去处理这么些啰嗦的逻辑(如果不相信,可以自己写一段包含try catch的代码,再写一段不包含的,反编译一下,就明白了)。每次增加的这些消耗,最终将归入系统的响应时间这个指标里,系统性能严重下降。诸如此类的还有,文件读到了末尾,网络流已经读取完毕,等等等等。这些都是常见的不能再常见的,文件总有个结束,是吧。当然,你用异常来表示,没有问题,没错。但是性能不好,而且如果是循环执行的代码,会衍生出更多的问题,比如内存泄露什么的,这些留待以后再说。

 

那么,对于频繁出现的,属于错误的,要怎么办呢?比如说,数组下标越界,比如说,数据库执行语句。在继续讲解之前,我们先来分析一下情况。下标越界是常见,不错。但是貌似大家都习惯了写类似 i < list.size()这样的代码,对吧?这样写,一般来说是不会出问题的。但是数据库执行语句这个事情,就不是程序员能力范围内的了,网络断开,数据库实例停止运行,数据库配置错误,都有可能造成问题的发生。针对这些情况,Java也给出了很好的示范。

 

下标越界呢,的确有这么一个异常,叫做ArrayIndexOutOfBoundsException。当你越界的时候,JVM会给你提示的,也就是抛出异常。

数据库执行语句的时候,ps.execute()这样的操作根本就不是全部由JVM来控制的,出现问题的可能性相当之大。于是,JVM规定,你必须要用try catch来包裹这样的语句。

 

发现没有?结合上面的描述,下标越界的用法就是一个典型的不须查异常,而数据库的SQLException,就是个典型的须查异常。须查异常是用来强化并且强制调用方必须检查异常的一种操作,为了安全。之所以强制,是因为被调用方明确知道会有很大可能出现控制范围之外的情况,不得不强制要求检查。尽管有一定几率不发生问题,但是Java不就是图个稳定么。不须查异常则是用来在出现了一些不应当出现的情况的时候,给调用方提个醒,仅此而已。这些使用方法,究其根本原因,就是JVM对于异常处理块的编译和处理方法造成的。

 

再给出一个例子。集合类有一个sort方法,在API里有这么一段话。

 

“在已经排序的、不可修改列表上调用 sort 方法可能会(也可能不会)抛出 UnsupportedOperationException。”

 

也就是说,Collections的sort方法,对具体实现没有做明确规定。你可以不支持。我们先来看签名。

 

public static <T extends Comparable<? super T>> void sort(List<T> list)

 

sort方法的入参是List的泛型子类,比方说,ArrayList或者LinkedList。但是并不是所有的列表都支持排序操作的,当你对一个不支持的集合类进行了此种操作的时候,这个实现类有权力对你的操作进行响应。这个响应有可能是咣当一个异常,UnsupportedOperationException。也有可能是返回一个null给你,偷偷看你被UnsupportedOperationException的哥们NullPointException调戏。也有可能把自己返回给你,告诉你,哥不跟你玩。

这一切的一切,都取决于这个实现类的实现思想和指导思想。如果sort以后的后果很严重,那么抛出UnsupportedOperationException是有必要且必须的。比如按照特定顺序排列的列表,不能让你排序,你要硬来的话,只好不干了。但是链表这种本身就是有序列表的玩意,你sort来干嘛?从头到尾捋一下,和没捋有什么区别?这时候,直接return this就行了。这里是使用须查异常还是不须查异常,就取决于实现类的开发人员对于这个问题的看法了。严重与否,频繁与否,仅此而已。严重的,必然抛出异常。其他的,就能不抛就不抛吧。当然,如果又频繁又严重,那么就不是异常不异常的事情了,麻烦你改代码改设计去。一个经常出现严重问题的方法,为毛还要存在,是吧?

posted @ 2011-12-03 22:54  徐浩然  阅读(302)  评论(0编辑  收藏  举报