首页 > 代码库 > Java编程思想(十一) —— 通过异常处理错误(2)
Java编程思想(十一) —— 通过异常处理错误(2)
前面一篇提到异常的基本概念和使用的原因,后面一截比较难,然后就分开写出来。
1)finally清理
对于一些代码,无论try有无抛出,都希望执行,为了这样可以在异常处理最后加上finally语句。印象最深的是与数据库打交道的时候,finally永远要记得回收Connection等等。
这样无论异常是否抛出,finally子句总能被执行。这样就不怕发生内存中的资源一直占用的情况,印象深刻的还有老师讲的,公司一个新来的忘记写finally语句,后面内存的东西越来越多,整个系统死掉了。
有一个特别的是,try中有return语句,还是会继续执行finally里的语句。
缺憾:异常丢失
class ImportantException extends Exception{ public String toString(){ return "import"; } } class LightException extends Exception{ public String toString(){ return "light"; } } public class LoseException { static void i() throws ImportantException{ throw new ImportantException(); } static void l() throws LightException{ throw new LightException(); } public static void main(String[] args) { try{ try { i(); }finally{ l();} }catch (Exception e) { e.printStackTrace(); } } }
important的异常还是丢失了,虽然书上的作者早已希望未来的版本会修正这个问题,但是目前版本7还是没改善啊。
另外,finally子句只用return也会丢失异常。
2)构造器问题
这个问题比较复杂,看了好久,和之前处理的不一样。
书上的原话,构造器会把对象设置成安全的初始状态,但是会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才清理。
为什么说和以前不一样,以前确实是像作者所说,用finally最后处理就行了。但是,构造器在创建的时候就失败呢?
<span style="font-size:18px;">public class TestConstructor { private BufferedReader bf; public TestConstructor(String name) throws Exception{ try { bf = new BufferedReader(new FileReader(name)); } catch (FileNotFoundException e) { System.out.println("can not open!"); //其实还没打开,不需要关闭 throw e; } catch(Exception e){ try{ bf.close(); }catch(IOException ie){ System.out.println("close faild"); } }finally{ //不要在这里关闭 } } public String getLine() { String s; try { s = bf.readLine(); } catch(IOException e) { throw new RuntimeException("getLine failed!"); } return s; } public void dispose(){ try { bf.close(); System.out.println("dispose successful"); } catch (IOException e) { throw new RuntimeException("getLine failed!"); } } }</span>
io流,构造器传参之后能把FileReader和BufferedReader合在一起,看不懂的话可以提前翻一下io流的内容。FileNotFoundException,其实文件都找不到,我们是不用进行清理的,因为finally在每次构造器完成都执行一遍,不能把关闭的语句写在这里,为什么和以前把清理的代码写在finally中不同呢?
目的不同,在这里我们需要TestConstructor对象在整个生命周期都处于打开状态。而之前的那些我们希望的是快点用完快点关闭。
还有一个特别的地方,本来String是要初始化的,但是因为异常的抛出可以不用初始化。真正不用TestConstructor的时候,就调用dispose方法清除。
<span style="font-size:18px;">public class Clean { public static void main(String[] args) { try { TestConstructor tc = new TestConstructor("a.txt"); try{ String s ; int i ; while((s = tc.getLine())!=null){ //自己要进行的字符处理 } }catch(Exception e){ e.printStackTrace(); }finally{ tc.dispose(); } } catch (Exception e) { e.printStackTrace(); } finally{ System.out.println("failed constructed!"); } } }</span>
这种嵌套是要自己去写的,Eclipse不给提示,因为语句已经在一个try块里面了。仔细分析一下,这个嵌套写的很漂亮,因为构造失败的话,我们根本就不需要去关闭TestConstructor,相反,构造成功的话,我们最后记得要清理,这和上面红字的地方是对应的,TestConstructor是读取文件的,我们确实需要再整个生命周期里面打开,而Clean这个类是调用Testconstructor这个类的,当成功构造和使用完读取字符串之后,自然而然就用dispose关闭,只不过我以前写程序的时候没有注意到,finally的处理和try,catch的嵌套可以写得这么美,真让人拍案叫绝。
3)异常匹配
这个问题比较简单,异常抛出后,找到匹配的处理程序后,就认为异常将的到处理,不再继续寻找。
try { } catch (FileNotFoundException e) { System.out.println("can not open!"); } catch(Exception e){ }如果异常与第一个匹配,那就捕获,如果不是,就由Exception捕获,因为Exception会捕获Exception以及其他从它派生的异常。
其他可选方式:
异常处理一个原则是“只有你知道如何处理才去捕获异常”,前面一篇文章已经说过,异常其实是为了将错误发生的地点的代码和处理错误的代码分开,让你专心写前者的代码。
作者自己也曾经犯过错误:
try{ }catch(Exception e){}称为:harmful if swallowed,吞食有害,异常有,因为catch是必须加上的,但是有没有进行处理,就这样被吞了。所以,要么写好错误处理,要么抛给高层。
4)历史与观点
历史就不说了,书上太长了。
说一下一个CLU设计师的观点——我们觉得强迫程序员在不知道该采取什么错事的时候提供处理程序是不现实的。但是我们也知道,放在那里不理也是不现实的,这是我自己的话,所以异常是个好东西。
总结:异常让我们可以集中精力解决问题,就是问题和业务逻辑的代码分开后再集中处理。
摘抄一下书上的异常使用指南:
把当前运行环境能做的事情尽量做完,然后把不同或者相同的异常抛向更高层。
让类库和程序更安全。
作为程序设计不可分割的一部分,要学会使用。
Java编程思想(十一) —— 通过异常处理错误(2)