这个 Merlin 发行版中新添加的许多功能(比如异常处理和日志记录功能),并不象其它一些功能一样显著或令人兴奋,但它们很有用,值得我们关注。所有的 Java 开发者应该都熟悉执行异常处理的基本结构:把可能抛出异常的代码放在
try 块中,然后,万一在这个块中确实抛出了异常,则由这个块下面的
catch 子句来处理。在这个 Merlin 发行版中,这个基本结构并没有发生改变。发行版 1.4 中的新功能是如果从
catch 子句重新抛出了一个异常,您可以附加上该异常的初始原因。这真是一个便于调试的高招!并且,如果您想记录下异常发生在何处,您不必手工分析堆栈跟踪信息。现在支持通过程序的方式访问堆栈跟踪数据,还有一个“日志记录 API”(Logging API)用于记录这些数据(或其它任何内容)。
下面是这个月我们要讨论的新功能的列表:
- 链式异常工具
- 以程序的方式访问堆栈跟踪信息
- 日志记录 API
清单 1 中的基本程序包含三个方法,这三个方法都可抛出异常。每个异常情况通过显示一条消息来处理。在第一个例子中,异常被重新抛出以便显示针对该问题的第二条消息。
import java.io.*;
public class Exceptions {
private static void fileAccess() throws IOException {
// Fails because prefix is too short
File f = File.createTempFile("x", "y");
}
private static void divZero() {
System.out.println(1/0);
}
private static void arrayAccess(String array[]) {
System.out.println("First: " + array[0]);
}
public static void main(String args[]) {
try {
try {
fileAccess();
} catch (Exception e) {
System.err.println("Prefix too short");
throw e;
}
} catch (Exception cause) {
System.err.println("Cause: " + cause);
}
try {
divZero();
} catch (Exception e) {
System.err.println("Division by Zero");
e.printStackTrace();
}
try {
arrayAccess(args);
} catch (Exception e) {
System.err.println("No command line args");
}
}
}
|
一个链式异常是这样一种异常,它允许您将一个“成因”异常附加到正在抛出的异常。本质上,您是在创建一条异常菊花链。例如,当抛出(和捕获)您的定制异常时,您可以说导致该异常的原因是一个 I/O 异常。支持链式异常是从
java.lang.Throwable 类开始的。现在,不是只有两个构造函数(其中一个无参数,另一个接受一条详细消息作为参数),而是有四个构造函数:
-
Throwable() -
Throwable(String message) -
Throwable(Throwable cause) -
Throwable(String message, Throwable cause)
当创建自己的异常类型时,您还应该另外添加两个构造函数。那样,您就可以在异常被创建时,很容易地传递产生该异常的原因。即使不改变您的
Exception 子类,您仍然可以将它们链接起来。这个方法只要求您调用自己子类的
initCause(Throwable cause) 方法。
要演示链接,清单 2 应该替换
Exceptions 类中的前两个 try 块。它定义一个定制异常类(同时附加上原因),并抛出这个定制异常类而不是
fileAccess() 异常处理的初识异常:
try {
try {
fileAccess();
} catch (Exception e) {
class TheException extends Exception {
public TheException() {
}
public TheException(String message) {
super(message);
}
public TheException(Throwable throwable) {
super(throwable);
}
public TheException(String message, Throwable throwable) {
super(message, throwable);
}
}
TheException theExc = new TheException("Prefix too short", e);
throw theExc;
}
} catch (Exception cause) {
System.err.println("Cause: " + cause);
System.err.println("OriginalCause: " + cause.getCause());
}
|
现在,我们将通过访问异常的堆栈跟踪信息来增加一点复杂性。如清单 2 中第二个方法调用所示,可以调用
printStackTrace() 来显示通向抛出异常的代码行的调用序列的堆栈转储信息。
printStackTrace() 方法可以接受
PrintStream 或
PrintWriter 作为参数,并且如果不为该方法提供目的地的话,它将把输出发送到
System.err 。
如果您希望以自己的格式来显示堆栈跟踪信息,而不是以缺省的格式来转储,您可以调用
getStackTrace() 方法,该方法将返回一个
StackTraceElement 对象数组。您可以发现每个元素的许多不同的功能:
-
getClassName() -
getFileName() -
getLineNumber() -
getMethodName() -
isNativeMethod()
通过调用每个元素的不同方法,您可以用任何自己喜欢的格式来显示堆栈转储信息。用清单 3 取代
printStackTrace() 调用将会使程序显示每个堆栈元素的文件名、行号和方法名称。
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
System.err.println(elements[i].getFileName() + ":" +
elements[i].getLineNumber() + " ==> " +
elements[i].getMethodName()+'()");
}
|
有一点要注意:数组的第一个元素(而不是最后一个元素)是这个调用跟踪的栈顶。
不想把堆栈跟踪信息发送到
System.err ,您可以使用新的
java.util.logging 包中提供的 Java 平台的日志记录工具。虽然通过 XML 和过滤有许多配置选项可用,但基本结构还是要求获得一个
Logger 对象并使用一般的
log(Level level, String message) 方法对方法进行日志记录,或调用针对特别日志级别的方法(如
fine() )。有七个不同的级别,另加两个指明“全部”或“无”的级别:
-
SEVERE -
WARNING -
INFO -
CONFIG -
FINE -
FINER -
FINEST -
ALL -
NONE
清单 4 向第三个
try 块添加了日志记录功能,只记录堆栈跟踪每部分的方法名称。
System.err.println("No command line args");
Logger logger = Logger.getLogger("net.zukowski.ibm");
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
|
缺省情况下,日志记录消息被发送到控制台。可以通过为
LogManager 附加一个
Handler 将日志记录添加到文件,如清单 5 所示。
try {
LogManager manager = LogManager.getLogManager();
Handler handler = new FileHandler("zuk.log");
manager.addGlobalHandler(handler);
// log it
} catch (IOException logException) {
System.err.println("Logging error");
}
|
当控制台输出格式不是任何一种易于分析的格式时,文件输出存储为 XML 文档。清单 6 显示了这个示例的这种输出。
<?xml version="1.0" standalone="no"?> <!DOCTYPE log SYSTEM "logger.dtd"> <log> <record> <date>2001-10-30T16:24:23</date> <millis>1004563463843</millis> <sequence>0</sequence> <logger>net.zukowski.ibm</logger> <level>WARNING</level> <class>Exceptions</class> <method>main</method> <thread>10</thread> <message>arrayAccess</message> </record> <record> <date>2001-10-30T16:24:24</date> <millis>1004563464015</millis> <sequence>1</sequence> <logger>net.zukowski.ibm</logger> <level>WARNING</level> <class>Exceptions</class> <method>main</method> <thread>10</thread> <message>main</message> </record> </log> |
清单 7 提供了一个完整的示例以供您试验这些新功能。
import java.io.*;
import java.util.logging.*;
public class Exceptions {
private static void fileAccess() throws IOException {
// Fails because prefix is too short
File f = File.createTempFile("x", "y");
}
private static void divZero() {
System.out.println(1/0);
}
private static void arrayAccess(String array[]) {
System.out.println("First: " + array[0]);
}
public static void main(String args[]) {
try {
try {
fileAccess();
} catch (Exception e) {
class TheException extends Exception {
public TheException() {
}
public TheException(String message) {
super(message);
}
public TheException(Throwable throwable) {
super(throwable);
}
public TheException(String message, Throwable throwable) {
super(message, throwable);
}
}
TheException theExc = new TheException("Prefix too short", e);
throw theExc;
}
} catch (Exception cause) {
System.err.println("Cause: " + cause);
System.err.println("OriginalCause: " + cause.getCause());
}
try {
divZero();
} catch (Exception e) {
System.err.println("Division by Zero");
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
System.err.println(elements[i].getFileName() + ":" +
elements[i].getLineNumber() + " ==> " +
elements[i].getMethodName()+'()");
}
}
try {
arrayAccess(args);
} catch (Exception e) {
System.err.println("No command line args");
try {
LogManager manager = LogManager.getLogManager();
Handler handler = new FileHandler("zuk.log");
manager.addGlobalHandler(handler);
Logger logger = Logger.getLogger("net.zukowski.ibm");
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
logger.log(Level.WARNING, elements[i].getMethodName());
}
} catch (IOException logException) {
System.err.println("Logging error");
}
}
}
}
|
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 请参与本文的
讨论论坛。
-
Chained Exception Facility概览描述了异常链接和堆栈跟踪访问功能。
-
Java Logging Overview展示了新的日志记录工具可用的许多其它功能。
- 通过日志记录工具的
javadoc学习如何使用它。
- IBM alphaWorks 的
Logging Toolkit for Java提供了一个优秀的、早于 Merlin 的日志记录工具。
- developerWorks Web 服务专区提供了
LogKit作为“一周组件”的推荐组件(2001 年 8 月)。
- Peter Haggar,IBM 的一位高级软件工程师,在“
Java Exception Handling”上提供了幻灯片演示(developerWorks,1998 年 3 月)。
- 请阅读 John Zukowski 写的
Merlin 技巧的完整论文集。
- 请在
developerWorks Java 技术专区上查找更多的 Java 参考资料。

John Zukowski 通过 JZ Ventures 公司从事战略性 Java 咨询,同时还担任一些 jGuru的由社区推动的 Java 常见问题解答的常驻指导。他最近的著作有 Apress 出版的 Java Collections 和 Definitive Guide to Swing for Java 2 (第 2 版)。可以通过 jaz@zukowski.net与他联系。