阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))

短时间的投入学习很难得到反馈,切记莫要放弃,先坚持下去,再总结教训与经验,什么时候开始都不算晚

Java核心技术Ⅰ

  • 第七章
    • 7.1 处理错误
      • 7.1.1 异常分类
      • 7.1.2 声明受查异常
      • 7.1.3 如何抛出异常
      • 7.1.4 创建类异常
    • 7.2 捕获异常
      • 7.2.1 捕获异常
      • 7.2.2 捕获多个异常
      • 7.2.3 再次抛出异常与异常链
      • 7.2.4 finally子句
      • 7.2.5 try-with-Resource语句
      • 7.2.6 分析堆栈轨迹元素
    • 7.3 使用异常机制的技巧
    • 7.4 使用断言
      • 7.4.1 断言的概念
      • 7.4.2 启用和禁用断言
      • 7.4.3 使用断言完成参数检查
      • 7.4.4 为文档假设使用断言
    • 7.5 记录日志
      • 7.5.1 基本日志
      • 7.5.2 高级日志
      • 7.5.3 修改日志管理器配置
      • 7.5.4 本地化
      • 7.5.5 处理器
      • 7.5.6 过滤器
      • 7.5.7 格式化器
      • 7.5.8 日志记录说明
    • 7.6 调试技巧

第七章

一个程序难免会出现错误,所以务必做到以下几点:

  • 向用户通告错误;
  • 保存所有的工作结果;
  • 允许用户以妥善的形式退出程序。

异常的作用:对于异常情况, 例如,可能造成程序崩溃的错误输入,Java使用一种称为异 常处理( exception handing) 的错误捕获机制处理。

断言的作用:在测试期间, 需要进行大量的检测以验证程序操作的正确性。 然而,这些检测可能非常耗时,在测试完成后也不必保留它们,因此,可以将这些检测删掉, 并在其他测试需要时将它们粘贴回来,这是一件很乏味的事情。本章的第 2 部分将介绍如何使用断言来有选择地启用检测。

日志的作用:当程序出现错误时,并不总是能够与用户或终端进行沟通。此时,可能希望记录下出现的问题,以备日后进行分析。

7.1 处理错误

用户期望在出现错误时, 程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成, 程序应该:

  • 返回到一种安全状态,并能够让用户执行一些其他的命令;或者
  • 允许用户保存所有操作的结果,并以妥善的方式终止程序。

异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。需要关注的问题:

  1. 用户输入错误
  2. 设备错误
  3. 物理限制(磁盘满了)
  4. 代码错误

对于方法中的一个错误,传统的做法是返回一个特殊的错误码,遗憾的是,并不是在任何情况下都能够返回一个错误码。有可能无法明确地将有效数据
与无效数据加以区分。一个返回整型的方法就不能简单地通过返回-1 表示错误,因为-1很可能是一个完全合法的结果。

7.1.1 异常分类

在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例。
阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))_第1张图片
需要注意的是,所有的异常都是由 Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception。
Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。程序员也无能为力。
在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常
派生于RuntimeException的异常包含下面几种情况:

  • 错误的类型转换
  • 数组访问越界
  • 数组访问越界

不是派生于 RuntimeException 的异常包括:

  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找 Class 对象, 而这个字符串表示的类并不存在

如果出现 RuntimeException异常, 那么就一定是你的问题” 是一条相当有道理的规则。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查( unchecked ) 异常,所有其他的异常称为受查(checked) 异常。

7.1.2 声明受查异常

如果遇到了无法处理的情况, 那么 Java 的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误
在自己编写方法时, 不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用 throws 子句声明异常, 什么异常必须使用 throws 子句声明, 需要记住在遇到下面 4 种情况时应该抛出异常:

  1. 调用一个抛出受查异常的方法, 例如, FilelnputStream 构造器。
  2. 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常
  3. 程序出现错误例如a[-1]=0会抛出一个ArraylndexOutOffloundsException 这样的非受查异常
  4. Java 虚拟机和运行时库出现的内部错误

如果没有处理器捕获这个异常,当前执行的线程就会结束。

对于那些可能被他人使用的 Java 方法, 应该根据异常规范( exception specification), 在方法的首部声明这个方法可能抛出的异常
如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。

总之,一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制( Error),要么就应该避免发生( RuntimeException)。如果方法没有声明所有可能发生的受查异常, 编译器就会发出一个错误消息。

如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方法中声明的异常更通用 (也就是说, 子类方法中可以抛出更特定的异常, 或者根本不抛出任何异常)。特别需要说明的是, 如果超类方法没有抛出任何受查异常, 子类也不能抛出任何受查异常。例如, 如果覆盖 JComponent.paintComponent 方法, 由于超类中这个方法没有抛出任何异常,所以, 自定义的 paintComponent 也不能抛出任何受查异常。

7.1.3 如何抛出异常

首先要决定应该抛出什么类型的异常。
EOFException 异常描述的是“ 在输人过程中, 遇到了一个未预期的 EOF 后的信号”。
EOFException 类还有一个含有一个字符串型参数的构造器。 这个构造器可以更加细致的描述异常出现的情况。

String gripe = "Content-length: " + len + ", Received: " + n;
throw new EOFException(gripe);

对于一个已经存在的异常类, 将其抛出非常容易,在这种情况下:

  1. 找到一个合适的异常类。
  2. 创建这个类的一个对象。
  3. 将对象抛出。

7.1.4 创建类异常

在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。 在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception的类或者派生于 Exception 子类的类。定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息的构造器。
例如:

class FileFormatException extends IOException
{
	public FileFormatException() {}
	public FileFormatException(String gripe) {
		super(gripe);
	} 
}

//就可以自己定义异常了
if (condition)
throw new FileFornatException();//throw new FileFornatException(String);

7.2 捕获异常

7.2.1 捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息, 其中包括异常的类型和堆栈的内容。
要想捕获一个异常, 必须设置 try/catch语句块。

try{
	code
	more code
}
catch (ExceptionType e) {
	handler for this type
}

如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类, 那么:

  1. 程序将跳过 try语句块的其余代码
  2. 程序将执行 catch 子句中的处理器代码

如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句。
通常, 应该捕获那些知道如何处理的异常, 而将那些不知道怎样处理的异常继续进行传递。如果想传递一个异常, 就必须在方法的首部添加一个 throws 说明符, 以便告知调用者这个方法可能会抛出异常。
如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如JComponent 中的 paintComponent ), 那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。

7.2.2 捕获多个异常

在一个 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赋值。

7.2.3 再次抛出异常与异常链

在 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();

强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节
如果在一个方法中发生了一个受查异常, 而不允许抛出它, 那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常。

7.2.4 finally子句

当代码抛出一个异常时, 就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
Java 有一种更好的解决方案,这就是 finally 子句。
不管是否有异常被捕获,finally 子句中的代码都被执行。

finally 子句执行情况:

  1. 代码没有抛出异常。 在这种情况下, 程序首先执行 try 语句块中的全部代码,然后执行 finally 子句中的代码。随后, 继续执行 try 语句块之后的第一条语句。正常执行到底
  2. 抛出一个在 catch 子句中捕获的异常。在这种情况下,程序将执行 try语句块中的所有代码,直到发生异常为止。此时,将跳过 try语句块中的剩余代码,转去执行与该异常匹配的 catch 子句中的代码, 最后执行 finally 子句中的代码。1.如果 catch 子句没有抛出异常,程序将执行finally 语句块之后的第一条语句,正常执行到底 。2.如果 catch 子句抛出了一个异常, 异常将被抛回这个方法的调用者,执行到finally
  3. 代码抛出了一个异常, 但这个异常不是由 catch 子句捕获的。在这种情况下,程序将执行 try 语句块中的所有语句,直到有异常被抛出为止。此时, 将跳过 try 语句块中的剩余代码, 然后执行 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子句里面。

7.2.5 try-with-Resource语句

假设资源属于一个实现了 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修饰的。

7.2.6 分析堆栈轨迹元素

堆栈轨迹( 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

有出处

7.3 使用异常机制的技巧

  1. 异常处理不能代替简单的测试,异常的基本规则是:只在异常情况下使用异常机制。

  2. .不要过分地细化异常,有必要将整个任务包装在一个 try语句块,使用多个catch处理。

  3. 利用异常层次结构。
    不要只抛出 RuntimeException 异常。应该寻找更加适当的子类或创建自己的异常类。
    不要只捕获 Thowable 异常, 否则,会使程序代码更难读、 更难维护。

  4. 不要压制异常。发生异常的频数很低很低很低,可以再catch块中什么也不写。

  5. 在检测错误时,“ 苛刻 ” 要比放任更好,例如栈空的时候,抛出一个 EmptyStackException异常要比在后面抛出一个 NullPointerException 异常更好。

  6. 不要羞于传递异常。让高层次的方法通知用户发生了错误, 或者放弃不成功的命令更加适宜。规则 5、6 可以归纳为“早抛出,晚捕获

7.4 使用断言

在一个具有自我保护能力的程序中, 断言很常用。

7.4.1 断言的概念

断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。
Java 语言引人了关键字 assert。这个关键字有两种形式:

assert 条件;//第一种
assert 条件:表达式;//第二种

这两种形式都会对条件进行检测, 如果结果为 false, 则抛出一个 AssertionError 异常。在第二种形式中,表达式将被传人 AssertionError 的构造器, 并转换成一个消息字符串。
要想断言x是一个非负数值, 只需要简单地使用下面这条语句

assert x >= 0;

或者将 x 的实际值传递给 AssertionError 对象, 从而可以在后面显示出来。

assert x >= 0 : x;

7.4.2 启用和禁用断言

在默认情况下, 断言被禁用。可以在运行程序时用 -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 开关不能应用到那些没有类加载器的“ 系统类” 上。在程序中也可以控制类加载器的断言状态。

7.4.3 使用断言完成参数检查

在 Java 语言中, 给出了 3 种处理系统错误的机制:

  • 抛出一个异常
  • 日志
  • 使用断言
    什么时候应该选择使用断言呢? 请记住下面几点:
  • 断言失败是致命的、 不可恢复的错误。
  • 断言检查只用于开发和测阶段(这种做法有时候被戏称为“ 在靠近海岸时穿上救生衣,但在海中央时就把救生衣抛掉吧”)。

因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。

由于可以使用断言,当方法被非法调用时, 将会出现难以预料的结果。有时候会拋出一个断言错误, 有时候会产生一个null指针异常, 这完全取决于类加载器的配置。

7.4.4 为文档假设使用断言

断言是一种测试和调试阶段所使用的战术性工具; 而日志记录是一种在程序的整个生命周期都可以使用的策略性工具。

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;

7.5 记录日志

记录日志API的优点:

  • 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
  • 可以很简单地禁止日志记录的输出, 因此,将这些日志代码留在程序中的开销很小。
  • 日志记录可以被定向到不同的处理器, 用于在控制台中显示, 用于存储在文件中等。
  • 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项。
  • 日志记录可以采用不同的方式格式化,例如,纯文本或 XML。
  • 应用程序可以使用多个日志记录器, 它们使用类似包名的这种具有层次结构的名字,例如, com.mycompany.myapp。
  • 在默认情况下,日志系统的配置由配置文件控制。如果需要的话, 应用程序可以替换这个配置。

本章的日志API指的是标准的Java日志框架,在很多用途方面,这个框架已经足够好,而且学习这个框架的API可以给理解其他框架做准备。例如常用的日志框架:log4j、Logback

7.5.1 基本日志

要生成简单的日志记录,可以使用全局日志记录器(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);

将会取消所有的日志。

7.5.2 高级日志

在一个专业的应用程序中,不要将所有的日志都记录到一个全局日志记录器中,而是可以自定义日志记录器。
可以调用 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 个日志记录器级别:

  • SEVERE
  • WARING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST
    在默认情况下,只记录前三个级别。 也可以设置其他的级別。例如,
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开始才显式的控制台上。

  1. 找到logging.properties文件,java在\jre\lib目录下。
  2. 在运行时加上-D参数
  3. 将打印级别设置为Level.FINER

输出:
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)

7.5.3 修改日志管理器配置

可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于: 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 启动应用程序不会对日志记录器产生任何影响 。

7.5.4 本地化

举个例子来说就是:某个资源包可能将字符串“ readingFile” 映射成英
文的 “ Reading file” 或者德文的“ Achtung! Datei wird eingelesen”。

7.5.5 处理器

在默认情况下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文档。
阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))_第2张图片
可以通过设置 H 志管理器配置文件中的不同参数(请参看表 7-1 ,) 或者利用其他的构造器 (请参看本节后面给出的 API注释)来修改文件处理器的默认行为。
阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))_第3张图片
也冇可能不想使用默认的日志记录文件名, 因此, 应该使用另一种模式, 例如, %h/myapp.log ( 有关模式变量的解释请参看表 7-2 )。
阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))_第4张图片
如果多个应用程序 (或者同一个应用程序的多个副本)使用同一个口志文件, 就应该开启 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();
    }

输出:
阅读Java核心技术Ⅰ的笔记(Java基础、第七章、异常、断言、日志(详细))_第5张图片
使用这种方式只存在一个问题, 这就是处理器会缓存记录, 并且只有在缓存满的吋候才将它们写人流中, 因此, 需要覆盖 publish 方法, 以便在处理器获得每个记录之后刷新缓冲区。

如果希望编写更加复杂的流处理器,就应该扩展 Handler 类, 并自定义 publish、 flush 和close 方法。

7.5.6 过滤器

在默认情况下, 过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。另外,可以通过实现 Filter 接口并定义下列方法来自定义过滤器。

boolean isLoggable(LogRecord record)

要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用 setFilter 方法就可以了。 注意,同一时刻最多只能有一个过滤器。

7.5.7 格式化器

ConsoleHandler 类和 FileHandler 类可以生成文本和 XML 格式的日志记录。但是, 也可以自定义格式。这需要扩展 Formatter 类并覆盖下面这个方法:

String format(LogRecord record);

很多文件格式(如 XML) 需要在已格式化的记录的前后加上一个头部和尾部在这个例子中,要覆盖下面两个方法:

String getHead (Handler h)
String getTail (Handler h)

最后,调用 setFormatter 方法将格式化器安装到处理器中。

7.5.8 日志记录说明

面对日志记录如此之多的可选项, 很容易让人忘记最基本的东西。下面的“ 日志说明书”总结了一些最常用的操作。

  1. 为一个简单的应用程序, 选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字,例如,com.mycompany.myprog, 这是一种好的编程习惯。 另外,可以通过调用下列方法得到日志记录器。Logger logger = Logger.getLogger("com.mycompany.myprog");为了方便起见,可能希望利用一些日志操作将下面的静态域添加到类中:private static final Logger logger = Logger.getLogger("com.mycompany.nyprog");
  2. 默认的日志配置将级别等于或高于 INFO 级别的所有消息记录到控制台。用户可以覆盖默认的配置文件。但是正如前面所述,改变配置需要做相当多的工作。因此,最好在应用程序中安装一个更加适宜的默认配置。自己用代码构造一个处理器,用文件的好处在于不用修改代码。
  3. 现在,可以记录自己想要的内容了。但需要牢记:所有级别为 INFO、 WARNING 和SEVERE 的消息都将显示到控制台上。 因此, 最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为 FINE 是一个很好的选择。

7.6 调试技巧

多查看日志信息和控制台输出,断点F7 F8 F9你值得拥有!

你可能感兴趣的:(java基础)