Java核心技术 卷1-总结-8

处 理 错 误

如何抛出异常

一个名为readData的方法正在读取一个首部具有下列信息的文件:

Content-length:1024

然而,读到733个字符之后文件就结束了。这是一种不正常的情况,希望抛出一个异常。
首先要决定应该抛出什么类型的异常。将上述异常归结为IOException是一种很好的选择。仔细地阅读Java API文档之后会发现:EOFException异常正是我们要抛出的异常。下面是抛出这个异常的语句:

throw new EOFException(); //或者
EOFException e = new EOFException();throw e;

下面将这些代码放在一起:

String readData(Scanner in) throws EOFException {
	while(...) {
		if (!in.hasNext()) // EOF encountered {
			if (n < len) {
				throw new EOFException();
			} 
		}
	}
	return s; 
}

对于一个已经存在的异常类,将其抛出非常容易。在这种情况下:

1. 找到一个合适的异常类。
2. 创建这个类的一个对象。
3. 将对象抛出。

一旦方法抛出了异常,这个方法就不可能返回到调用者。也就是说,不必为返回的默认值或错误代码担忧。

创建异常类

在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,需要创建自己的异常类。需要做的只是定义一个派生于Exception的类,或者派生于Exception子类的类。 例如,定义一个派生于IOException的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类ThrowabletoString方法将会打印出这些详细信息,这在调试中非常有用)。

class FileFormatException extends IOException {
	public FileFormatException() {}
	public FileFormatException(String gripe) { 
	super(gripe);
	}
}

这样就可以抛出自己定义的异常类型了。

String readData(BufferedReader in) throws FileFormatException {
	while(...) {
		if (ch ==-1) { // EOF encountered
			if (n < len) {
				throw new FileFormatException();
			}
		}
	}
	return s;
}

捕获异常

抛出一个异常十分容易。只要将其抛出就不用理睬了。但是,有些代码必须捕获异常。捕获异常需要进行周密的计划。

捕获异常

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

要想捕获一个异常,必须设置try/catch语句块。最简单的try语句块如下所示:

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

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么

1. 程序将跳过try语句块的其余代码。
2. 程序将执行catch子句中的处理器代码。

如果在try语句块中的代码没有抛出任何异常,那么程序将跳过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异常。在这种情况下,将跳出整个while循环,进入catch子句,并生成一个栈轨迹。这样处理异常基本上合乎情理,但是通常,最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去处理。 如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException

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

如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。

哪种方法更好呢?通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。 如果想传递一个异常,就必须在方法的首部添加一个 throws说明符,以便告知调用者这个方法可能会抛出异常。

如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围。

捕获多个异常

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

try {
	code that might throw exceptions
}
catch(FileNotFoundException e) {
	emergency action for missing files 
}
catch (UnknownHostException e) {
	emergency action for unknown hosts 
}
catch (IOException e) {
	emergency action for all other I/O problems
}

异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息,可以试着使用e.getMessage()
得到详细的错误信息(如果有的话),或者使用e.getClass().getName()得到异常对象的实际类型。

再次抛出异常与异常链

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。 如果开发了一个供其他程序员使用的子系统,那么,用于表示子系统故障的异常类型可能会产生多种解释。ServletException就是这样一个异常类型的例子。执行servlet的代码可能不想知道发生错误的细节原因,但希望明确地知道servlet是否有问题。下面给出了捕获异常并将它再次抛出的基本方法:

try {
	access the database 
}
catch (SQLException e) {
	throw new ServletException("database error:" + e.getMessage());
}

这里,ServleException用带有异常信息文本的构造器来构造。

有时你可能只想记录一个异常,再将它重新抛出,而不做任何改变:

try {
	access the database
}
catch (Exception e) {
	logger.log(level,message,e);
	throw e;
}

finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。 如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。Java中的finally子句可以很好的解决这些问题。不管是否有异常被捕获,finally子句中的代码都被执行。 在下面的示例中,程序将在所有情况下都会关闭文件。

InputStream in = new FileInputStream(....);
try {
	//1
	code that might throw exceptions 
	//2
}
catch (IOException e) {
	//3
	show error message 
	//4 
}
finally {
	//5 
	in.close();
}
//6

在上面这段代码中,有下列4种情况会执行finally子句:

  1. 代码没有抛出异常。在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后,继续执行try语句块之后的第一条语句。也就是说,执行标注的1、2、5、6处。
  2. 抛出一个在catch子句中捕获的异常。在这种情况下,程序将执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
  3. 如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。在这里,执行标注1、3、4、5、6处的语句。
  4. 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有语句,直到有异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。在这里,执行标注1、5处的语句。

try 语句可以只有finally子句,而没有 catch子句。 例如,下面这条try 语句:

InputStream in = ...;
try {
	code that might throw exceptions 
} 
finally { 
	in.close();
}

无论在try语句块中是否遇到异常,finally子句中的in.close()语句都会被执行。

当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果 finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。 请看一个复杂的例子:

public static int f(int n) {
	try {
		intr = n * n;
		return r;
	} finally {	
		if (n == 2) {
			return 0;
		}
	}
}

如果调用f(2),那么try语句块的计算结果为r=4,并执行return语句。然而,在方法真正返回前,还要执行finally子句。finally子句将使得方法返回0,这个返回值覆盖了原始的返回值4

有时候,finally子句也会带来麻烦。例如,清理资源的方法也有可能抛出异常。在执行finally语句块,并调用close方法时。close方法本身也有可能抛出IOException异常。当出现这种情况时,原始的异常将会丢失,转而抛出close方法的异常。 这会有问题,因为第一个异常很可能更重要。如果想重新抛出原来的异常,代码会变得极其繁琐。如下所示:

InputStream in =...;
Exception ex = null;
try {
	try {
		code that might throw exceptions 
	}
	catch (Exception e) { 
		ex = e;
		throw e; 
	}
}
finally {	
	try {
		in.close();
	}
	catch (Exception e) {
		if (ex == null) { 
			throw e;
		}
	}
}

带资源的try语句可以很好的解决这个问题。

带资源的try 语句

对于以下代码模式:

open a resource 
try {
	work with the resource }
finally {
	close the resource 
}

假设资源属于一个实现了AutoCloseable接口的类,Java SE7及以上为这种代码模式提供了一个很有用的快捷方式。AutoCloseable接口有一个方法:

void close() throws Exception

带资源的try语句(try-with-resources)的最简形式为:

try (Resource res = ...) {
	work with res
}

try块退出时,会自动调用res.close()。典型的例子如下,读取一个文件中的所有单词:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words")), "UTF-8") {
	while (in.hasNext()) {
	System.out.println(in.next());
	}
}

这个块正常退出时,或者存在一个异常时,都会调用in.close()方法,就好像使用了finally块一样。 还可以指定多个资源。例如:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"),"UTF-8"); PrintWriter out = new Printwriter("out.txt")) {
	while (in.hasNext()) {
		out.println(in.next().toUpperCase());.
	}
}

不论这个块如何退出,inout都会关闭。如果你用常规方式手动编程,就需要两个嵌套的try/finally语句。
如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好地处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。只要需要关闭资源,就要尽可能使用带资源的try 语句。
注:带资源的try语句自身也可以有catch子句和一个finally子句。这些子句会在关闭资源之后执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值