有些时候当程序出错时,由于程序出错或外部一些环境的改变时,可能会造成用户数据的丢失,用户可能就不会再使用这个软件了,那么当出错时至少应该做到下面的几个
1.向用户报告错误
2.保留所有的工作结果
3.允许用户以妥善的方式退出程序
一.处理错误
异常处理的任务就是将控制器从错误产生的地方转移到能够处理这种情况的错误处理器
1)输入错误
2)设备错误
3)物理限制
4)代码错误
1.异常分类
再Java中,异常是派生于Throwable的一个实例,当Java中的异常不满足于我们时,我们可以创建自己的异常
所有的异常都是由Throwable继承而来的,下一层就分为Error和Exception
Error类层次描述Java运行时系统的内部错误和资源和资源耗近的问题,应用程序不应该抛出这种问题
Exception又分为RuntiomeException和另一个分支处理异常,由于程序错误的是属于RuntimeException,而程序本身没有错误的,像IO这种错误是属于其他异常的
RuntimeException包含下面几种异常:
1)错误的类型转换
2)数组访问越界
3)访问null
不是RuntimeException的有
1)视图打开文件的尾部后面来读取数据
2)视图打开一个不存在的文件
如果程序出现一个RuntimeException异常,那么就一定是你的问题
比如应该通过检查数组下标是不是越界来避免ArrayIndexOutOfBoundsException异常
Java语言规范将派生于Error和RuntimeException类的所有异常称为 非受查异常 ,将所有其他的异常称为 受查异常
编译器将核查是不是受查异常来提供异常处理器
2.声明受查异常
如果遇到无法处理的情况,那么Java方法可以抛出一个异常,一个方法不仅需要告诉编译器将要返回什么值,还可以告诉编译器可能发生什么错误
方法应该在首部声明所有可能抛出的异常,这样可以从首部反映出这个方法可能抛出哪一类受检查异常
如 public FileInputStream(String name) throws FileNotFoundException
这表示这个构造器可能会出现一个File...错误,如果真的出现这种情况,那么构造器不会初始化一个新的FileInputStream对象,而是抛出这个异常,抛出异常后运行时系统就会开始搜索异常处理器,以便知道应该如果处理这个异常
自己在编写方法的时候要注意下面四种情况来决定是不是需要抛出异常也就是使用throws子句声明
1)调用一个抛出受查异常的方法
2)程序运行时发现错误并利用throw语句来抛出一个受查异常
3)程序发生错误
4)Java虚拟机和运行时库出现的内部错误
如果出现起前两种异常,那么就必须告诉调用这个方法的程序员有可能抛出异常,因为任何一个抛出异常的方法都可能是一个死亡陷阱,如果没有处理器来处理捕获这个异常,那么当前执行的线程就会结束
对于那些被别人使用的受查异常,那么就需要根据规范在方法的首部声明这个方法可能抛出的异常
不需要声明Java内部的错误也即使从Error继承来的错误,也不应该声明从RuntimeException继承的非受检查的异常(因为这些错误是受我们控制的,我们应该注意去完善自己的代码,而不是去注意它)
一个方法应该声明所有可能抛出的受查异常,而非受查异常要么不可控制,要么就应该避免发生,如果方法没有声明所有可能发生的受检查异常,编译器就会发出一个错误
当然我们也可以来捕获异常,这样会使得异常不会被抛出方法之外去,也不需要throws规范,下面会讨论如何决定一个异常是被捕获还是被抛出让其他处理器来进行处理
注意:如果在子类种覆盖了超类中的方法,那么子类声明中的受查异常不能比超类方法中声明的异常更加通用(也就是说,子类的方法可以抛出更特定的异常或是不抛出异常),如果超类没有抛出异常,那么子类也不能抛出异常
如果类中的一个方法声明将会抛出一个异常,而这个异常是某个特定的类的实例时,那么这个方法就会抛出这个类的异常,或者是这个类的任意一个子类的异常
3.如何抛出异常
1)找到一个合适的异常类
2)创建这个类的一个对象
3)将对象抛出
一旦抛出异常,那么这个方法就不能返回调用者了,也就是说不必为返回的错误代码来担心
String readDate(String in) throws EOFException {
fi(n<len) throw new EOFException();
4.创建异常
定义一个派生于Exception的类,或派生于Exception子类的类
定义的类应该包含有两个构造器,一个是默认的构造器,另一个是带有详细描述信息的构造器
class FileFormatException extends IOException {
public FileFormatException(){}
public FileFormatException(Stirng gripe) {
super(gripe);
}
}
现在就可以抛出自己定制的异常了,和上面的抛出异常一样的代码
二捕获异常
对于一些异常来说,我们必须去捕捉它
1.捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止,并在控制台打印出异常信息,其中包含异常的类型和堆栈的内容
要捕获一个异常,那么就需要设置try/catch语句
try{
}catch(Exception e) {
}
如果try语句中的任何代码抛出了一个在catch子句中说明的任何一个异常类,那么
1)程序将跳过try语句里面的其他代码
2)执行catch子句中的处理器代码
如果try子句中没有抛出任何异常,那么程序将会跳过catch子句
如果方法中的任何代码抛出了一个在catch子句中没有声明异常类型,那么这个方法就会立刻退出(调用者应该为这种异常设计catch子句)
public void read(String s) {
try{
}catch(IOException e) {
e.printStackTrace();
}
}
但是最好选择什么都不做,把异常传递给调用者,如果read这个方法出现了错误,那么就让read方法的调用者去操心,如果使用这种处理方式,那么就需要声明这个方法可能会抛出一个IOException
public void read(String filename) throws IOException{
}
编译器会严格地执行throws说明符,如果调用了一个抛出受查的方法,那么就必须对它进行处理,或是继续传递,如果想传递一个异常,那么就必须在方法的首部添加一个throws说明符,以便告诉调用者可能会抛出异常
经常我们式通过阅读Java的api文档,以便知道方法可能会抛出的哪种异常,再决定是由自己来处理,还是添加到throws列表,对后面的情况,将异常交给能够胜任的处理器进行处理要比压制它的处理更好
例外:如果我们编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中的每一个受查异常,不允许子类throws说明符中出现超过超类方法所列出的异常类的范围
2.捕获多个异常
一个try语句可以捕获多个异常,并对不同类型的异常做出不同的处理
try{
}catch(){
}catch(){
}catch(){
}
异常对象可能包含与异常本身有关的信息,可以用e.getMessage()得到详细的信息,或使用e.getClass().getName()得到异常的实际类型
如果某个动作之间的异常动作是一样的话,那么就可以进行合并
catch(FileNotFoundException|UnknownHostException e){
}
3.再次抛出异常和异常链
再子句中也可以抛出一个异常,这样做的目的是改变异常的类型,因为我们有时可能不想知道发生错误的细节原因,而是希望明确地知道它是不是有问题,如下面:
try{
}catch(SQLException e) {
throw new SerlvelException("ddd" e.getMessage());
}
还有一种更好的方法,就是将原始的异常设置为新异常的“原因”
try{
}catch(SQLException e) {
Throwable se = new ServletException("database err" );
se.initCause(e);
throws se;
}
当捕获到异常时,可以使用下面的语句来重新得到原始的异常
Throwable e = se.getCause();
后面那种可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节
注意:如果在一个方法中发生了一个受查异常而不允许抛出它,那么这种包装技术就有用了,我们可以捕获这个受查异常,然后将它包装为运行时的异常
4.finally子句
不管是不是有异常被捕获,finally字间距中的代码都会被执行,它是用来处理某个方法使用时利用到的某些资源
当需要关闭资源的时候,finally子句是一个不错的选择
注意:我们应该解耦try/catch和try/finally子句如
try{
try{
}finally{
in.close()
}
}catch(IOException e) {
}
这样最外面的try语句就只有一个职责,就是确保报告出现的错误,也会报告finally中出现的错误,内存的try语句就是来确保关闭输入流
注意:当finally子句中包含return语句的时候,那么将会出现一种想不到的结果,假如利用return语句在try语句中退出,在方法返回之前,finally子句里面的内容将会被执行,如果finally子句中也有一个return语句,那么这个返回值将会覆盖原始的返回值
有时候,finally子句也会带有麻烦,如清除资源的方法也可能会抛出异常,假如希望确保在流处理代码中遇到异常时将流关闭,如果想要对此进行处理,那么代码会变得极其繁杂,下面会来介绍带资源的try语句来解决这个问题(在Java7后面加入的)
5.带有资源的try语句
假设资源属于一个实现了AutoCloseable接口的类,JavaSE7为这种代码模式提供了一个很有用的快捷方式,AutoCloseable接口有一个方法 void close throws Exception
带有资源的try语句的最简的形式为
try(Resource res = ...) {
work with res;
}
当try退出时,会自动调用res.close(),下面是一个例子
try(Scanner in = new Scanner(new FileInputStream("user/share/dict/words")),"UTF-8"){
while(in.hasNext()){
System.out.println(in.next());
}
}
这个块正常退出或是存在一个异常时,都会调用in.close()方法,就好像使用了一个finally块一样
从此来看,如果只是需要关闭资源,就要尽可能使用带有资源的try语句
6.分析堆栈轨迹元素
堆栈轨迹是一个方法调用过程的列表,它包含了程序执行中方法调用的特定位置
1)可以使用Throwable类的printStack方法访问堆栈轨迹的文本描述信息
Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String des = out.toString();
2)更灵活的是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组,可以在你的程序里面分析这个对象数组
Throwable t = new Throwable();
StackTraceElement frames = t.getStackTrace();
for(StackTraceElement frame:frames) {
}
StackTraceElement类含有能够获得文件名和当前执行的代码行数的方法
三.使用异常机制的技巧
1)异常处理不能代替简单的测试,只有在异常的情况下面才去使用异常机制,因为与使用简单的测试相比,捕获异常所花费的时间大大超过使用简单的测试
2)不要过分细化异常
我们应该将整个任务包装在一个try语句中,这样当有一个操作出现错误的时候,整个任务都可以取消
3)利用异常层次结构
不要只抛出RuntimeException异常,而是应该寻找更加合适的子类或是创建自己的异常类,将一种异常转换为另外一种更加合适的异常时不要犹豫,要考虑受查异常和非受查异常的区别,以受查异常本来就很多了,不要为逻辑错误来抛出异常
4)不要压制异常
5)在检测错误的时候,“苛刻”要比放任更好
在检测到错误的时候,如用无效的参数调用一个方法的时候,返回一个虚拟的值好还是抛出一个异常好,如:当栈空时,Stack.pop()是要返回一个null好还是抛出一个异常好,我们认为:在出错的地方抛出一个EmptyStackException异常比要在后面抛出一个NullPointerException异常更加好
6)不要羞于传递异常
如果调用一个抛出异常的方法,这些方法会本能地捕获这些可能产生的异常,但是其实传递异常要比捕获这些异常更好
四.使用断言
1.断言的概念
断言机制允许在测试期间插入一些检查语句,当代码发布时,这些插入的检测语句会被自动移走
Java语言引入了关键字assert,这个关键字有两种形式:
1)assert 条件;
2)assert :条件表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssetionError异常,,第二种,表达式将会传入AssertionError的构造器,并转换为一个消息字符串,表达式的目标是产生一个消息字符
assert x>=0;//断言x是一个非负值
assert x>=0:x;将x的实际的值传递给AssertionError对象,从而可以在后面现实出来
2.启用断言和禁用断言
1)在默认的情况下,断言被禁止,可以在运行程序时用-enableassertions或是-ea选项来启用
Java -ea MyApp
在启用断言和禁用断言时不必重新编译程序,启用或是禁用断言是类加载器的功能,当断言被禁用的时候,类加载器将跳过断言的代码,因此不会降低程序运行的速度
2)可以使用-da禁用某个特定的类和包的断言
3)然后那些没有类加载器的”系统类“来说,上面的代码不能使用,需要使用-esa启动断言
3.使用断言完成参数检查
在Java语言中,给出了3种处理系统错误的机制
1)抛出一个异常
2)日志
3)使用断言
什么时候应该使用断言尼?
断言检查只是用于开发和测试的阶段
在使用断言的时候应该看某需要是不是有前置条件