这篇和前面的 总结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也不强制去处理它。