【java基础(四十七)】异常(三)捕获异常、异常链

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

捕获异常

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

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

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

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

  • 程序将跳过try语句块的其余代码。
  • 程序将执行catch字句中的处理器代码

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

如果方法中的任何代码抛出了一个在catch字句中没有声明的异常类型,那么这个方法就会立刻退出。

为了演示捕获异常的处理过程,我们看一个读取数据的典型程序代码:

public void read(String filename) {
	try {
		InputStream in = new FileInputStream(filename);
		int b;
		while ((b = in.read()) != -1)
			...
	} catch (IOException exception) {
		exception.printStrackTrace();
	}
}

需要注意的是,try语句中的大多数代码都很容易理解:读取并处理字节,知道遇到文件结束符为止。正如Java API中看到的那样,read方法有可能抛出一个IOException异常。在这种情况下,将跳出整个while循环,进入catch字句,并生成一个栈轨迹。对于一个普通的程序来说,这样处理异常基本上合乎情理。还有其他的选择吗?

通常,最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去操心。如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。

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

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

哪种方法更好呢?通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。

如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。

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

捕获多个异常

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

try {
	code that might throw exceptions
} catch (FileNotFoundExcetpion e) {
	...
} catch (UnknownHostException e) {
	...
} catch (IOException e) {
	...
}

异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息,可以试着使用

e.getMessage()

得到详细的错误信息,或者使用

e.getClass().getName()

得到异常对象的实际类型。

在Java SE7中,同一个catch字句可以捕获多个异常类型。如:

try {
	code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
	...
} catch (IOException e) {
	...
}

只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。

再次抛出异常与异常链

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

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

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

不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的“原因”:

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

当捕获到异常时,就可以使用下面这条语句重新得到原始异常:

Throwable e = se.getCause();

强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

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

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

在Java SE 7之前,这种方法存在一个问题,假设这个代码在以下方法中:

public void updateRecord() throws SQLException

Java编译器查看catch块中的throw语句,然后查看e的类型,会指出这个方法可以抛出任何Exception而不只是SQLException。

现在这个问题已经有所改进。编译器会跟踪到e来自try块。假设这个try块中仅有的已检查异常是SQLException实例,另外,假设e在catch块中未改变,将外围方法声明为throws SQLException就是合法的。

捐赠

若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章