短时间的投入学习很难得到反馈,切记莫要放弃,先坚持下去,再总结教训与经验,什么时候开始都不算晚
一个程序难免会出现错误,所以务必做到以下几点:
异常的作用:对于异常情况, 例如,可能造成程序崩溃的错误输入,Java使用一种称为异 常处理( exception handing) 的错误捕获机制处理。
断言的作用:在测试期间, 需要进行大量的检测以验证程序操作的正确性。 然而,这些检测可能非常耗时,在测试完成后也不必保留它们,因此,可以将这些检测删掉, 并在其他测试需要时将它们粘贴回来,这是一件很乏味的事情。本章的第 2 部分将介绍如何使用断言来有选择地启用检测。
日志的作用:当程序出现错误时,并不总是能够与用户或终端进行沟通。此时,可能希望记录下出现的问题,以备日后进行分析。
用户期望在出现错误时, 程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成, 程序应该:
异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。需要关注的问题:
对于方法中的一个错误,传统的做法是返回一个特殊的错误码,遗憾的是,并不是在任何情况下都能够返回一个错误码。有可能无法明确地将有效数据
与无效数据加以区分。一个返回整型的方法就不能简单地通过返回-1 表示错误,因为-1很可能是一个完全合法的结果。
在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例。
需要注意的是,所有的异常都是由 Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception。
Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。程序员也无能为力。
在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包含下面几种情况:
不是派生于 RuntimeException 的异常包括:
“ 如果出现 RuntimeException异常, 那么就一定是你的问题” 是一条相当有道理的规则。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查( unchecked ) 异常,所有其他的异常称为受查(checked) 异常。
如果遇到了无法处理的情况, 那么 Java 的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
在自己编写方法时, 不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用 throws 子句声明异常, 什么异常必须使用 throws 子句声明, 需要记住在遇到下面 4 种情况时应该抛出异常:
如果没有处理器捕获这个异常,当前执行的线程就会结束。
对于那些可能被他人使用的 Java 方法, 应该根据异常规范( exception specification), 在方法的首部声明这个方法可能抛出的异常。
如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
总之,一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制( Error),要么就应该避免发生( RuntimeException)。如果方法没有声明所有可能发生的受查异常, 编译器就会发出一个错误消息。
如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方法中声明的异常更通用 (也就是说, 子类方法中可以抛出更特定的异常, 或者根本不抛出任何异常)。特别需要说明的是, 如果超类方法没有抛出任何受查异常, 子类也不能抛出任何受查异常。例如, 如果覆盖 JComponent.paintComponent 方法, 由于超类中这个方法没有抛出任何异常,所以, 自定义的 paintComponent 也不能抛出任何受查异常。
首先要决定应该抛出什么类型的异常。
EOFException 异常描述的是“ 在输人过程中, 遇到了一个未预期的 EOF 后的信号”。
EOFException 类还有一个含有一个字符串型参数的构造器。 这个构造器可以更加细致的描述异常出现的情况。
String gripe = "Content-length: " + len + ", Received: " + n;
throw new EOFException(gripe);
对于一个已经存在的异常类, 将其抛出非常容易,在这种情况下:
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。 在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception的类,或者派生于 Exception 子类的类。定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息的构造器。
例如:
class FileFormatException extends IOException
{
public FileFormatException() {}
public FileFormatException(String gripe) {
super(gripe);
}
}
//就可以自己定义异常了
if (condition)
throw new FileFornatException();//throw new FileFornatException(String);
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息, 其中包括异常的类型和堆栈的内容。
要想捕获一个异常, 必须设置 try/catch语句块。
try{
code
more code
}
catch (ExceptionType e) {
handler for this type
}
如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类, 那么:
如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句。
通常, 应该捕获那些知道如何处理的异常, 而将那些不知道怎样处理的异常继续进行传递。如果想传递一个异常, 就必须在方法的首部添加一个 throws 说明符, 以便告知调用者这个方法可能会抛出异常。
如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如JComponent 中的 paintComponent ), 那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。
在一个 try 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的 catch 子句:
try
{
code that might throwexceptions
}
catch (FileNotFoundException e) {
emergencyactionfor missingfiles
}
catch (UnknownHostException e) {
emergency actionfor unknown hosts
}
catch (IOException e) {
emergencyactionfor all other I/O problems
}
得到详细的错误信息e.getHessage()
,得到异常对象的实际类型e.getClass().getName()
。
同一个 catch 子句中可以捕获多个异常类型。
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
}
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
捕获多个异常时, 异常变量隐含为 final 变量。,不能为e赋值。
在 catch 子句中可以抛出一个异常,这样做的目的是改变异常的类型,: 如果开发了一个供其他程序员使用的子系统, 那么,用于表示子系统故障的异常类型可能会产生多种解释。ServletException 就是这样一个异常类型的例子。执行 servlet 的代码可能不想知道发生错误的细节原因, 但希望明确地知道 servlet 是否有问题。
try
{
access the database
}
catch (SQLException e) {
throw new ServletException("database error: " + e.getMessage());
}
这里,ServleException 用带有异常信息文本的构造器来构造。
不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的“原因”:
try
{
access the database
}
catch (SQLException e)
Throwable se = new ServletException ("database error");
se.initCause(e);
throw se;
}
当捕获到异常时, 就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
如果在一个方法中发生了一个受查异常, 而不允许抛出它, 那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常。
当代码抛出一个异常时, 就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
Java 有一种更好的解决方案,这就是 finally 子句。
不管是否有异常被捕获,finally 子句中的代码都被执行。
finally 子句执行情况:
try 语句可以只有 finally 子句,而没有 catch 子句。
try
{
code that might throwexceptions
}
finally
{
in.close();
}
无论在 try 语句块中是否遇到异常,finally 子句中的 in.close() 语句都会被执行。
强烈建议解耦合try/catch 和 try/finally 语句块。这样可以提高代码的清晰度。如下:
InputStrean in = . . .;
try
{
try
{
code that might throwexceptions
}
finally
{
in.doseO;
}
}
catch (IOException e) {
show error message
}
内层的 try语句块只有一个职责, 就是确保关闭输入流。外层的 try 语句块也只有一个职责, 就是确保报告出现的错误。这种设计方式不仅清楚, 而且还具有一个功能,就是将会报告 finally 子句中出现的错误。
当 finally 子句包含 return 语句时, 将会出现一种意想不到的结果„ 假设利用 return语句从 try语句块中退出。在方法返回前, finally 子句的内容将被执行。如果 finally 子句中也有一个 return 语句,这个返回值将会覆盖原始的返回值。 finally子句的体要用于清理资源,不要把改变控制流的语句(return、throw、continue、break)放在finally子句里面。
假设资源属于一个实现了 AutoCloseable 接口的类,Java SE 7 为这种代码模式提供了一个很有用的快捷方式。AutoCloseable 接口有一个方法:
void close() throws Exception
另外,还有一个 Closeable 接口。这是 AutoCloseable 的子接口, 也包令 •个 close方法。不过, 这个方法声明为抛出一个 IOException。
带资源的 try 语句(try-with-resources) 的最简形式为:
try (Resource res = . . .) {
work with res
}
try块退出时,会自动调用 res.close()。下面给出一个典型的例子, 这里要读取一个文件中的所有单词:
try (Scanner in = new Scanner(new FileInputStream("/user/share/dict/words")), "UTF-8") {
while (in.hasNext())
System.out.println(in.next());
}
这个块正常退出时, 或者存在一个异常时, 都会调用 inxloseO 方法, 就好像使用了finally块一样。
还可以指定多个资源例如:
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"). "UTF-8"):
PrintWriter out = new PrintWriter("out.txt")) {
while (in.hasNextO)
out.println(in.next().toUpperCase());
}
不论这个块如何退出, in 和 out 都会关闭。如果你用常规方式手动编程,就需要两个嵌套的 try/finally语句。
只要需要关闭资源, 就要尽可能使用带资源的 try语句。
带资源的 try 语句自身也可以有 catch 子句和一个 finally 子句。 这些子句会在关闭资源之后执行。
再Java9中,想要再try的括号内传参进去的话,只能传一个事实最终变量, 也就是finally修饰的。
堆栈轨迹( stack trace ) 是一个方法调用过程的列表, 它包含了程序执行过程中方法调用的特定位置前面已经看到过这种列表, 当 Java 程序正常终止, 而没有捕获异常时, 这个列表就会显示出来。
可以调用 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
有出处
异常处理不能代替简单的测试,异常的基本规则是:只在异常情况下使用异常机制。
.不要过分地细化异常,有必要将整个任务包装在一个 try语句块,使用多个catch处理。
利用异常层次结构。
不要只抛出 RuntimeException 异常。应该寻找更加适当的子类或创建自己的异常类。
不要只捕获 Thowable 异常, 否则,会使程序代码更难读、 更难维护。
不要压制异常。发生异常的频数很低很低很低,可以再catch块中什么也不写。
在检测错误时,“ 苛刻 ” 要比放任更好,例如栈空的时候,抛出一个 EmptyStackException异常要比在后面抛出一个 NullPointerException 异常更好。
不要羞于传递异常。让高层次的方法通知用户发生了错误, 或者放弃不成功的命令更加适宜。规则 5、6 可以归纳为“早抛出,晚捕获”
在一个具有自我保护能力的程序中, 断言很常用。
断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。
Java 语言引人了关键字 assert。这个关键字有两种形式:
assert 条件;//第一种
assert 条件:表达式;//第二种
这两种形式都会对条件进行检测, 如果结果为 false, 则抛出一个 AssertionError 异常。在第二种形式中,表达式将被传人 AssertionError 的构造器, 并转换成一个消息字符串。
要想断言x是一个非负数值, 只需要简单地使用下面这条语句
assert x >= 0;
或者将 x 的实际值传递给 AssertionError 对象, 从而可以在后面显示出来。
assert x >= 0 : x;
在默认情况下, 断言被禁用。可以在运行程序时用 -enableassertions 或 -ea 选项启用:
java -enableassertions MyApp
需要注意的是, 在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器( class loader) 的功能。当断言被禁用时, 类加载器将跳过断言代码, 因此,不会降低程序运行的速度。
也可以在某个类或整个包中使用断言, 例如:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
这条命令将开启 MyClass 类以及在 com.mycompany.mylib 包和它的子包中的所有类的断言。选项 -ea 将开启默认包中的所有类的断言。
也可以用选项 -disableassertions 或 -da 禁用某个特定类和包的断言:
ava -ea:... -da:MyClass MyApp
然而, 启用和禁用所有断言的 -ea 和 -da 开关不能应用到那些没有类加载器的“ 系统类” 上。在程序中也可以控制类加载器的断言状态。
在 Java 语言中, 给出了 3 种处理系统错误的机制:
因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。
由于可以使用断言,当方法被非法调用时, 将会出现难以预料的结果。有时候会拋出一个断言错误, 有时候会产生一个null指针异常, 这完全取决于类加载器的配置。
断言是一种测试和调试阶段所使用的战术性工具; 而日志记录是一种在程序的整个生命周期都可以使用的策略性工具。
if (i % 3 == 0)
else if (i % 3 = 1)
else // (i % 3 == 2)
...
在这个示例中,使用断言会更好一些。
if (i % 3 == 0)
else if (i % 3 == 1)
...
else
{
assert i % 3 == 2;
...
}
实际上都认为 i 是非负值, 因此, 最好在 if 语句之前使用下列断言:
assert i >= 0;
记录日志API的优点:
本章的日志API指的是标准的Java日志框架,在很多用途方面,这个框架已经足够好,而且学习这个框架的API可以给理解其他框架做准备。例如常用的日志框架:log4j、Logback
要生成简单的日志记录,可以使用全局日志记录器(global logger) 并调用其 info 方法:
Logger.getGlobal().info("File->Open menu item selected");
在默认情况下,这条记录将会显示以下内容:
May 10, 2013 10:12:15 PM LogginglmageViewer fileOpen
INFO: File->0pen menu item selected
但是, 如果在适当的地方(如 main 开始)调用
Logger.getGlobal().setLevel (Level.OFF);
将会取消所有的日志。
在一个专业的应用程序中,不要将所有的日志都记录到一个全局日志记录器中,而是可以自定义日志记录器。
可以调用 getLogger 方法创建或获取记录器:
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
为什么是static类型呢?因为未被任何变量引用的日志记录器可能会被垃圾回收。
与包名类似,日志记录器名也具有层次结构。事实上, 与包名相比,日志记录器的层次性更强。例如, 如果对 com.mycompany 日志记录器设置了日志级别,它的子记录器也会继承这个级别。
Logger logger=Logger.getLogger("com.zjx.myapp");
Logger logger1=Logger.getLogger("com.zjx");
logger1.setLevel(Level.OFF);
logger.info("aaaa");//不打印
通常, 有以下 7 个日志记录器级别:
logger.setLevel(Level.FINE);
现在, FINE 和更高级别的记录都可以记录下来。可以使用 Level.ALL 开启所有级别的记录, 或者使用 Level.OFF 关闭所有级别的记录。
对于所有的级别有下面几种记录方法:
logger.warning(message);
logger.log(Level.FINE, message);
默认的日志配置记录了 INFO 或更高级别的所有记录, 因此,应该使用 CONFIG、FINE, FINER 和 FINEST 级别来记录那些有助于诊断,但对于程序员又没有太大意义的调试信息。如果将默认的记录级别设计为 INFO 或者更低, 则需要修改日志处理器的配置。 默认的日志处理器不会处理低于 INFO 级别的信息。
可以调用 logp 方法获得调用类和方法的确切位置, 这个方法的签名为:
logger.logp(Level.INFO,getClass().getName(),"test11","执行了");
输出:
Jul 23, 2020 6:46:29 PM com.interfaceStudy.InterfaceTest test11
INFO: 执行了
格式:
时间 类名 方法名
INFO:信息体
为什么使用logp而不是使用log,因为默认的日志记录将显示包含日志调用的类名和方法名, 如同堆栈所显示的那样。但是,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。
下面有一些用来跟踪执行流的方法:
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 开始的日志记录。
例如:
public void read(String filepath) throws FileNotFoundException {
Logger logger=Logger.getLogger("com.zjx.myapp");
logger.setLevel(Level.ALL);
//方法开始的时候
logger.entering(getClass().getName(),"read",filepath);
File file=new File(filepath);
FileInputStream fileInputStream = null;
try {
fileInputStream=new FileInputStream(file);
byte[] bytes = new byte[1024];
while ((fileInputStream.read(bytes))!=-1){
fileInputStream.read(bytes,0,bytes.length);
System.out.println(new String(bytes));
}
} catch (FileNotFoundException e) {
FileNotFoundException f = new FileNotFoundException("找不到文件");
e.initCause(f);
throw e;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileInputStream!=null){
try {
fileInputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
//方法结束的时候
logger.exiting(getClass().getName(),"read",filepath);
}
}
这里要先去日志配置文件种设置能在控制台上打印的级别,而且运行时加入-D参数,不然无法显示,默认的是从INFO开始才显式的控制台上。
输出:
Jul 23, 2020 7:30:49 PM com.interfaceStudy.InterfaceTest read
FINER: ENTRY src/test.txt
123123
asdsa
Jul 23, 2020 7:30:49 PM com.interfaceStudy.InterfaceTest read
FINER: RETURN src/test.txt
记录日志的常见用途是记录那些不可预料的异常。可以使用下面两个方法提供日志记录中包含的异常描述内容。
void throwing(String className , String methodName , Throwable t)
void log(Level 1 , String message , Throwable t)
例如还是上面的那个代码在catch块种修改如下:
catch (FileNotFoundException e) {
FileNotFoundException f = new FileNotFoundException("找不到文件");
logger.throwing(getClass().getName(),"read",f);
e.initCause(f);
throw e;
}
输出:
Jul 23, 2020 7:37:28 PM com.interfaceStudy.InterfaceTest read
FINER: ENTRY /src/test.txt
Jul 23, 2020 7:37:28 PM com.interfaceStudy.InterfaceTest read
FINER: THROW
java.io.FileNotFoundException: 找不到文件
at com.interfaceStudy.InterfaceTest.read(InterfaceTest.java:175)
at com.interfaceStudy.InterfaceTest.test12(InterfaceTest.java:151)
…
Jul 23, 2020 7:37:28 PM com.interfaceStudy.InterfaceTest read
FINER: RETURN /src/test.txt
java.io.FileNotFoundException: 找不到文件
\src\test.txt (The system cannot find the path specified)
可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于: jre/lib/logging.properties
要想使用另一个配置文件, 就要将 java.util.logging.config.file 特性设置为配置文件的存储位置, 并用下列命令启动应用程序:
java -Djava.util.logging.config. file=configFile MainClass
日志管理器在 VM 启动过程中初始化, 这在 main 执行之前完成。 如果在 main中调用 System.setProperty(“java.util.logging.config.file”,file), 也 会 调 用 LogManager.readConfiguration() 来重新初始化曰志管理器。第二个参数是文件的路径。
要想修改默认的日志记录级别, 就需要编辑配置文件,并修改以下命令行
.level=INFO
可以通过添加以下内容来指定自己的日志记录级别
日志记录器名.level,例如上面的
com.zjx.myapp.level=FINE
也就是说,在日志记录器名后面添加后缀 .level。
要想在控制台上看到 FINE 级别的消息, 就需要进行下列设置
java.util.logging.ConsoleHandler.level=FINE
在曰志管理器配置的属性设置不是系统属性, 因此, 用 -Dcom.mycompany.myapp.level= FINE 启动应用程序不会对日志记录器产生任何影响 。
举个例子来说就是:某个资源包可能将字符串“ readingFile” 映射成英
文的 “ Reading file” 或者德文的“ Achtung! Datei wird eingelesen”。
在默认情况下t 日志记录器将记录发送到 ConsoleHandler 中, 并由它输出到 System.err流中。特别是,日志记录器还会将记录发送到父处理器中,**而最终的处理器(命名为"")**有一个 ConsoleHandler。
与日志记录器一样,处理器也有日志记录级别。对于一个要被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值。日志管理器配置文件设置的默认控制台处理器的日志记录级别为
java.uti1.1ogging.ConsoleHandler.level =INF0
要想记录 FINE 级别的日志,就必须修改配置文件中的默认日志记录级别和处理器级别。另外,还可以绕过配置文件,安装自己的处理器。
Logger logger=Logger.getLogger("com.zjx");
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
Handler handler=new ConsoleHandler();
handler.setLevel(Level.FINEST);
//安装上去
logger.addHandler(handler);
logger.finest("绕过配置文件,安装自己的处理器");
在默认情况下, 日志记录器将记录发送到自己的处理器和父处理器。我们的日志记录器是原始日志记录器(命名为“ ”)的子类,而原始日志记录器将会把所有等于或高于 INFO,级別的记录发送到控制台。然而,我们并不想两次看到这些记录。鉴于这个原因,应该将useParentHandlers属性设置为false。
**要想将日志记录发送到其他地方, 就要添加其他的处理器。**日志 API为此提供了两个很有用的处理器:一个是 FileHandler,另一个SocketHandler。SocketHandler 将记录发送到特定的主机和端口, 而更令人感兴趣的是 FileHandler, 它可以收集文件中的记录。
可以像下面这样直接将记录发送到默认文件的处理器:
Logger logger=Logger.getLogger("com.zjx");
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
FileHandler handler=new FileHandler();
handler.setLevel(Level.FINEST);
//安装上去
logger.addHandler(handler);
logger.finest("绕过配置文件,安装自己的处理器");
这些记录被发送到用户主目录的 javan.log 文件中, n 是文件名的唯一编号。 如果用户系统没有主目录( 例如, 在 Windows95/98/Me,) 文件就存储在 C:\Window 这样的默认位置上,我的电脑是Win10,在C盘的User下面的某一个文件夹里面。
上面程序的输出被格式化为XML文档。
可以通过设置 H 志管理器配置文件中的不同参数(请参看表 7-1 ,) 或者利用其他的构造器 (请参看本节后面给出的 API注释)来修改文件处理器的默认行为。
也冇可能不想使用默认的日志记录文件名, 因此, 应该使用另一种模式, 例如, %h/myapp.log ( 有关模式变量的解释请参看表 7-2 )。
如果多个应用程序 (或者同一个应用程序的多个副本)使用同一个口志文件, 就应该开启 append 标志。 另外, 应该在文件名模式中使用 %u, 以便每个应用程序创建日志的唯一副本。
开启文件循环功能也是一个不错的主意。日志文件以 myapp.log.0, myapp.log.1 , myapp.log.2, 这种循环序列的形式出现3 只要文件超出了大小限制, 最旧的文件就会被删除, 其他的文件将重新命名, 同时创建一个新文件, 其编号为 0。
还可以通过扩展 Handler 类或 StreamHandler 类自定义处理器。如下:
@Test
public void test15() throws IOException {
Logger logger=Logger.getLogger("com.zjx");
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
Handler handler=new StreamHandler(){
{
JFrame jf = new JFrame("测试窗口");
jf.setSize(500, 500);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
// 创建一个 5 行 10 列的文本区域
final JTextArea textArea = new JTextArea(20,20);
// 设置自动换行
textArea.setLineWrap(true);
// 添加到内容面板
panel.add(textArea);
jf.setContentPane(panel);
jf.setVisible(true);
setOutputStream(new OutputStream() {
@Override
public void write(int b) throws IOException { }
@Override
public void write(byte[] b,int off,int len){
textArea.append(new String(b,off,len));
}
});
}
@Override
public void publish(LogRecord record){
super.publish(record);
flush();
}
};
handler.setLevel(Level.FINEST);
//安装上去
logger.addHandler(handler);
logger.info("这个就类似于输出流的一个重定向");
Scanner scanner=new Scanner(System.in);
scanner.nextLine();
}
输出:
使用这种方式只存在一个问题, 这就是处理器会缓存记录, 并且只有在缓存满的吋候才将它们写人流中, 因此, 需要覆盖 publish 方法, 以便在处理器获得每个记录之后刷新缓冲区。
如果希望编写更加复杂的流处理器,就应该扩展 Handler 类, 并自定义 publish、 flush 和close 方法。
在默认情况下, 过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。另外,可以通过实现 Filter 接口并定义下列方法来自定义过滤器。
boolean isLoggable(LogRecord record)
要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用 setFilter 方法就可以了。 注意,同一时刻最多只能有一个过滤器。
ConsoleHandler 类和 FileHandler 类可以生成文本和 XML 格式的日志记录。但是, 也可以自定义格式。这需要扩展 Formatter 类并覆盖下面这个方法:
String format(LogRecord record);
很多文件格式(如 XML) 需要在已格式化的记录的前后加上一个头部和尾部在这个例子中,要覆盖下面两个方法:
String getHead (Handler h)
String getTail (Handler h)
最后,调用 setFormatter 方法将格式化器安装到处理器中。
面对日志记录如此之多的可选项, 很容易让人忘记最基本的东西。下面的“ 日志说明书”总结了一些最常用的操作。
Logger logger = Logger.getLogger("com.mycompany.myprog");
为了方便起见,可能希望利用一些日志操作将下面的静态域添加到类中:private static final Logger logger = Logger.getLogger("com.mycompany.nyprog");
多查看日志信息和控制台输出,断点F7 F8 F9你值得拥有!