异常Throwable

1.有效处理java异常三原则

java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮,易于调试。异常之所以是一种强大的调试手段,在于其回答了以下三个问题:

  • 什么出了错?
  • 在哪里出错?
  • 为什么出错?

有三个原则可以帮助你在调试过程中最大限度的使用好异常:

  • 具体明确
  • 提早抛出
  • 延迟捕获

通过杜撰个人财务管理器类JCheckbook进行讨论,JCheckbook用于记录及追踪诸存取款,票据开具之类的银行账户活动。

1.1具体明确

java定义了一个异常类的层次结构,以Throwable开始,扩展出Error和Exception,而Exception又扩展出RuntimeException。

      图一,java异常层次结构

这四个类是泛化的,并不提供出错信息,虽然实例化这几个类语法上合法(如:new Throwable()),java提供了大量异常子类,如果贴近业务,你也可以定义自己的异常类。

1.1.1捕获异常时尽量明确很重要。java让明确捕获异常变得容易,因为我们可以对同一try块定义多个catch块,从而对每一种异常分别进行恰当处理。

File prefsFile = new File(prefsFilename);
 
try{
    readPreferences(prefsFile);
}
catch (FileNotFoundException e){
    // alert the user that the specified file
    // does not exist
}
catch (EOFException e){
    // alert the user that the end of the file
    // was reached
}
catch (ObjectStreamException e){
     // alert the user that the file is corrupted
}
catch (IOException e){
    // alert the user that some other I/O
    // error occurred
}

 最后,我们注意到JCheckbook并没有在readPreferences()中捕获异常,而是将捕获和处理异常留到用户界面层来做(Controller层),这样就能用对话框或其他方式来通知用户。这被称为“延迟捕获”。

1.2提早抛出

异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法名调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。

java.lang.NullPointerException
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:103)
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

 通过逐步回退跟踪堆栈信息并检查代码,我们可以确定错误原因是向readPreferences()传入了一个空文件名参数。

扫描二维码关注公众号,回复: 4101474 查看本文章
public void readPreferences(String filename)
throws IllegalArgumentException{
    if (filename == null){
         throw new IllegalArgumentException("filename is null");
    }  //if
   //...perform other operations...
   InputStream in = new FileInputStream(filename);
   //...read the preferences file...
}

 通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。

这样我们的堆栈信息就能如实提供:

java.lang.IllegalArgumentException: filename is null
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)
at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)

 通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。

1.3延迟捕获

捕获之后该拿异常怎么办?最不应该的就是什么都不做。适当分离用户界面代码和程序逻辑就可以提高我们代码的可重用性。

 在有条件处理异常之前过早捕获它,通常会导致更严重的错误和其他异常。

  例如:readPreferences()方法在调用FileInputStream构造方法时立即捕获和记录可能抛出的FileNotFoundException

public void readPreferences(String filename){
   //...
   InputStream in = null;
   // DO NOT DO THIS!!!
try{
    in = new FileInputStream(filename);
}
catch (FileNotFoundException e){
    logger.log(e);
}
in.read(...);
//...
}

 分析:上面的代码在完全没有能力从FileNotFoundException中恢复过来的情况下就捕获了它。如果文件无法找到,下面的方法显然无法读取它。

 调试程序时,本能告诉我们要看日志最后面的信息。那将会是NullPointerException,非常让人讨厌的是这个异常非常不具体。错误信息不仅误导我们什么出了错(真正的错误是FileNotFoundException而不是NullPointerException),还误导了错误的出处。真正 的问题出在抛出NullPointerException处的数行之外,这之间有可能存在好几次方法的调用和类的销毁。

 结论【抛异常】:既然readPreferences() 真正应该做的事情不是马上捕获这些异常,把责任交给 readPreferences()的调用者,让它来研究处理配置文件缺失的恰当方法,它有可能会提示用户指定其他文件,或者使用默认值,实在不行的话也 许警告用户并退出程序。把异常处理的责任往调用链的上游传递的办法,就是在方法的throws子句声明异常。

当然,你的程序最终需要捕获异常,否则会意外终止。但这里的技巧是在合适的层面捕获异常,以便你的程序要么可以从异常中有意义的恢复并继续下去,而不是导致更深入的错误;

猜你喜欢

转载自www.cnblogs.com/AlanWilliamWalker/p/9970773.html