1.概述
- Java提供异常处理机制来处理异常
- 断言:在测试中,不需要写测试代码,测试完将代码删除。我们使用断言来有选择性的进行测试
- 日志:记录下出现的问题,已备日后分析
2.处理错误
2.1 异常分类
exception.png
- Error:描述了运行时系统的我内部错误等。一般除了通知用户,并尽力使程序安全终止外,无能为力
- Exception:根据unchecked分为unchecked Exception和checked Exception
-
RuntimeExcetion:unchecked Exception。
- NullPointerException
- ClassCastException
- ArrayIndexOutOfBoundsException
-
IOException:checked Exception
- 如试图打开一个不存在的文件
- 试图在文件尾部后面读取数据
-
RuntimeExcetion:unchecked Exception。
- 编译器会检查是否为所有的checked Exception提供了异常处理器。也就是编译期间,编译器会检查你有没有对IOException进行异常处理,但是不会关心你否处理了数组越界异常
2.2 声明checked异常
- 每个包含的checked异常的方法都需要通过
throws
声明具体可能会抛出什么异常,包含多个checked Exception则需要声明多个。 - 什么时候需要抛出异常?
- 调用会抛出checked Exception的方法时
- 方法本身利用
throw
,抛出异常时 - 程序出现运行时异常
- 系统和内部错误
- 什么时候需要声明异常呢?
- 上述前两种情况
- 不需要声明系统错误以及运行时的unchecked Exception,也就是RuntimeException。我们应该尽力去修正这些RuntimeException。
- 总之,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)
- 异常可以被抛出,也可以被捕获,具体采用什么策略要看具体情况
3.捕获异常
3.1 捕获异常
- 捕获异常,必须设置
try/catch
语句块 - 如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。
- 通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递
- 如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常
- 注意:如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如JComponent中的paintComponent),那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围
3. 2 捕获多个异常
- 在一个
try
语句块中,可以捕获多个异常,并对不同类型的异常做出不同的处理
3.3 再次抛出异常和异常链
- 在
catch
子句中可以抛出异常,这样做的目的是改变异常的类型,如:try { } catch(SQLException e) { throw new ServletException(); }
-
另一种更好的办法:将原始异常作为新异常的原因
获取到异常后,使用下面这条语句重新得到异常:try { } catch(SQLException e) { Throwable se = new ServletException(); se.initCause(e); throw se; }
se.getCause()
- 如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常
3.4 finally子句
- 当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。请看一个复杂的例子:
public static int f(int n) { try { int r=2; return r; } finally { if (r=2) return 0; } }
3.5 带资源的try语句
- 带资源的
try语句
:try块退出时或存在一个异常时,会自动调用res.close() - 如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好地处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由addSuppressed方法增加到原来的异常。如果对这些异常感兴趣,可以调用getSuppressed方法,它会得到从close方法抛出并被抑制的异常列表。
3.6 分析堆栈轨迹元素
- 堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置
- 可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息
- 一种更灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组,可以在你的程序中分析这个对象数组
- StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时,还含有能够获得类名和方法名的方法
4.使用异常机制的技巧
- 异常处理不能代替简单的测试
对比下面两段代码:
if(!s.empty)s.pop;
与
try
{
}
catch(EmptyStackException e)
捕获异常花费的执行时间相比于前者是巨大的,因此:只在异常情况下使用异常机制
- 利用异常层次结构
不要只抛出RuntimeException异常。应该寻找更加适当的子类或创建自己的异常类
不要只捕获Thowable异常,否则,会使程序代码更难读、更难维护 - 不要压制异常
- 在检测错误时,“苛刻”要比放任更好
例如,当栈空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackException异常要比在后面抛出一个NullPointerException异常更好 - 不要羞于传递异常
4-5总结起来就是”早抛出,晚捕获“
5.断言
5.1 断言是什么
double y = Math.sqrt(x)
假设我们编写了以上代码,此时我们需要检测x
是否大于0,我们可以这样做:
if (x<0) throw new IllegalArgumentException();
但是这样这段代码会一直保留在程序中,即使测试完毕也不会自动删除。如果程序中含有大量这种检查,程序运行起来会相当慢
- 断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时,这些插入的检测语句将会被自动移走。
5.2 断言的使用
assert 条件;
或
assert 条件:表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串
5.3 什么时候使用断言
java中给出了3种处理系统异常的机制:
- 抛出一个异常
- 日志
- 断言
- 什么时候使用断言
错误1. 断言失败是致命的、不可恢复的错误
- 断言检查只用于开发和测阶段
因此不应该使用断言向程序的其他部分通告发生了可恢复性的