天天看点

Java异常Java异常的分类

Java异常的分类

Java中的所有的异常都是

Throwable

的子类,在大的方面分为

Error

Exception

两大类,

Exception

又可以分为非检查异常(UncheckedException,继承于

RuntimeException

)和检查异常(CheckedException继承于Exception,但不继承于

RuntimeException

),如下图所示:

Java异常Java异常的分类

Error

Error通常用于描述系统级别的错误,当出现系统级别的错误的时候,系统环境已经不健康了,因此Error不需要强制捕获和声明,也不强制处理.常见的Error有

OutOfMemoryError

,

StackOverflowError

,

InternalError

,

UnknownError

等.

平时写代码的时候应该尽量避免一些可避免的Error,例如OutOfMemoryError,在大文件处理或使用缓存的时候容易出现.

非检测异常(UncheckedException)

直接或间接继承于

RuntimeException

以及其本身都属于非检测异常.

该类异常无需显示捕捉和处理,适用于调用的代码不能继续执行,需要立即终止的情况.例如数组越界,调用null对象的实例方法或属性,方法参数的检测等.

出现非检测异常的情况大部分情况是代码写挫了,此时记下日志即可.

常见的RuntimeException

  • NullPointerException

    : 空指针异常,当应用试图在要求使用对象的地方使用了null时,抛出该异常.
  • IllegalArgumentException

    : 方法被传入了非法或不适当的参数.
  • IndexOutOfBoundsException

    : 下标越界异常,数组、字符串或者集合的索引超出了范围。
  • IllegalStateException

    : 非法状态异常(表示在非法或不适当的时间调用了方法)
  • ArithmeticException

    : 算术条件异常.例如:整数除零.
  • SecurityException

    : 安全性异常.
  • ClassCastException

    : 类型强制转换异常.

当对方法的参数进行检查时,参数值为null,此时应该抛出IllegalArgumentException还是NullPointerException?

参见: https://stackoverflow.com/questions/3881/illegalargumentexception-or-nullpointerexception-for-a-null-parameter

个人理解:虽然《Effective Java》和jdk源码更倾向于抛出NullPointerException,但是不够直观,容易造成误解,例如以下一段代码;

//如果这行代码报NullPointerException异常,我们首先会怀疑loginModule为null,而不是userName或者password为null.
loginModule.login(userName,password);
                
其次,

IllegalArgumentException

就是设计用于参数检查的,不应该为了参数null的检查,而引入其它类型的异常.

所以,参数检查时,参数为null时,使用

IllegalArgumentException

会更加合理.

检测异常(CheckedException)

检测异常指的是继承于

Exception

,但不继承于

RuntimeException

的异常.

该类异常需要显示使用catch进行捕捉,当catch到检测异常时,可以选择进一步的恢复或处理其它一些事情.

例如IOException,当catch到IOException以后,可以对io流进行及时的关闭.

常见的检测异常

  • ClassNotFoundException

    :当程序尝试使用字符串的形式加载类时,但是未找到相应的类.
  • FileNotFoundException

    :尝试打开指定路径的文件失败.
  • InterruptedException

    :一个线程在等待、睡眠或其他方式占用时,并且该线程在活动之前或期间被中断.
  • IOException

    :程序进行IO操作失败或被中断.
  • SQLException

    :数据库访问错误.

常见误区

参见: https://www.ibm.com/developerworks/cn/java/j-lo-exception-misdirection/index.html

异常类型不够明确

抛出的异常越明确越好,越明确的异常越能提供更多的信息,抛出的异常最好需要携带message,用于对异常出现的原因进行描述,方便以后更准确的定位问题,例如下面参数的检查:

public String md5(String data) {
    if (data == null) {
        // 这里抛出的异常不够具体,而且抛出的异常不含message.
        throw new RuntimeException();
    }
    ...
}
                

上面的代码抛出的异常不够具体,而且没有message,应该为下面的形式:

public String md5(String data) {
    if (data == null) {
        throw new IllegalArgumentException("data is null.");
    }
    ...
}
                

直接将异常显示在页面或者客户端

实际上任何异常对用户而言没有实际意义,可以在异常中引入错误代码,一旦出现异常,只需要将异常的额代码呈现给用户,或者将错误代码换成更通俗易懂的提示,开发人员也可以根据错误代码去排查问题.

对代码层次结构的污染

例如下面一段代码:

public boolean insert(String name,String pwd)throws SQLException{
	//将用户名和密码插入数据库
  }
                

上面的代码将会污染到上层调用的代码,我们可以改为下面一种形式:

public boolean insert(String name,String pwd){
    try{
        //将用户名和密码插入数据库
    }catch(SQLException e){
        //利用非检测异常封装检测异常,降低层次耦合
        throw new RuntimeException(SQLErrorCode, e);
    }finally{
        //关闭连接,清理资源
    }
}
                

忽略异常

例如下面的异常,仅仅是将异常输出到控制台,没有任何实际意义.而且代码将会继续执行,进而导致其它无关的异常.

public boolean insert(String name,String pwd){
    try{
        //将用户名和密码插入数据库
    }catch(SQLException e){
        //这里直接将异常打印到控制台并没有实际意义
        //这里可以将异常post到服务器,也可以转换为RuntimeException抛出.
        e.printStacktrace();
    }finally{
        //关闭连接,清理资源
    }
}
                

将异常包含在循环语句块中

下面的代码可能会导致catch中的代码块执行多次.

for(int i=0; i<100; i++){
    try{

    }catch(XXXException e){
         //...
    }
}
                

另一种情况与上面的情况类似:A类循环调用B类中的某个方法,该方法块中存在

try-catch

这样的语句块.

利用Exception捕捉所有潜在的异常

一段代码块中可能会抛出几种不同类型的异常,可能为了代码的简洁,会使用基类Exception捕捉所有潜在的异常.这样会丢失原有的异常详细信息;

public void retrieveObjectById(Long id){
    try{
        //…抛出 IOException 的代码调用
        //…抛出 SQLException 的代码调用
    }catch(Exception e){
        //这里利用基类 Exception 捕捉的所有潜在的异常,如果多个层次这样捕捉,会丢失原始异常的有效信息
        throw new RuntimeException(“Exception in retieveObjectById”, e);
    }
}
                

可以改为以下形式:

public void retrieveObjectById(Long id){
    try{
        //…抛出 IOException 的代码调用
        //…抛出 SQLException 的代码调用
    }catch(IOException e){
        //仅仅捕捉 IOException
        throw new RuntimeException(/*指定这里 IOException 对应的错误代码*/code,“Exception in retieveObjectById”, e);
    }catch(SQLException e){
        //仅仅捕捉 SQLException
        throw new RuntimeException(/*指定这里 SQLException 对应的错误代码*/code,“Exception in retieveObjectById”, e);
    }
}