异常分类
在Java程序设计语言中,所有异常对象都是派生于Throwable
类的一个类实例。
继承关系图如下:
可以看到Throwable异常被分为两大类:
Error
:表示Java运行时系统的内部错误和资源耗尽错误。Exception
:程序运行的异常,一般我们能操作的都是这种错误。
RuntimeException
一般是编程错误导致的。
派生于RuntimeException
的异常包括以下问题:
- 错误的强制转换
- 数组访问越界
- 访问null指针
IOException
的异常包括以下问题:
- 试图超越文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
如果出现
RuntimeException
,那么就一定是你的问题。
所以这句话有一定的根据(当然错误也可能出现在你引用的库中)
Java语言规范将派生于Error
类或RuntimeException
类的所有异常称为非检查型(unchecked)
异常,所有其他的异常称为检查型(check)
异常。
抛出异常
可以通过throw
关键字来抛出异常。
Java在库中定义了很多错误类型,我们可以通过查阅文档来获得相应的错误类型。(当然我们也可以定义自己的错误类型)
示例如下:
1 | public void method1(){ |
创建异常类
我们可以通过继承Exception
类或者其子类来创建单独的异常类。
基本示例如下:
1 | public class MyException extends Exception{ |
Throwable
类中定义了很多成员,其中主要有message:String
和cause:Throwable
。而Exception
也根据其提供了4个构造方法。
- 无参构造器
- 只含
message
的构造器 - 只含
cause
的构造器 - 包含
message
和cause
的构造器
所以我们也可以这样定义
1 | package test.mw.ExceptionTest; |
捕获异常
如果发生了某个异常,但没有任何地方捕获这个异常,程序就会停止,并且会在控制面板上打印一个消息:包括这个异常的信息和调用堆栈。
捕获异常
可以通过
1 | try{ |
来捕获异常,其含义如下:
- 如果normal code发生错误,则错误后的代码都不会被执行。
- 将错误传递到catch块中进行处理
- finally中的代码无论如何都会执行(normal code发生错误或不发生错误都会执行)
如果normal code中可能发生的错误不只一个。我们可以通过以下两种方式来进行处理:
- 添加多个catch块来分别处理
- 使用
catch(Exception1 | Exception2 | Exception3 e)
的格式来捕获多个错误。 - 使用
catch(Exception e)
来通过多态来捕获所有可能的异常。(这里即使发生错误,也会在控制台打印出具体的类名,而不是Exception
)。
再次抛出异常
一般来说,我们可以再次抛出异常。而在最外围来捕获所有的异常。其有两种形式:
在
catch
中捕获到后再次抛出。1
2
3
4
5try{
// code
}catch(Exception e){
throw e;
}直接在方法上抛出错误。
1
2
3public void String save() throws Exception{
// code
}
try…with-Resource语句
有时我们需要在try语句中打开一些资源,并且最后必须关闭,我们通常会使用finally
语句来实现。如:
1 | try{ |
在Java7之后,可以用try...with-Resource
语句来关闭资源,前提是这个资源实现了AutoCloseable
接口。格式如下:
1 | try(Resource res = ...){ |
在try语句退出之后,会自动调用res.close()
。
也可以指定多个资源:
1 | try(Resource res1 = new Resource(); |
使用异常的技巧
- 异常处理不能代替简单的判断:即能用
if
判断是否出现异常的,就不直接用异常。 - 不要过分的细化异常。
- 不要只捕获
Exception
或者Throwable
,因为这对代码阅读并不友好,无法直接判断可能出现的错误。 - 不要对异常进行静默处理。即
catch
必须做点什么,而不是仅捕获,什么都不做。 - 并不一定要捕获所有异常,有时候抛出到外层是更好的选择(可以在外层做统一处理)。
断言
有时候我们需要在测试期间加入一些判断,如果条件不成立,则退出程序。但是在正式版本中,将会删除这部分代码,这是很麻烦的。
所以引入了断言机制,其格式如下:
assert conditon;
:如果condition为false,则退出程序,并抛出AssertionError
。assert condition : expresssion
:如果condition为false,则退出程序,并抛出AssertionError
,并且信息为expression。
启用断言
断言默认是被禁用的。。
启用断言需要加上vm关键字:-ea
或者-enableassertions
。
也可以使用-desableassertions
或-da
来禁止断言。
idea或eclipse中可以在环境参数中配置。
启用或禁用断言是类加载器的功能。断言被禁用时,类加载器会去除断言代码,因此不会降低程序运行时的代码。
日志
在Java程序中,我们经常需要打印中间信息。
我们通常使用System.out.print
来进行打印。
但是其具有很多缺点。相较于直接输出,日志API有以下 优点:
- 日志分级。并且可以分级打印特定日志级别下的信息。
- 可以很简单的禁止日志记录。
- 可以将日志信息打印到控制台或保存在文件中。
- 可以对所有日志进行过滤。
- 可以单独的格式化日志信息。
- 可以单独的配置日志设置。
Java从1.5开始,在java.util
下增加了Logger
类。但是这个类已经很少有人使用。使用Log4j
的更多,所以这里不详细介绍。