人在江湖

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  82 Posts :: 10 Stories :: 169 Comments :: 0 Trackbacks

这篇和前面的 总结java的interface和abstract class 一样。跳过最基础语法不聊,只说一些比较tricky的东西和一些好的practice.

 

语法:

Exception继承自Throwable. Throwable还有一个儿子是Error,但是一般用不到。不过有些二百五面试官喜欢问回字有几种写法,所以还是要知道有Error这回事的。Exception分为checked和unchecked两种。

 

java的checked exception一直是个很有争议的东西。Thinking in Java的作者Bruce, C#的设计者和Martin Fowler都对checked exception持质疑态度。 一个典型的反对理由是:"We felt it was unrealistic to require the programmer to provide handlers in situations where no meaningful action can be taken." 这话太实惠了。写方法给caller调用,怎么能magically知道调用这个方法的所有caller,能够处理特定的异常呢?

 

关于Exception有个比较基本的语法是,子类override的方法如果声明抛出exception, 只能抛出父方法声明的exception,或者那个exception的子类。需要注意的是,“回”字还有一种写法,对于constructor来说没有这个限制,子类可以抛出任意exception。父类构造函数声明的exception,子类也必须声明,而且子类的构造函数不能捕捉父类声明的exception. 这个想想也容易理解,父类构造出错了,儿子居然能处理还把自己生出来了,没老子哪来的儿子?当然你可以抬杠说老子戴绿帽子的情况。

 

实践:

1. 尽量不要在构造函数里做复杂的操作,尽量不要让constructor抛出exception。如果在构造函数里抛出exception,需要用nested try block. 如下:

   1: public class Cleanup { 
   2:   public static void main(String[] args) { 
   3:     try { 
   4:       InputFile in = new InputFile("Cleanup.java"); 
   5:       try { 
   6:         String s; 
   7:         int i = 1; 
   8:         while((s = in.getLine()) != null) 
   9:           ; // Perform line-by-line processing here... 
  10:       } catch(Exception e) { 
  11:         System.out.println("Caught Exception in main"); 
  12:         e.printStackTrace(System.out); 
  13:       } finally { 
  14:         in.dispose(); 
  15:       } 
  16:     } catch(Exception e) { 
  17:       System.out.println("InputFile construction failed"); 
  18:     } 
  19:   } 
  20: } 

而不是用finally来做清理工作。

 

2. exception的一个基本使用原则是,exception不是设计用来控制程序flow的。 这是很简单的道理,还是引用effective java的一个例子吧

   1: // Horrible abuse of exceptions. Don't ever do this!
   2: try {
   3: int i = 0;
   4: while(true)
   5: range[i++].climb();
   6: } catch(ArrayIndexOutOfBoundsException e) {
   7: }

我真正要说明的是,上面说的原则很对,但是走到极端就不对了。有的人为了 不用exception控制程序flow, 就写一大堆的if…else语句试图考虑各种情况,正好前不久有同事说了个笑话,我觉得可以辅助解释这个问题。

=============================

某日,老师在课堂上想考考学生们的智商,就问一个男孩“树上有十只鸟,开枪打死一只,还剩几只?”

男孩反问“是无声手枪,还是其他没有声音的枪么?”

“不是。”

“枪声有多大?”

“80~100分贝。”

“那就是说会震的耳朵疼?”

“是。”

“在这个城市里打鸟犯不犯法?”

“不犯。”

“您确定那只鸟真的被打死啦?”

“确定.”老师已经不耐烦了,”拜托,你告诉我还剩几只就行了,OK?”

“OK.鸟里有没有聋子?”

“没有。”

“有没有鸟智力有问题,呆傻到听到枪响不知道飞的?”

“没有,智商都在200以上!”

“有没有关在笼子里的?”

“没有。”

。。。

==============================

后面还有一堆“例外”情况。我们写程序总不能真写成

if(鸟是聋子)

else if(鸟是傻子)

else if(鸟是瘸子)

原则应该是,如果一些情况确实是 “例外情况”,就用exception处理吧。不要很勤奋地写一堆defensive的判断。我们不会有故事里的小男孩儿思维那么滴水不漏的。别把Java程序退回c语言了。另一个例子是FileNotFoundException, java I/O没有让你每次用文件都提前调用exists()检查一下,我想原因不光是 检查文件的那一毫秒文件存在,run到下一步的时候,下一毫秒文件消失了,Sun没觉得你人品那么坏吧。理念仍然是,如果你觉得文件肯定存在,你就直接用吧,一旦不存在,你再另外当成异常情况处理。不要让一堆if…else弄脏了程序。

 

3. exception有个典型用法是在方法体中,进行参数合法性校验

 

   1: public BigInteger mod(BigInteger m) {
   2: if (m.signum() <= 0)
   3: throw new ArithmeticException("Modulus <= 0: " + m);
   4: ... // Do the computation
   5: }

也有很多人用assert语句判断, 比如 Assert.notNull(object)。手动抛exception可以抛特定的类型,assert语句更方便。可以根据实际情况取舍。

 

4. 既然上面说了checked exception本身是java设计不太合理的地方。我倾向于说,应该及时把checked exception translate成unchecked. 我知道exception的处理原则有一条是,如果你不知道怎么处理它,就不要捕捉它。 对于checked exception来说,它总force你去处理,太讨厌了。如果caller不知道怎么处理,留着给更上层的程序处理…底层的程序都不会处理,一般来说上层的程序就更不知道该怎么处理了,那还不如在尽量底层的调用中,要么处理它(这种情况很少,打log之类的算不上“处理”),要么就转成RuntimeException抛上来, 消灭掉checked exception带来的burden. 注意:translate exception的过程中,不要扔了原来的exception, 而要把它放在exception constructor的argument里, new RuntimeException(e)。这是很基本的东西,不多说。

 

5. 不要吞exception. 这个太基础,不多说。

 

6. 见到exception要把它记log里,而不是简单print stack一下,log4j的api有可以接受Throwable作为参数的。

 

7. 每层抛出来的exception要对当前这一层有意义,比如persistence层出问题,UI上你告诉客户hibernate的session关闭了,不能继续load数据了,客户还以为你的程序怎么跟狗熊一样还会冬眠的。即使是UI层以下,底层exception,比如sql exception也不要爬到domain层里处理

 

8. apache commons的lang包里有ExceptionUtils类,玩儿exception最好把这个工具揣口袋里。

9.exception是设计的一部分, 但它不同于API的设计。通常我们设计API的时候,不会设计一个函数destroyBaghdad(),通常我们会写destroyCity(Baghdad)。这样做的目的是为了重用。换句话说,你设计API的时候,总是装作忘了use case(caller), 而去写适合复用的API, 尽管上面的例子use case就是destroy Baghdad, 你还是要写更general的destroyCity函数,然后把城市的名字作为参数传进去.但exception的设计不应该用同样的思路做,因为你很难料想到复用的情况下,你声明的exception是不是总能在任何情况下都得到妥善的处理。举个例子,这是我临时想的例子,形象但是科技太超前了。我们写一个print()程序给打印机A,print()的时候没有纸可以抛一个checked NoPaperException,这时候exceptino的处理程序可以自动加载纸(目前这么高级的功能正在贝尔实验室研发呢)。也许有新型的打印机B总是先加纸,后打印,那么永远也不存在NoPaperException。如果打印机是老式打印机C,不会自动加载纸,见到NoPaterException也无计可施,没法处理。checked exception的哲学是,强制让caller处理它。从上面的例子看,只有A打印机需要并能够处理NoPaperException。 B打印机不需要处理exception。C打印机没能力处理exception. 所以,|“需要并能够”处理是个太严格的限制,一般情况下不应该用checked exception. 我们可以让print声明抛出unchecked exception. 提醒caller可以处理它,但是对于不应该处理它的caller也不强制去处理它。

posted on 2011-02-22 00:12 人在江湖 阅读(9130) 评论(5)  编辑  收藏 所属分类: java

Feedback

# re: 总结java的exception 2011-02-22 21:43 人在江湖
很奇怪,这篇blog点击率高的离谱,你们是怎么看到这个blog的?我之前的blog每天最多一千出头的点击率,这篇24小时内到四千多了,真的困惑。  回复  更多评论
  

# re: 总结java的exception 2011-02-22 23:01 HappyGao
@人在江湖
是从首页看到的呀。  回复  更多评论
  

# re: 总结java的exception[未登录] 2011-02-24 16:29 llq
楼主很幽默,我是初学者,多多向你学习  回复  更多评论
  

# re: 总结java的exception 2011-02-25 13:24 Lu Han
好文!
最近洒家刚刚被问到回字的写法哦!  回复  更多评论
  

# re: 总结java的exception 2011-02-28 09:10 mashiguang
你都骂了250了,我懂不敢告诉你是“茴”而非“回”了,管他3721,我就孔乙己一回,相信楼主明白我是好意提醒。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: