在Java中,当可能发生受检异常时,必须处理它。对于可能引发受检异常的方法,有两种选择:在方法内处理异常,或是告诉方法的调用者来处理。

假定一个方法返回从磁盘读入的字符串。从磁盘读入时可能会发生错误。这件事会产生一个 IOException 异常。因为 IOException 是受检异常,所以它必须被处理。可以在方法体内处理异常。但有时,程序员不能肯定异常发生时怎么做对方法的调用者是最好的。是应该中止执行,还是进行其他的处理更有意义?当不能肯定要采取哪个动作时,可以让方法的调用者来处理异常。只要异常能在某个地方被处理了,就不需要在方法内来处理。

一个方法可能引起受检异常但又不处理它,就必须在方法头声明这件事。例如,如果方法readString抛出一个IOException异常但不处理它,则它的方法头应该是如下这样的:

public StringreadString(. . .) throws IOException

throws子句(throws clause)表明方法readString不用去处理执行期间可能发生的IOException类型的异常。但如果另一个方法调用readString方法,则那个方法必须处理这个异常。调用方法可以自己处理IOException,也可以在它的方法头中包含一个throws子句告诉它的调用者来处理异常。最终,每个抛出的受检异常都应该在程序的某个地方被处理。

可以在throws子句中列出多个用逗号分开的受检异常。子句的语法如下:

throws 异常列表

列在异常列表中的异常名用逗号分隔。它们的次序不重要。

总结:如果方法可能抛出一个受检异常,则或者在方法头写throws子句声明它,或者在方法内处理它。不这样做的话会导致语法错误。

如果方法可能抛出未检异常,则可以在throws子句中声明它,或是处理它,但也可以什么都不做。

如果方法抛出一个异常但没有处理它,则方法的执行将结束。例如,如果前一个方法readString抛出一个IOException,则它的执行立即结束。但程序继续执行,异常传给readString的调用者。

程序设计提示:当定义一个可能抛出受检异常的方法时,如果不能提供对异常的合理反应,则要在方法头写一个throws子句将异常传给方法的调用者。避免在throws子句中使用Exception,因为这样做,不会给其他程序员提供关于调用方法的任何有用信息。而是要尽量指明异常。

要处理异常,必须先标出可能引起异常的Java语句。还必须决定要找哪个异常。方法的文档及throws子句会告诉我们可能发生什么受检异常。这就是我们要处理的异常。

处理异常的代码含有两段。第一段try块(try block)含有可能抛出异常的语句。第二段含有一个或多个catch块。每个catch块(catch block)含有处理或捕获(catch)某种类型异常的代码。所以,因为调用方法readString而处理IOException的代码可能有如下的形式:

try
{
< 可能含有的其他代码 >
anObject.readString(.. .); // Might throw an IOException
< 更多其他的代码 >
}
catch (IOException e)
{
< 响应异常的代码,可能含有下面这行: >
System.out.println(e.getMessage());
}

try块中的语句的运行,与没有这个块时是一样的。如果没发生异常,则try块全部执行,然后执行catch块后的语句。但如果在try块内发生了IOException,则执行立即转到catch块。现在已经捕获了异常。

catch块的语法类似于一个方法定义。标识符e称为catch块参数(catch block parameer);它表示catch块将处理的IOException的对象。虽然catch块不是方法定义,但在try块内抛出一个异常,类似于调用一个catch块,其中参数e表示一个实际的异常。

作为一个对象,每个异常都有存取方法getMessage,它返回抛出异常时创建的描述字符串。通过显示这个字符串,可以告诉程序员所发生异常的性质。

catch块执行完毕,执行它后面的语句。但如果问题是严重的,最好的反应是中断程序吗?catch块可以调用exit方法来中止程序,如下所示:

System.exit(0);

赋给System.exit的参数0,表示程序的正常结束。虽然程序遇到了一个严重问题,但程序设计者有意中断程序,这在操作系统看来是正确的。

总结:如果没有处理受检异常,或在throws子句中声明它,编译程序会提示问题。有些异常可以在方法的定义中来处理,而有些可以在它的throws子句中声明。一般地,不处理或声明运行时(未检)异常,因为它们表示程序的一个错误。抛出这样的异常时会中断程序的执行。

其参数是C类型的catch块,可以捕获类C或C的任何后代类的异常。

单个 try 块中的语句,可能会抛出不同类型异常中的任意一个。要捕获这些异常,可以在 try 块后写多个 catch 块。当抛出一个异常时, catch 块出现的次序很重要。执行进入到其参数与异常的类型相匹配的第一个 catch 块——按照出现的次序。这些 catch 块的次序非常重要。例如,下列 catch 块次序不好,因为用于 FileNotFoundException 的 catch 块永远不会执行:

catch (IOExceptione)
{
……
}
catch (FileNotFoundExceptione)
{
……
}

按照这个次序,任何I/O异常都将被第一个catch块所捕获。因为FileNotFoundException派生于IOException,所以FileNotFoundException异常是IOException异常的一种,将与第一个catch块的参数相匹配。幸运的是,编译程序可能会对这个次序给出警告信息。

正确的次序是,将多个具体异常放在其祖先类的前面,如下所示:

catch (FileNotFoundExceptione)
{
……
}
catch (IOException e) //Handle all other IOExceptions
{
……
}

程序设计提示:因为受检异常和运行时异常的类都以Exception为祖先,故避免在catch块中使用Exception。而是,尽可能地捕获具体的异常,且先捕获最具体的。

语法:try-catch块的语法如下:

try
{
}
catch (exceptionType e)
{
System.out.println(e.getMessage());
}

< 其他的catch块>

程序设计提示:可能的话避免嵌套的try-catch块。

虽然在try块或catch块中再嵌套try-catch块是合法的,但应该尽可能地避免这样做。先看看能不能用不同的逻辑来组织代码以避免嵌套。如果不行,将内层块移到在外层块中调用的新方法中。

如果必须嵌套try-catch块,则可遵循以下指南。当catch块出现在另一个catch块中时,它们必须使用不同的标识符表示各自的参数。如果计划在try块内嵌套try-catch块,若外层catch块处理了相关的异常,则可以忽略内层的catch块。这种情形中,内层try块抛出的异常被外层try块捕获。

在方法内,当你不能使用合理的方式来解决不正常或意外事件的情形下,就需要抛出异常。方法执行 throw 语句则抛出一个异常。一般的形式是

throw exception_object;

不是使用一条单独的语句来创建异常对象,通常是在throw语句中创建对象,如下面这个例子:

throw newIOException();

这个语句创建类IOException的一个新对象并抛出它。与应该尽可能地捕获具体异常一样,抛出异常也应该尽可能地具体化。

可以调用异常类的默认构造方法,也能提供带字符串参数的构造方法。得到的对象的数据域中将含有该字符串,且在处理异常的catch块中可以使用这个对象和这个字符串。然后catch块可使用异常的方法getMessage来获取这个字符串,默认构造方法为这个字符串提供的是默认值。

语法:throw语句有下列语法:

throw exception_object;

其中exception_object是异常类的一个实例,一般地通过调用类的两个构造方法之一来创建:

new class_name()

new class_name(message)

通过异常的方法getMessage,在捕获异常的代码段中,可使用默认构造方法提供的字符串或字符串message。