天天看点

Java学习笔记-捕获异常

到目前为止,我们已经知道如何抛出一个异常。这个过程很容易,只要将其抛出就不用再理睬了。当然,有些代码必须要捕获异常。捕获异常需要进行周密的计划。

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会被终止执行,并在控制台上打印出异常信息,其中包括异常的类型以及堆栈的内容。

1.捕获一个异常

想要捕获一个异常,必须设置try/catch语句块:

try{
	code
	more code
	more code
}
catch(ExceptionType e){
	handler for this type
}
           

如果try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么程序将跳过try语句块中的其他代码并且执行catch子句中的处理器代码。

如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。

如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法会立刻退出(希望调用者为这种类型的一场设计了catch子句)。

下面给出一个读取文本的程序代码:

public void read(String filename){
	try{
		InputStream in=new FileInputStream(filename);
		int b;
		while((b=in.read())!=-1){
			process input;
		}
	}
	catch(IOException exception){
		exception.printStackTrace();//生成一个栈轨迹
	}
}
           

通常,最好的选择是什么都不做,而是将一场传递给调用者,如果调用read方法出现了错误去让调用者去操心。如果采用这种处理方式,我们需要声明会出现一个这样的异IOException。

public void read(String filename)throws IOException{
	InputStream in=new FileInputStream(filename);
	int b;
	while((b=in.read())!=-1){
		process input;
	}
}
           

请记住,编译器严格执行throw说明符。如果调用了一个抛出已检查异常的方法,就必须对它进行处理,或者将它继续进行传递。

**通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。

**

2.捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常的类型使用一个单独的catch子句:

try{
	code 
}
catch(ExceptionType e){
	handler for this type
}
catch(ExceptionType e){
	handler for this type
}
           

也可以使用catch合并操作,使得同一个catch子句中可以捕获多个异常类型,对应不同的异常处理动作是一致的情况。

catch(FileNotFoundException | UnknowHostException e){
	handler for this type
}
           

3.异常的包装以及重新抛出

initCause()这个方法就是对异常来进行包装的,目的就是为了出了问题的时候能够追根究底。因为一个项目,越往底层,可能抛出的异常类型会用很多,如果你在上层想要处理这些异常,你就需要挨个的写很多catch语句块来捕捉异常,这样是很麻烦的。如果我们对底层抛出的异常捕获后,抛出一个新的统一的异常,会避免这个问题。但是直接抛出一个新的异常,会让最原始的异常信息丢失,这样不利于排查问题。举个例子,在底层会出现一个A异常,然后在中间代码层捕获A异常,对上层抛出一个B异常。如果在中间代码层不对A进行包装,在上层代码捕捉到B异常后就不知道为什么会导致B异常的发生,但是包装以后我们就可以用getCause()方法获得原始的A异常。这对追查BUG是很有利的。

try{
	access the database
}
catch(SQLException e){
	Throwable se=new ServletException("database Error");
	se.initCause(e);
	throw se;
}
           

4.finally子句

finally里一般拿来做一些善后清理工作。

try块里出现错误的话,会立即跳出try块,找到匹配的错误,执行catch块里的语句。此时,可能在try块里打开的文件没关闭,连接的网络没断开,对这些浪费的内存就不能及时释放回收。

如果有finally块的话,不管有没有出错,都会执行finally块里的内容。有意思的是,即使try里包含continue,break,return,try块结束后,finally块也会执行。

try{
	code
}
catch(ExceptionType e){
	handler for this type
}
finally{
	code
}
           

5.分析堆栈跟踪元素

堆栈跟踪是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。

可以调用Throwable类的printStackTrace方法访问堆栈跟踪的文本描述信息。

Throwable t=new Throwable();
ByteArrayOutputStream out=new ByteArrayOutputStream();
t.printStack(out);
String description=out.toString();
           

一种更为灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组,可以在你的程序中分析这个对象数组:

Throwable t=new Throwable();
StackTraceElement[] frames=t.getStackTrace();
for(StackTraceElement frame:frames){
	Analyze frame
}
           

StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时,还含有能够获得类名和方法名的方法。toString方法将产生一个格式化的字符串,其中包含所获得的信息。

当然Thread类也可以提供类似的功能,在学习线程的时候我们会加入这些内容,我们现在只需知道静态的Thread.getAllStackTrace方法可以产生所有线程的堆栈跟踪。

6.使用异常机制的技巧

最后来谈谈使用异常机制的技巧:

1.异常处理不能代替简单的测试

与执行简单地测试相比,捕捉异常所花费的时间大大地超过了前者,因此使用异常的基本规则是:只在异常的情况下使用异常机制。

2.不要过分地细化异常

很多程序员都喜欢将每一条语句都分装在一个独立的try语句块中:

PrintStream out;
Stack s;
for(i=0;i<100;i++){
	try{	
		n=s.pop();
	}
	catch(EmptyStackException e){
	}
	try{
		out.writeInt(n);
	}
	catch(IOException e){
	}
}
           

这种编程方式会导致代码量的急剧膨胀。首先这段代码希望从栈中弹出100个数值,并输出它们。我们可以将整个任务包装在一个try语句块中,这样当任何一个操作出现问题时,整个任务都可以取消。

PrintStream out;
Stack s;
try{
	for(i=0;i<100;i++){	
		n=s.pop();
		out.writeInt(n);
	}
	catch(EmptyStackException e){
	}
	catch(IOException e){
	}
}
           

这样代码就简洁多了,同时也满足了异常处理机制的其中一个目标,将正常处理与错误处理分开。

3.利用异常层次结构

不要只抛出RuntimeException异常。应该寻找更加适当的子类或者创建自己的异常类。

不要只捕获Throwable异常,否则代码会变得更加难于理解。

异常转换在一些情况也异常重要。