第7章 异常、断言和曰志
7.1 处理错误
发生错误应该
- 回到一种安全状态,并能够让用户执行一些其他的命令;
- 或者允许用户保存所有操作的结果,并以妥善的方式终止程序。
异常处理任务
- 异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
7.1.1 异常分类
异常分类

- 在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例。
- Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。
- Exception 层次结构分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。
- 划分两个分支的规则是: 由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于其他异常。
派生于RuntimeException 的异常
- 错误的类型转换。
- 数组访问越界。
- 访问 null 指针。
不是派生于 RuntimeException 的异常
- 试图在文件尾部后面读取数据。
- 试图打开一个不存在的文件。
- 试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在。
一条原则
- “如果出现 RuntimeException 异常,那么就一定是你的问题” 是一条相当有道理的规则。
受查异常/非受查异常
- Java 语 言 规 范 将 派 生 于 Error 类 或 RuntimeException 类的所有异常称为非受查 (unchecked) 异常, 所有其他的异常称为受查 (checked) 异常。
7.1.2 声明受查异常
方法和异常
- 一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
- 方法应该在其首部声明所有可能抛出的异常。
应该抛出异常情况
- 调用一个抛出受査异常的方法,例如,FilelnputStream 构造器。
- 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常。
- 程序出现错误, 例如,a[-l]=0 会抛出一个 ArraylndexOutOffloundsException 这样的非受查异常。
- Java 虚拟机和运行时库出现的内部错误。
异常的使用
class MyAnimation {
...
public Image loadlmage(String s) throws FileNotFoundException, EOFException {
...
}
}
- 对于那些可能被他人使用的 Java 方法,应该根据异常规范 (exception specification),在方法的首部声明这个方法可能抛出的异常。
- 如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
- 不需要声明 Java 的内部错误, 即从 Error 继承的错误。任何程序代码都具有抛出那些异常的潜能, 而我们对其没有任何控制能力。
- 也不应该声明从 RuntimeException 继承的那些非受查异常 。
throws关键字
- 用于声明受查异常。
总结
- 总之,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制 (Error),要么就应该避免发生 (RuntimeException )。
- 如果方法没有声明所有可能发生的受查异常,\编译器就会发出一个错误消息。
可以抛出异常的子类
- 如果类中的一个方法声明将会抛出一个异常, 而这个异常是某个特定类的实例时,则这个方法就有可能抛出一个这个类的异常, 或者这个类的任意一个子类的异常。
7.1.3 如何抛出异常
抛出对象情况
- 找到一个合适的异常类。
- 创建这个类的一个对象。
- 将对象抛出 (使用 throw 关键字)。
7.1.4 创建异常类
需要
- 我们需要做的只是定义一个派生于Exception 的类,或者派生于 Exception 子类的类。
Throwable类
- Throwable();
- Throwable (String message)
- String getMessage()
7.2 捕获异常
7.2.1 捕获异常
try/catch块
try {
code
more code
more code
}catch (ExceptionType e) {
handler for this type
}
- 如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类,那么程序将跳过 try语句块的其余代码。程序将执行 catch 子句中的处理器代码。
- 如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句。
- 如果方法中的任何代码拋出了一个在 catch 子句中没有声明的异常类型,那么这个方法就会立刻退出(希望调用者为这种类型的异常设tKT catch 子句)。
捕获还是抛出异常
- 编译器严格地执行 throws 说明符。如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。
- 通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理
的异常继续进行传递。
注意异常在继承中的使用
- 如果编写一个覆盖超类的方法,而这个方法又没有抛出异常 ( \如 JComponent 中的 paintComponent ),那么这个方法就必须捕获方法代码中出现的每一个受查异常。
- 不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。(异常子类可行)
7.2.2 捕获多个异常
try {
code that might throw exceptions
}catch (FileNotFoundException e) {
emergencyactionfor missingfiles
}catch (UnknownHostException e) {
emergency actionfor unknown hosts
}catch (IOException e) {
emergencyactionfor all other I/O problems
}
//on java se 7
try {
code that might throw exceptions
}catch (FileNotFoundException | UnknownHostException e) {
emergency action for missing files and unknown hosts
}catch (IOException e) {
emergency action for all other I/O problems
}
捕获多个异常
- 在一个 try 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
- 在 Java SE 7中,同一个 catch 子句中可以捕获多个异常类型。
- 捕获多个异常时,异常变量隐含为 final 变量。(赋值后不知确切类型)
7.2.3 再次抛出异常与异常链
catch异常
- 在 catch 子句中可以抛出一个异常,这样做的目的是改变异常的类型。
新异常的“ 原因”
- 可以有一种更好的处理方法,并且将原始异常设置为新异常的“ 原因”:
try{
access the database
}catch (SQLException e) {
Throwable se = new ServletException ("database error");
se.initCause(e);
throw se;
}
- 重新得到原始异常:Throwable e = se.getCause() ;
强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常。
catch异常和throws异常不一致问题
- Java 编译器查看 catch 块中的 throw 语句,然后查看 e 的类型,会指出这个方法可以抛出任何 Exception 而不只是 SQLException。
- 在Java SE 7,编译器会跟踪到 e 来自 try块。假设这个 try块中仅有的已检査异常是 SQLException 实例, 另外, 假设 e 在catch 块中未改变, 将外围方法声明为 throws SQLException 就是合法的。(实际类型确定)
7.2.4 finally 子句
资源回收问题
- 如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
finally语句
- 不管是否有异常被捕获,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();
}
- 代码没有抛出异常。在这种情况下,程序首先执行 try 语句块中的全部代码,然后执行 finally 子句中的代码t 随后,继续执行 try 语句块之后的第一条语句。也就是说,执行标注的 1、 2、 5、 6 处。
- 抛出一个在 catch 子句中捕获的异常。在上面的示例中就是 IOException 异常。在这种情况下,程序将执行 try语句块中的所有代码,直到发生异常为止。此时,将跳过 try语句块中的剩余代码,转去执行与该异常匹配的 catch 子句中的代码,最后执行 finally 子句中的代码。
如果 catch 子句没有抛出异常, 程序将执行 try 语句块之后的第一条语句。在这里,执行标注 1、 3、 4、5、 6 处的语句。
如果 catch 子句抛出了一个异常, 异常将被抛回这个方法的调用者。在这里, 执行标注1、 3、 5 处的语句。 - 代码抛出了一个异常,但这个异常不是由 catch 子句捕获的。在这种情况下,程序将执行 try 语句块中的所有语句,直到有异常被抛出为止。此时,将跳过 try 语句块中的剩余代码,然后执行 finally子句中的语句, 并将异常抛给这个方法的调用者。在这里,执行标注 1、5 处的语句。
有finally无catch的try语句
InputStream in = . .
try{
code that might throw exceptions
}finally {
in.close();
}
- try 语句可以只有 finally 子句,而没有 catch 子句。
- 在需要关闭资源时,用这种方式使用 finally 子句是一种不错的选择。
解耦合 try/catch 和 try/finally 语句块
InputStrean in = . . .;
try{
try{
code that might throw exceptions
}finally {
in.close();
}
}catch (IOException e) {
show error message
}
- 这种设计方式不仅清楚,而且还具有一个功能,就是将会报告 finally 子句中出现的错误。
包含return的finally语句
- 假设利用 return语句从 try语句块中退出。在方法返回前,finally 子句的内容将被执行。如果 finally 子句中也有一个 return 语句,这个返回值将会覆盖原始的返回值。
public static int f(int n) {
try{
int r = n * n;
return r;
}finally {
if (n = 2) return 0;
}
}
如果调用 f(2), 那么 try 语句块的计算结果为 r = 4, 并执行 return 语句然而,在方法真正返回前,还要执行 finally 子句。finally 子句将使得方法返回 0, 这个返回值覆盖了原始的返回值 4。
finally的异常
- 执行 finally 语句块,并调用 dose 方法。而 close 方法本身也有可能抛出 IOException 异常。
- 因为第一个异常很可能更有意思。如果你想做适当的处理,重新抛出原来的异常,代码会变得极其繁琐。
InputStream in = ...;
Exception ex = null;
try {
try
code that might throw exceptions
}catch (Exception e) {
throw e;
}
}finally {
try{
in.close();
}catch (Exception e){
if (ex = null) throw e;
}
}
7.2.5 带资源的 try 语句
带资源的 try 语句
//**最简形式**
try (Resource res = . . .) {
work with res
}
-
Java SE 7 中,增加了一个AutoCloseable 接口,它一个方法:void close() throws Exception。
另外,还有一个 Closeable 接口。这是 AutoCloseable 的子接口,也包令一个 close方法。不过,这个方法声明为抛出一个 IOException。
-
可以指定多个资源。
try (
Scanner in = new Scanner(new FileInputStream("7usr/share/dict/words"), "UTF-8");
PrintWriter out = new PrintWriter("out.txt")
){
while (in.hasNextO)
out.println(in.next().toUpperCaseO);
}
- 带资源的 try 语句中,原来的异常会重新抛出,而 close方法抛出的异常会 “被抑制” 。可以调用 getSuppressed 方法获得从 close 方法抛出并被抑制的异常列表。
- 带资源的 try 语句自身也可以有 catch 子句和一个 finally 子句。这些子句会在关闭资源之后执行。(无需关注太多)
7.2.6 分析堆栈轨迹元素
堆栈轨迹
- 堆栈轨迹 (stack trace) 是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
- 可以调用 Throwable 类的 printStackTrace 方法访问堆栈轨迹的文本描述信息。
Throwable t = new Throwable();
StringWriter out = new StringWriter() ;
t.printStackTrace(new PrintWriter(out));
String description = out.toString();
StackTraceElement 类
- 使用getStackTrace 方法会得到 StackTraceElement 对象的一个数组,可以在你的程序中分析这个对象数组。
Throwable t = new Throwable() ;
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement frame : frames)
analyze frame
- StackTraceElement 类含有能够获得文件名和当前执行的代码行号的方法, 同时,还含有能够获得类名和方法名的方法。
- 静态的 Thread.getAllStackTrace 方法,它可以产生所有线程的堆栈轨迹。
7.3 使用异常机制的技巧
-
异常处理不能代替简单的测试
基本规则是:只在异常情况下使用异常机制。
-
不要过分地细化异常
-
利用异常层次结构
不要只抛出 RuntimeException 异常。应该寻找更加适当的子类或创建自己的异常类。
不要只捕获 Thowable 异常,否则,会使程序代码更难读、更难维护。
考虑受查异常与非受查异常的区别。不要为逻辑错误抛出这些异常。
将一种异常转换成另一种更加适合的异常时不要犹豫。 -
不要压制异常
如果一个方法中异常出现情况极小概率,并不太重要,可以将异常关闭(捕获)。
如果认为异常非常重要,就应该对它们进行处理。 -
在检测错误时,“ 苛刻 ” 要比放任更好
在出错的地方抛出一个 EmptyStackException异常要比在后面抛出一个 NullPointerException 异常更好。(出错应该抛异常)
-
不要羞于传递异常
让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。
规则 5、6 可以归纳为“ 早抛出,晚捕获’
7.4 使用断言
7.4.1 断言的概念
断言的概念
- 假设确信某个属性符合要求,并且代码的执行依赖于这个属性。(断定条件没问题)
- 断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。
assert语句两种形式
assert 条件;
assert 条件 : 表达式;
- 这两种形式都会对条件进行检测,如果结果为 false, 则抛出一个 AssertionError 异常。
- 在第二种形式中,表达式将被传人 AssertionError 的构造器,并转换成一个消息字符串。
“ 表达式” 部分的唯一目的是产生一个消息字符串。
AssertionError 对象并不存储表达式的值,因此,不可能在以后得到它。
正如 JDK 文档所描述的那样:如果使用表达式的值,就会鼓励程序员试图从断言中恢复程序的运行,这不符合断言机制的初衷。
7.4.2 启用和禁用断言
默认情况
- 在默认情况下,断言被禁用。
启用/禁用断言
java -enableassertions MyApp
java -ea:... -da:MyClass MyApp
java -ea:MyClass -eaiconi.inycompany.inylib.. , MyApp
- 可以在运行程序时用 -enableassertions 或 -ea 选项启用。
- 可以用选项 -disableassertions 或 -da 禁用某个特定类和包的断言。
- 也可以在某个类或整个包中使用断言。
几点注意
- 在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器(class loader) 的功能。
- 启用和禁用所有断言的 -ea 和 -da 开关不能应用到那些没有类加载器的“ 系统类”上。对于这些系统类来说,需要使用 -enablesystemassertions/-esa 开关启用断言。
- 在程序中也可以控制类加载器的断言状态。
**ClassLoader类和断言**
- void setDefaultAssertionStatus( boolean b ) 1.4
对于通过类加载器加载的所有类来说,如果没有显式地说明类或包的断言状态,就启用或禁用断言。
- void setCIassAssertionStatus(String className , boolean b ) 1.4
对于给定的类和它的内部类,启用或禁用断言。
- void setPackageAssertionStatus( String packageName , bool ean b ) 1.4
对于给定包和其子包中的所有类,启用或禁用断言。
- void clearAssertionStatus( ) 1.4
移去所有类和包的显式断言状态设置,并禁用所有通过这个类加载器加载的类的断言。
7.4.3 使用断言完成参数检查
3种处理系统错误的机制
- 抛出一个异常
- 日志
- 使用断言
使用断言
- 断言失败是致命的,不可恢复的错误。
- 断言检查只用于开发和测阶段。
因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。
前置条件
@param a the array to be sorted (must not be null).
不允许用 null 数组调用这个方法,并在这个方法的开头使用断言:
assert a != null;
- 计算机科学家将这种约定称为前置条件 (Precondition)。
7.4.4 为文档假设使用断言
else // (i % 3 == 2) 和 else assert i % 3 == 2;
7.5 记录曰志
较难理解,内容多,且复杂,记得更多一些
记录日志优点(概况)
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
- 可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
- 日志记录可以被定向到不同的处理器,用于在控制台中显示, 用于存储在文件中等。
- 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项。
- 日志记录可以采用不同的方式格式化,例如,纯文本或 XML。
- 应用程序可以使用多个日志记录器,它们使用类似包名的这种具有层次结构的名字,例如 com.mycompany.myapp。
- 在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。
7.5.1 基本日志
全局日志记录器
- 要生成简单的日志记录,可以使用全局日志记录器(global logger) 并调用其 info 方法:
Logger.getClobal().info("File->Open menu item selected");
7.5.2 高级曰志
创建或获取记录器
- 可以调用 getLogger 方法创建或获取记录
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp"):
防止垃圾回收
- 未被任何变量引用的日志记录器可能会被垃圾回收。
- 为了防止这种情况发生,需要使用一个静态变量存储日志记录器的一个引用。
日志层次结构
- 日志记录器名也具有层次结构,且层次性更强。
- 日志记录器的父与子之间将共享某些属性,子记录器会继承父继承器的级别。
日志记录器级别
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
日志记录级别
- 在默认情况下,只记录INFO或更高级别的所有记录。
- 也可以设置其他的级別:logger.setLevel (Level .FINE)。
- 还可以使用 Level.ALL 开启所有级别的记录,或者使用 Level.OFF 关闭所有级别的记录。(需要配合修改日志处理器)
几种记录方法
logger.warning(message);
logger.fine(message);
logger.log(Level.FINE, message);//指定级别
日志堆栈信息
- 默认的日志记录将显示包含日志调用的类名和方法名。
- 但是,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。
- 可以调用 logp 方法获得调用类和方法的确切位置,这个方法的签名为:
void logp(Level 1, String className, String methodName, String message)
跟踪执行流的方法
void entering(String dassName, String methodName)
void entering(String className, String methodName, Object param)
void entering(String className, String methodName, Object[] params)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
- 这些调用将生成 FINER 级别和以字符串 ENTRY 和 RETURN 开始的日志记录。
描述异常内容的方法
void throwing(String className, String methodName, Throwable t)
void log(Level 1, String message, Throwable t)
- 调用 throwing 可以记录一条 FINER 级别的记录和一条以 THROW 开始的信息。
7.5.3 修改日志管理器配置
日志配置文件
- 可以通过编辑配置文件来修改日志系统的各种属性。
- 配置文件默认保存在:jre/lib/logging.properties。
更换配置文件
- 要想使用另一个配置文件,就要将 java.utiUogging.config.file 特性设置为配置文件的存储位置。
- 并用下列命令启动应用程序:
java -Djava.util.logging.config.file-configFile MainClass
日志管理器
- 日志管理器在 VM 启动过程中初始化,这在 main 执行之前完成。
- 如果在 main中调用 System.setProperty(“java.util.logging.config.file”,file),也会调用 LogManager.readConfiguration() 来重新初始化曰志管理器。
所以logger.setLevel (Level .FINE)只是动态修改了级别,并未修改默认值?
修改配置文件的值
.level=INFO//想修改默认的日志记录级别
com.mycompany.myapp.level=FINE//指定自己的日志记录级别
- 日志记录并不将消息发送到控制台上,这是处理器的任务。
- 要想在控制台上看到 FINE 级别的消息,就需要进行下列设置
java.util.logging.ConsoleHandler.level=FINE
注意无法修改系统属性
- 在曰志管理器配置的属性设置不是系统属性,因此,用 - Dcom.mycompany.myapp.level= FINE 启动应用程序不会对日志记录器产生任何影响。
可以通过修改配置文件的值使属性生效,但无法通过命令行执行程序的时候修改属性。
修改日志管理器
- 日志属性文件由 java.util.logging.LogManager 类处理。
- 可以通过将 java.util.logging.manager 系统属性设置为某个子类的名字来指定一个不同的日志管理器。
另外,在保存标准日志管理器的同时,还可以从日志属性文件跳过初始化。还有一种方式是将 java.util.logging.config.class 系统属性设置为某个类名,该类再通过其他方式设定日志管理器属性。有关 LogManager 类的佯细内容请参看 API 文档。
7.5.4 本地化
本地化
- 本地化的应用程序包含资源包 (resource bundle) 中的本地特定信息。资源包由各个地区 (如美国或德国) 的映射集合组成。
例如, 某个资源包可能将字符串“ readingFile” 映射成英文的 “ Reading file” 或者德文的“ Achtung! Datei wird eingelesen”。
资源包
- 一个程序可以包含多个资源包,一个用于菜单;其他用于日志消息。
- 每个资源包都个名字(如 com.mycompany.logmessages)。
- 要想将映射添加到一个资源包中,需要为每个地区创建一个文件。
日志中使用
Logger logger = Logger.getLogger(1oggerName, "com.mycompany.1ogmessages");
logger.info("readingFi1e");
- 在请求日志记录器时,可以指定一 资源包:
- 然后,为日志消息指定资源包的关键字,而不是实际的日志消息字符串。
占位符的使用
- 通常需要在本地化的消息中增加一些参数,因此,消息应该包括占位符{0}、{1} 等。
//日志文件名中使用
logger.log(Level.INFO, "readingFile", fileName);
logger.log(Level.INFO, "renami ngFile", new Object[] { oldName , newName });
7.5.5 处理器
日志处理器
- 在默认情况下,日志记录器将记录发送到自己的处理器和父处理器。
- 日志记录器是原始日志记录器 (命名为“ ”) 的子类,原始日志记录器有一个 ConsoleHandler 处理器,它会将记录输出到 System.err 流中。
- 处理器也有日志记录级别,默认级别为INFO。
切换处理器
Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel (Level.FINE);
1ogger.setUseParentHandlers(false);
//因为发到自己和父处理器,不想两次看到这些记录
Handler handler = new ConsoleHandler();
handler,setLevel (Level.FINE);
1ogger.addHandler(handler):
- 原始日志记录器将会把所有等于或高于 INFO级別的记录发送到控制台。(使用 setUseParentHandlers(false) 取消)
API处理器
- FileHandler, 它可以收集文件中的记录。
- SocketHandler 将记录发送到特定的主机和端口。
FileHandler处理器
- 记录被发送到用户主目录的 javan.log 文件中,n 是文件名的唯一编号。
如果用户系统没有主目录 (例如,在 Windows95/98/Me) ,文件就存储在 C:\Window 这样的默认位置上。
- 在默认情况下,记录被格式化为 XML。
文件处理器配置参数

日志记录文件模式变量

文件命名说明
- 如果多个应用程序 (或者同一个应用程序的多个副本) 使用同一个口志文件,就应该开启 append 标志。
- 应该在文件名模式中使用 %u, 以便每个应用程序创建日志的唯一副本。
- 开启文件循环功能,日志文件以 myapp.log.O, myapp.log.1 , myapp.log.2, 这种循环序列的形式出现。只要文件超出了大小限制,最旧的文件就会被删除,其他的文件将重新命名, 同时创建一个新文件,其编号为 0。
Handler handler = new Fi1eHandler("%h/LogginglmageViewer.log", 0, LOG_ROTATION_COUNT):
StreamHandler 类自定义处理器
- 使用这种方式只存在一个问题,这就是处理器会缓存记录,并且只有在缓存满的吋候才将它们写人流中,需要覆盖 publish 方法,以便在处理器获得每个记录之后刷新缓冲区。
7.5.6 过滤器
过滤器
- 过滤器根据日志记录的级别进行过滤。
- 每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。可以通过实现 Filter 接口并定义下列方法来自定义过滤器。
boolean isLoggab1e(LogRecord record)
//可以利用自己喜欢的标准, 对日志记录进行分析,返回 true 表示这些记录应该包含在日志中。
- 可以使用 setFilter 方法将一个过滤器安装到一个日志记录器或处理器中。
- 同一时刻最多只能有一个过滤器。
7.5.7 格式化器
格式化器
- ConsoleHandler 类和 FileHandler 类可以生成文本和 XML 格式的日志记录。
- 可以扩展 Formatter 类并覆盖下面这个方法,自定义格式 (格式化器) 。
String format(LogRecord record)
- 在 format 方法中,String formatMessage(LogRecord record) 可以对记录中的部分消息进行格式化、参数替换和本地化应用操作。
- 要在已格式化的记录的前后加上一个头部和尾部在这个例子中,要覆盖下面两个方法:
String getHead(Handler h)
String getTail (Handler h)
- 调用 setFormatter 方法将格式化器安装到处理器中。
7.5.8 日志记录说明
日志说明书
- 为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字。
- 为了方便起见,可能希望利用一些日志操作将下面的静态域添加到类中。
- 改变配置需要做相当多的工作,最好在应用程序中安装一个更加适宜的默认配置。
//下列代码确保将所有的消息记录到应用程序特定的文件中。
if (System.getProperty("java.util.logging.config.class") == null
&& System.getProperty("java.util.logging.config.file") == null) {
try{
Logger.getLogger("").setLevel(Level.ALL);
final int LOC_ROTATION_COUNT = 10;
Handler handler = new FileHandler("Wmyapp.log", 0, LOG_ROTATI0N_COUNT):
Logger.getLogger("").addHandler(handler):
}catch (IOException e) {
logger.log(Level.SEVERE, "Can't create log file handler", e);
}
}
- 最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为 FINE 是一个很好的选择。
7.6 调试技巧
- 可以用下面的方法打印或记录任意变量的值:
System.out.println("x=" + x);
Logger.getClobal0-info(nx=" + x);
- 在每一个类中放置一个单独的 main方法。这样就可以对每一个类进行单元测试。
- JUiiit 是一个非常常见的单元测试框架,利用它可以很容易地组织测试用例套件。
- 日志代理(logging proxy) 是一个子类的对象,它可以截获方法调用, 并进行日志记录,然后调用超类中的方法。
//覆写方法的匿名内部类
//当调用 nextDouble 方法时,就会产生一个日志消息。
Random generator = new Random(){
public double nextDouble(){
double result = super.nextDouble();
Logger.getClobal().info("nextDouble: " + result);
return result;
}
};
- 利 用 Throwable 类提供的 printStackTrace 方法,可以从任何一个异常对象中获得堆栈情况。
只要在代码的任何位置插入 Thread.duapStack() 语句就可以获得堆栈轨迹;
//代码将捕获任何异常,打印异常对象和堆栈轨迹,然后重新拋出异常
try{
...
}catch (Throwable t) {
t.printStackTraceO;
throw t;
}
- —般来说, 堆栈轨迹显示在 System.err 上。可以利用 printStackTrace(PrintWriter s)方法将它发送到一个文件中。
//记录或显示堆栈轨迹
StringWriter out = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(out));
String description = out.toString();
- 将一个程序中的错误信息保存在一个文件中是非常有用的。然而,错误信息被发送到 System.err 中,而不是 System.out 中。
//不能够通过运行下面的语句获取它们:
java MyProgram > errors.txt
//而是采用下面的方式捕获错误流:
java MyProgram 2> errors.txt
//在同一个文件中同时捕获 System.err和 System.out
java MyProgram 1> errors.txt 2>&1
- 让非捕获异常的堆栈轨迹出现在 System.err 中并不是一个很理想的方法。可以调用静态的 Thread.setDefaultUncaughtExceptionHandler 方法改变非捕获异常的处理器:
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
//save information in logfile
};
};
)
- 要想观察类的加载过程,可以用 -verbose 标志启动 Java 虚拟机。
- -Xlint 选项告诉编译器对一些普遍容易出现的代码问题进行检査。
术语“ lint” 最初用来描述一种定位 C 程序中潜在问题的工具,现在通常用于描述查找可疑但不违背语法规则的代码问题的工具。
| 参数 | 解释 |
|---|---|
| -Xlint 或 -Xlint:all | 执行所有的检查 |
| -Xlint:deprecation | 与-deprecation一样,检查废弃的方法 |
| -Xlint:fall through | 检查switch语句中是否缺少break语句 |
| -Xlint :finally | 警告finally子句不能正常地执行 |
| -Xlint:none | 不执行任何检查 |
| -Xlint:path | 检查类路径和源代码路径上的所有目录是否存在 |
| -Xlint:serial | 警告没有serialVersionUID的串行化类 |
| -Xlint:unchecked | 对通用类型与原始类型之间的危险转换给予警告 |
- java 虚拟机增加了对 Java 应用程序进行监控 (monitoring) 和管理 (management ) 的支持。它允许利用虚拟机中的代理装置跟踪内存消耗、线程使用、类加载等情况。
找出运行虚拟机的操作系统进程的 ID。在 UNIX/Linux 环境下,运行 ps 实用工具,在 Windows 环境下,使用任务管理器。然后运行jconsole 程序:jconsole processID
- 可以使用 jmap 实用工具获得一个堆的转储, 其中显示了堆中的每个对象。
jmap -dump:format=b, file=dumpFileName processID
jhat dumpFileName
//然后,通过浏览器进人localhost:7000,将会运行一个网络应用程序,借此探查转储对象时堆的内容。
- 如果使用-Xprof标志运行 Java 虚拟机,就会运行一个基本的剖析器来跟踪那些代码中经常被调用的方法。剖析信息将发送给 System.out 。输出结果中还会显示哪些方法是由即时编译器编译的。
1万+




被折叠的 条评论
为什么被折叠?



