侧边栏

异常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()传入了一个空文件名参数。

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子句声明异常。

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

  • 如果你不能处理异常,不要捕获该异常。
  • 如果要捕获,应该在异常源近的地方捕获它。
  • 不要捕获异常后,什么也不做。
  • 除非你要重新抛出异常,否则把它log起来。
  • 当一个异常被重新包装,然后重新抛出的时候,不要打印statck trace(System.out.println是高代价的,会降低系统吞吐量,在生产环境中别用异常的printStackTrace()方法,会默认打到控制台上)
  • 用自定义的异常类,不要每次都抛出java.lang.Exception。方法的调用者可以通过Throw知道有哪些异常需要处理,所以它是自我描述。
  • 如果编写业务逻辑,对于终端无法修复的错误,系统应该抛出非检查异常(unchecked exception);如果编写一个第三方的包给其他的开发人员,对于不可修复的错误,要用需检查的异常(checked exception)。

2.异常基础

java语言采取了统一异常处理的机制。

Exception是所有异常的父类,任何异常都扩展Exception类。Exception相当于一个错误类型,采用异常的好处就是可以精确定位程序出错的源码位置,获取详细的错误信息。

Java异常处理通过五个关键字来实现,try,catch,throw ,throws, finally。具体的异常处理结构由try….catch….finally块来实现。try块存放可能出现异常的java语句,catch用来捕获发生的异常,并对异常进行处理。Finally块用来清除程序中未释放的资源。不管理try块的代码如何返回,finally块都总是被执行。

2.1Checked异常还是unChecked异常?

Java异常分为两大类:checked 异常和unChecked 异常。所有继承java.lang.Exception 的异常都属于checked异常。所有继承java.lang.RuntimeException的异常都属于unChecked异常。
当一个方法去调用一个可能抛出checked异常的方法,必须通过try…catch块对异常进行捕获进行处理或者重新抛出。
我们看看Connection接口的createStatement()方法的声明。
public Statement createStatement() throws SQLException;

 

SQLException是checked异常。当调用createStatement方法时,java强制调用者必须对SQLException进行捕获处理。

public String getPassword(String userId){   
       try{   
       ……   
              Statement s = con.createStatement();   
              ……   
       Catch(SQLException sqlEx){   
              ……   
   }   
……   
}   

 

或者

public String getPassword(String userId)throws SQLException{   
   Statement s = con.createStatement();   
} 

 

(当然,像Connection,Satement这些资源是需要及时关闭的,这里仅是为了说明checked 异常必须强制调用者进行捕获或继续抛出)

unChecked异常也称为运行时异常,通常RuntimeException都表示“用户”无法恢复的异常,如无法获得数据库连接,不能打开文件等。虽然用户也可以像处理Checked异常一样捕获unChecked异常。但是如果调用者并没有去捕获unChecked异常时,编译器并不会强制你那么做。

举个栗子:

// 将字符串类型转换为整型数值
String str = “123”; int value = Integer.parseInt(str);

 

Integer.parseInt()源码:
public static int parseInt(String s) throws NumberFormatException 

当传入的参数不能转换成相应的整数时,将会抛出NumberFormatException。因为NumberFormatException扩展于RuntimeException,是unChecked异常。所以调用parseInt方法时无需要try…catch。

java推荐人们在应用代码中应该使用checked异常。但是这样会带来很多问题,导致了太多的try...catch代码,同时也导致了很多难以理解的代码产生。

当开发人员必须捕获一个自己无法正确处理的checked异常,通常是重新封装成一个新的异常后抛出,这没有给程序带来好处,反而代码难以理解。

checked异常导致破坏接口方法:

一个接口上的一个方法已被多个类使用,当为这个方法额外添加一个checked异常时,那么所有调用此方法的代码都需要修改。

2.2对于checked与unChecked异常的使用原则:

如果一个异常是致命的,不可恢复的。或者调用者去捕获它没有任何益处,使用unChecked异常。
如果一个异常是可以恢复的,可以被调用者正确处理的,使用checked异常。
在使用unChecked异常时,必须在在方法声明中详细的说明该方法可能会抛出的unChekced异常。由调用者自己去决定是否捕获unChecked异常
当所有调用者必须处理这个异常,可以让调用者进行重试操作;或者该异常相当于该方法的第二个返回值。使用checked异常。
这个异常仅是少数比较高级的调用者才能处理,一般的调用者不能正确的处理。使用unchecked异常。有能力处理的调用者可以进行高级处理,一般调用者干脆就不处理。
这个异常是一个非常严重的错误,如数据库连接错误,文件无法打开等。或者这些异常是与外部环境相关的。不是重试可以解决的。使用unchecked异常。因为这种异常一旦出现,调用者根本无法处理。
如果不能确定时,使用unchecked异常。并详细描述可能会抛出的异常,以让调用者决定是否进行处理。
 

2.3自定义异常

一般情况下尽量不要去设计新的异常类,而是尽量使用java中已经存在的异常类。

不管是新的异常是chekced异常还是unChecked异常。我们都必须考虑异常的嵌套问题。我们在定义一个新的异常类时,必须提供这样一个可以包含嵌套异常的构造函数。并有一个私有成员来保存这个“起因异常”。

 

所以我们需要覆写printStackTrace方法来显示全部的异常栈跟踪。包括嵌套异常的栈跟踪。

Checked异常类继承Exception,unChecked异常类继承RuntimeException。

2.4如何记录异常

作为一个大型应用系统都需要用日志文件来记录系统运行,以便于跟踪和记录系统的运行情况。系统发生的异常理所当然的需要记录在日志系统中。

异常应该在最初产生的位置记录!!

如果捕获到一个异常,但是这个异常可以处理,则无需记录异常(unCheckedException)

捕获到一个未记录过的异常或外部系统异常时,应该记录异常的详细信息

 

在javaEE项目中异常处理从逻辑上分为多层,表现层、业务层、数据库访问层。

一般地,我们需要定义一个unChecked异常,让集成层接口的所有方法都声明抛出这unChecked异常。实现类进行捕获。

public DataAccessException extends RuntimeException{   
 ……   
}   
public interface UserDao{   
 public String getPassword(String userId)throws DataAccessException;   
}   
    
public class UserDaoImpl implements UserDAO{   
public String getPassword(String userId)throws DataAccessException{   
 String sql = “select password from userInfo where userId= ‘”+userId+”’”;   
try{   
    …   
     //JDBC调用   
     s.executeQuery(sql);   
    …   
   }catch(SQLException ex){   
      throw new DataAccessException(“数据库查询失败”+sql,ex);   
   }   
}   
}   

 

对于表示层,一般情况下不需要捕获异常,即使捕获异常也不要将原信息展现给用户,要友好展示。

 

可以对表示层进行捕获,利用springmvc进行统一异常处理,简化代码,减少复用性,调高代码质量。

 

 

 

 

参考文章链接:

http://klyuan.iteye.com/blog/72170

http://blog.csdn.net/luqin1988/article/details/7970792

http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html  对应的译文,不怎么准确:http://blog.sciencenet.cn/blog-252888-761119.html

http://www.oschina.net/question/92866_15634 

http://www.importnew.com/1701.html

http://sunflowers.iteye.com/blog/767175

 

posted @ 2018-11-16 18:08  泡代码的小二黑  阅读(379)  评论(0编辑  收藏  举报
script src="https://files.cnblogs.com/files/fenggwsx/clipboard.min.js"/script script src="https://files.cnblogs.com/files/fenggwsx/cp.js"/script