【转】个人对java错误的一些探索

【转】个人对java异常的一些探索

【转】http://www.blogjava.net/shenjingta/archive/2008/04/15/193192.html

很多事情都可以总结为WWWH问题。也就是What When Why & How。java异常恰巧是其中之一。 


首先,java的异常是什么?抛去官方冗长的定义,我觉得异常或者说exception,其实可以理解为这样一句话:“wow!!这可不是我期待的状态”。 


如果这样理解异常,那么对于何时使用异常也就再清楚不过了。那就是,只有当出现了不在正常流程之中的某种状态,这个时候就应该使用异常了。比如连接超时、传入的参数为null或者数据无法插入到数据库。 


那么,为什么我们要使用异常呢?理由其实很简单,为了更加简洁的代码或者说为了OO。如果java没有异常,我们会怎么处理呢? 
if(aMethod() == -1 ) 
{ 
//do someting 
} 
else 
{ 
//do someting 
} 
有了异常以后,我们怎么做呢? 
try 
{ 
aMethod(); 
}catch(MyException e){ 
//do someting 
} 
或者 
public anotherMethod() throws MyException 
{ 
aMethod(); 
} 
这里只列举了一个最简单的例子。如果aMethod()这个方法到处都需要调用,这种好处就会更显著了。 


至于我们需要不需要使用异常呢?答案必然是肯定的。但是网上也有一些声音说,异常是一种耗费性能的操作,应该少使或者尽量不使。我认为,这跟没有很好的理解如何使用异常有关系。 


下面我就来说说,最重要的一点,也就是how,如何使用异常。 


首先,我们来看看,当发生异常的时候,JVM做了什么? 



为了抛出异常,JVM 发出 athrow 字节码指令。 athrow 指令引起 JVM 将异常对象弹出执行堆栈。然后 JVM 搜索当前执行堆栈帧来寻找第一个 catch 子句,这个子句可以处理该类的一个异常或者其超类的一个异常。如果在当前的堆栈帧里没有找到 catch block ,那么当前堆栈帧就被释放,异常在下一个堆栈帧的上下文中被重新抛出,如此这般,直到找到包含匹配的 catch 子句的堆栈帧,或者是到了执行堆栈的底部。最后,如果没找到适当的 catch 块,所有的堆栈帧都会被释放,线程在 ThreadGroup 对象有了处理异常的机会后被终止(参考 ThreadGroup.uncaughtException )。如果找到了适当的 catch 块,程序计数器会重置到那一 

块代码的第一行。 (注:1) 



从上面的描述可以看出,抛出异常是一种代价很高的操作。有的人可能要问了,如果只是为了简洁的代码或者OO,这种代价值得吗?我的回答是,不值得!!!这点和我上面说的并不冲突。如果有一点经济学的知识,一定会知道边际量这个概念,说白了就是我多花一个单位的成本,是否可以带过我多于一单位的收益。将这点用到编程上也是通用的。 
我们可以考虑这样一段代码 
for(int i=0;i<list.size();i++) 
{ 
AClass myClass = list.get(i); 
if(myClass.aValue!=null && !"".equals(myClass.aValue)) 
{ 
//do something 
} 
} 


这段代码很好的处理了参数不正常的情况。但有一个问题,每次循环程序都需要判断参数是否正常。假设我们知道大部分的参数都是正常的,那么这些操作就显得有些浪费了。如果改成 
for(int i=0;i<list.size();i++) 
{ 
AClass myClass = list.get(i); 
try 
{ 
//do something 
}catch(Exception e){} 
} 
这样只有当很小几率发生错误的时候,我们就可以节省一些操作。这也就是经济学上说的边际收益大于边际成本的时候,只有这个状态,我们才应该这样使。反观另外一个状态,如果我们不能确定参数是否大部分为正常或者我们确定大部分参数都不会为正常,那么就应该使用第一种方法。总之,这点是需要灵活掌握的。 


另外一个值得注意的地方就是,不要将异常用于流程跳转,异常就是指异常的状态,而不是控制语句。如果你能注意这点,你就已经避免了90%的错误异常应用。举个例子。inputStream#read大家一定都用过,为什么read方法要返回-1作为读取到文件尾的状态,而不是用异常,就是这个道理。 



关于如何使用异常的另外一点就是,我们如何catch异常。这一点其实也很简单,总结为一句话就是,如果你不知道如何处理这个异常,那么就不要catch这个异常。 


我们经常会遇见这样一种情景:我们调用了某个接口,这个接口抛出了一个checkedexception。这个时候就是考验你的时候了,catch还是throw 这是个问题。我的经验是,如果你觉得这个异常是一个可恢复的异常,比如参数错误,那么你就需要catch这个 

exception,然后告诉用户重新输入。如果这个exception是一个不可恢复的exception,比如SQLException,那么你干脆就不要处理它,把它留给底层去处理,让底层定向到统一的异常页面。当然,这种操作并不是严格的,你也可以catch这个exception,然后返回到你自己的异常页面。 


最值得注意的是,永远不要没有理由的私吞exception。比如 
try 
{ 



}catch(Exception e){ 
//do nothing 
} 
如果你确定可以这种做,那么没有关系。但是如果你不能,千万一定不要这么做,如果你这么做了,很有可能导致无法定位异常,把自己或者别人搞到死。 



关于如何使用异常的最后一个问题就是runtimeexception 或者 checkedexception,这是个问题! 


很长一段时间,我也曾困惑于这个问题,记得以前看过一本书,好像是java夜未眠,记不清楚了。里面说,要尽量使用runtimexception,但是当我这样做的时候,问题就来了,调用我接口的人不知道我的方法会抛出什么异常,也就无从处理了,这样就导致了程序的不健壮。其实这个问题需要根据你的业务进行判断,没有什么尽量或者最好。一个简单的原则是如果希望客户程序员有意识地采取措施,那么抛出checkedexception.举两个例子 
public void aMethod(String s) 
{ 
if(s==null) throw new Runtimeexception(); 
} 
public void otherMethod(String uri) throws NoSuchFileException 
{ 
try 
{ 
File file = new File(uri) 
}catch(IOException e) 
{ 
throw new NoSuchFileException() 
} 
} 


看出点什么了吗?你肯定很奇怪为什么都是参数不正确,第一个方法抛出的是一个RuntimeException而第二个是一个CheckedException。因为s为null是一个我们非期待的参数,而这种状态可能并不是经常出现的,属于一种非业务错误或者不可恢复异常,我们不需要明确的通知客户程序员。 



而第二个不同,第二个异常代表用户输入了某种错误的参数,而这个异常需要客户程序员去通知用户。所以要求客户程序员去catch这样exception。 


以上是我对异常的一些理解,如果大家有不同意见可以进行讨论