异常的捕获比较简单,只列出需要注意的地方
捕获异常
使用try/catch语句块捕获一个异常。
编译器严格地执行throws说明符,如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。
这个规则也有一个例外:如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围。
捕获多个异常
可以按照下列方式为每个异常类型使用一个单独的catch字句:
要想获得对象的更多信息,可以试着使用e.getMessage()
。得到详细的错误信息(如果有的话),或者使用e.getClass().getName()
。
在Java SE 7中,同一个catch字句中可以捕获多个异常类型。
***注释:***捕获多个异常时,异常变量隐含为final变量。不能在字句中为e赋不同的值。捕获多个异常不仅会让你的代码看起来更简单,还会更高效。生成的字节码只包含一个对应公共catch字句的代码块。
再次抛出异常与异常链
在catch字句中可以抛出一个异常。
这里,ServleException用带有异常信息文本的构造器来构造。不过一种更好的处理方法是将原始异常设置为新异常的“原因”:
当捕获到异常时,就可以使用Throwable e = e.getCause();
重新得到原始异常。
**强烈建议使用这种包装技术。**这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
如果再一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常。
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
在上面这段代码中,有下列3中情况会执行finally字句:
- 代码没有抛出异常。执行标注的1、2、5、6处。
- 抛出一个在catch字句中捕获的异常。执行标注的1、3、4、5、6处的字句。
- 代码抛出一个异常,但不由catch字句捕获。执行标注1、5处的语句。
try语句可以只有finally字句,而没有catch字句。例如:
InputStream in = ...;
try
{
code that might throw exceptions
}
finally
{
in.close();
}
在需要关闭资源时,用这种方式使用finally子句是一种不错的选择。
提示:强烈建议解耦合try/catch和try/finally语句块。这样可以提高代码的清晰度。例如:
InputStream in = ...;
try
{
try
{
code that might throw exceptions
}
finally
{
in.close();
}
}
catch(IOException e)
{
show error message
}
警告:当finally子句包含return语句时,会出现一种想不到的结果。例如:
public static in f(int n)
{
try
{
int r = n * n;
return r;
}
finally
{
if(n == 2) return 0;
}
}
如果调用f(2),那么finally子句使得方法返回0,覆盖了try语句块中原始的返回值4。
有时候,finally子句也会带来麻烦。例如,清理资源时的方法也有可能抛出异常。
InputStream in = ...;
try
{
code that might throw exceptions
}
finally
{
in.close();
}
finally语句块中的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;
}
}
Java SE 7 中关闭资源的处理会容易得多。
带资源的try语句
带资源的try语句(try-with-resources)的最简形式为:
try (Resource res = ...)
{
work with res
}
资源属于一个实现了AutoCloseable接口的类,Java SE 7 为这种代码模式提供了一个很有用的快捷方式。AutoCloseable接口有一个方法:
void close() throws Exception
try块退出时,会自动调用res.close()。
可以指定多个资源。例如:
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());
}
}
不论这个块如何退出,in和out都会关闭。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由addSuppressed方法增加到原来的异常。可以调用getSuppressed方法,它会得到从close方法抛出并被抑制的异常列表。
分析堆栈轨迹元素
**堆栈轨迹(stack trace)**是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。
Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String description = out.toString();
一种更灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组。例如:
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement frame : frames)
analyze frame
StackTraceElement类有能够获得文件名和当前执行的代码行号的方法,同时,还含有能偶获得类名和方法名的方法。toString方法将产生一个格式化的字符串。
静态的Thread.getAllStackTrace方法,它可以产生所有线程的堆栈轨迹。
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Thread t : map.keySet())
{
StackTraceElement[] frames = map.get(t);
analyze frames
}