• 异常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

  • 相关阅读:
    动态规划 ------最短路径问题
    回溯算法 ------回溯算法的设计思想和适用条件
    回溯算法 ------ 回溯算法的设计思想及适用条件
    回溯算法 ------回溯算法的几个例子
    纯css实现翻书效果
    从vue源码看props
    js循环中使用async/await踩过的坑
    js实现word转换为html
    从vue源码看Vue.set()和this.$set()
    微信、qq二次分享
  • 原文地址:https://www.cnblogs.com/AlanWilliamWalker/p/9970773.html
Copyright © 2020-2023  润新知