Java异常的分类
Java中的所有的异常都是
Throwable
的子类,在大的方面分为
Error
和
Exception
两大类,
Exception
又可以分为非检查异常(UncheckedException,继承于
RuntimeException
)和检查异常(CheckedException继承于Exception,但不继承于
RuntimeException
),如下图所示:
Error
Error通常用于描述系统级别的错误,当出现系统级别的错误的时候,系统环境已经不健康了,因此Error不需要强制捕获和声明,也不强制处理.常见的Error有
OutOfMemoryError
,
StackOverflowError
,
InternalError
,
UnknownError
等.
平时写代码的时候应该尽量避免一些可避免的Error,例如OutOfMemoryError,在大文件处理或使用缓存的时候容易出现.
非检测异常(UncheckedException)
直接或间接继承于
RuntimeException
以及其本身都属于非检测异常.
该类异常无需显示捕捉和处理,适用于调用的代码不能继续执行,需要立即终止的情况.例如数组越界,调用null对象的实例方法或属性,方法参数的检测等.
出现非检测异常的情况大部分情况是代码写挫了,此时记下日志即可.
常见的RuntimeException
-
: 空指针异常,当应用试图在要求使用对象的地方使用了null时,抛出该异常.NullPointerException
-
: 方法被传入了非法或不适当的参数.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
-
:程序进行IO操作失败或被中断.IOException
-
:数据库访问错误.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);
}
}