七、异常、断言和日志
(1)受检查的异常:这种在编译时被强制检查的异常称为"受检查的异常"。即在方法的声明中声明的异常。
(2)不受检查的异常:在方法的声明中没有声明,但在方法的运行过程中发生的各种异常被称为"不被检查的异常"。这种异常是错误,会被自动捕获。
7.1 处理错误
- 异常层次结构
- RuntimeException 派生包括
- 错误的类型转换(ClassCastException等)
- 数组访问越界(ArrayIndexOutOfBoundsException等)
- 访问 null 指针(NullPointerException等)
- 非 RuntimeException 派生包括
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class 对象, 而这个字符串表示的类并不存在
- EOFException(未预期的EOF异常)
7.2 捕获异常
-
捕获多个异常时, 异常变量隐含为final 变量。例如, 不能在以下子句体中为e 赋不同的值:
catch (FileNotFoundException | UnknownHostException e) { ... }
-
捕获异常并将它再次抛出
try { // access the database } catch (SQLException e) { Throwable se = new ServletException("database error"); se.initCause(e); throw se; }
捕获到异常时, 就可以使用下面这条语句重新得到原始异常
Throwable e = se.getCause(); // 让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
-
finally 子句
不管是否有异常被捕获, finally 子句中的代码都被执行
- 带资源的try 语句
-
try 自动关闭资源条件
-
实现了 AutoCloseable 接口的类
void close() throws Exception
-
实现了 Closeable 接口的类,这是AutoCloseable 的子接口, 也包含一个close方法,但是抛出 IOException
void close() throws IOException
-
-
带资源的try 语句(try-with-resources ) 的最简形式为
try (Resource res = ...) { // work with res }
try 块退出时,会自动调用res.dose()
7.3 使用异常机制的技巧
-
异常处理不能代替简单的测试,只在异常情况下使用异常机制
-
不要过分地细化异常
-
利用异常层次结构
-
不要压制异常
-
在检测错误时,“ 苛刻” 要比放任更好
-
不要羞于传递异常
早抛出, 晚捕获
7.4 断言
-
关键词,这两种形式都会对条件进行检测,
assert 条件;
如果结果为false,则抛出一个AssertionError 异常assert 条件:表达式;
如果结果为false,表达式将被传人AssertionError 的构造器, 并转换成一个消息字符串。表达式” 部分的唯一目的是产生一个消息字符串
-
启用和禁用断言
// 在默认情况下, 断言被禁用。可以在运行程序时用 -enableassertions 或 -ea 选项启用: java -enableassertions MyApp //需要注意的是, 在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器 (class loader) 的功能。当断言被禁用时, 类加载器将跳过断言代码, 因此,不会降低程序运行的速度。
-
Java 的 3 种处理系统错误的机制
- 抛出一个异常
- 日志
- 使用断言
-
断言的使用
- 断言失败是致命的、不可恢复的错误。
- 断言检查只用于开发和测阶段(这种做法有时候被戏称为“ 在靠近海岸时穿上救生衣,但在海中央时就把救生衣抛掉吧”)。
注意
断言是一种测试和调试阶段所使用的战术性工具;
而日志记录是一种在程序的整个生命周期都可以使用的策略性工具。
7.5 记录曰志
-
基本曰志
-
要生成简单的日志记录,可以使用全局日志记录器(global logger) 并调用其info 方法
Logger.getClobal(),info("File->Open menu item selected"); // 调用以下方法取消所有日志 Logger.getClobal().setLevel(Level.OFF) ;
-
-
高级曰志
-
调用getLogger 方法创建或获取记录
private static final Logger logger = Logger.getLogger("com.mycompany.myapp"); // 未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,要像上面的例子中一样, 用一个静态变量存储日志记录器的一个引用。 SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST // 设置最低日志等级
-
使用日志如下(默认的日志配置记录了INFO 或更高级别的所有记录)
logger.fine(message); logger.warning(message); logger.setLevel(Level.FINE); logger.log(Level.FINE, message);
-
logp方法可以获得调用类和方法的确切位置
void logp(Level 1, String className, String methodName, String message)
-
跟踪执行流
void entering(String className, 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)
-
日志记录中包含的异常描述内容
void throwing(St ring className, String methodName, Throwable t) void log(Level level, String message, Throwable t)
-
-
处理器
-
日志管理器配置文件设置的默认控制台处理器的日志记录级别为
java.uti1.1ogging.ConsoleHandler.level=INF0
-
要想记录FINE 级别的日志,就必须修改配置文件中的默认日志记录级别和处理器级别。另外,还可以绕过配置文件,安装自己的处理器。
Logger logger = Logger.getLogger("com.mycompany.myapp"); logger.setLevel(Level.FINE); 1ogger.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler,setLevel(Level.FINE); 1ogger.addHandler(hander);
举例:**SocketHandler ** 将记录发送到特定的主机和端口。**FileHandler ** 可以收集文件中的记录。
-
文件处理器配置参数
- 日志记录文件模式变量
-
-
过滤器
-
实现niter 接口并定义下列方法来自定义过滤器
boolean isLoggab1e(LogRecord record)
-
-
格式化器
-
ConsoleHandler 类和FileHandler 类可以生成文本和XML 格式的日志记录。
-
自定义格式需要扩展Formatter 类并覆盖下面这个方法
String format(LogRecord record) // 下述方法对记录中的部分消息进行格式化、参数替换和本地化应用操作。 String formatMessage(LogRecord record) // 很多文件格式(如XML) 需要在已格式化的记录的前后加上一个头部和尾 String getHead(Handler h) String getTail(Handler h)
-
-
日志记录说明
- 为一个简单的应用程序, 选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字
- 默认的日志配置将级别等于或高于INFO 级别的所有消息记录到控制台。用户可以覆盖默认的配置文件。但是正如前面所述, 改变配置需要做相当多的工作。因此,最好在应用程序中安装一个更加适宜的默认配置。
- 现在,可以记录自己想要的内容了。但需要牢记: 所有级别为INFO、WARNING 和SEVERE 的消息都将显示到控制台上。因此, 最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为FINE 是一个很好的选择。
API注释
7.1.4 创建异常类
javaJang.Throwabie 1.0
Throwable()
// 构造一个新的Throwabie 对象, 这个对象没有详细的描述信息。
Throwable(String message)
// 构造一个新的throwabie 对象, 这个对象带有特定的详细描述信息。习惯上, 所有派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。
String getMessage()
// 获得Throwabie 对象的详细描述信息。
7.2.6 分析堆栈轨迹元素
java.Iang.Throwable 1.0
Throwable(Throwable cause) 1.4
Throwable(String message, Throwable cause) 1.4
// 用给定的“ 原因” 构造一个Throwable 对象。
Throwable initCause(Throwable cause) 1.4
// 将这个对象设置为“ 原因”。如果这个对象已经被设置为“ 原因”, 则抛出一个异常。返回this 引用。
Throwable getCause() 1.4
// 获得设置为这个对象的“ 原因” 的异常对象。如果没有设置“ 原因”, 则返回null。
StackTraceElement[] getStackTrace() 1.4
// 获得构造这个对象时调用堆栈的跟踪。
void addSuppressed(Throwable t) 7
// 为这个异常增加一个“ 抑制” 异常。这出现在带资源的try 语句中, 其中t 是dose 方法抛出的一个异常。
Throwable[] getSuppressed() 7
// 得到这个异常的所有“ 抑制” 异常。一般来说,这些是带资源的try 语句中close 方法拋出的异常。
java.lang.Exception 1.0
Exception(Throwable cause) 1.4
Exception(String message, Throwable cause)
// 用给定的“ 原因” 构造一个异常对象。
java.lang.RuntimeException 1.0
RuntimeException(Throwable cause) 1.4
RuntimeException(String message, Throwable cause) 1.4
// 用给定的“ 原因” 构造一个RuntimeException 对象。
java.lang.StackTraceElement 1.4
String getFileName()
// 返回这个元素运行时对应的源文件名。如果这信息不存在, 则返回null。
int getLineNumber()
// 返回这个元素运行时对应的源文件行数。如果这个信息不存在,则返回-1。
String getClassName()
// 返回这个元素运行时对应的类的完全限定名。
String getMethodName()
// 返回这个元素运行时对应的方法名。构造器名是<init>;静态初始化器名是<clinit>;这里无法区分同名的重载方法。
boolean isNativeMethod()
// 如果这个元素运行时在一个本地方法中, 则返回true。
String toString()
// 如果存在的话, 返回一个包含类名、方法名、文件名和行数的格式化字符串。
7.4.4 为文档假设使用断言
java.Iang.ClassLoader 1.0
void setDefaultAssertionStatus(boolean b) 1.4
// 对于通过类加载器加载的所有类来说, 如果没有显式地说明类或包的断言状态, 就启用或禁用断言。
void setClassAssertionStatus(String className, boolean b) 1.4
// 对于给定的类和它的内部类,启用或禁用断言。
void setPackageAssertionStatus(String packageName, boolean b) 1.4
// 对于给定包和其子包中的所有类,启用或禁用断言。
void clearAssertionStatus() 1.4
// 移去所有类和包的显式断言状态设置, 并禁用所有通过这个类加载器加载的类的断言。
7.5.7 格式化器
java.util.logging.Logger 1.4
Logger getLogger(String 1oggerName)
Logger getLogger(String 1oggerName, String bund eName)
// 获得给定名字的日志记录器。如果这个日志记录器不存在, 创建一个日志记录器。
// 参数:loggerName 具有层次结构的日志记录器名。例如,com.mycompany.myapp
// bundleName 用来查看本地消息的资源包名
void severe(String message)
void warning(String message)
void info(String message)
void config(String message)
void fine(String message)
void finer(String message)
void finest(String message)
// 记录一个由方法名和给定消息指示级别的日志记录。
void entering(String className, String methodName)
void entering(String className, String methodName, Object param)
void entering(String className, String methodName, Object[] param)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
// 记录一个描述进人/ 退出方法的日志记录, 其中应该包括给定参数和返回值。
void throwing(String className, String methodName, Throwable t)
// 记录一个描述拋出给定异常对象的日志记录。
void log(Level level, String message)
void log(Level level, String message, Object obj)
void log(Level level, String message, Object[] objs)
void log(Level level, String message, Throwable t)
// 记录一个给定级别和消息的日志记录, 其中可以包括对象或者可抛出对象。要想包括对象, 消息中必须包含格式化占位符{0}、{1} 等。
void logp(Level level, String className, String methodName, String message)
void logp(Level level, String className, String methodName, String message, Object obj)
void logp(Level level, String className, String methodName, String message, Object[] objs)
void logp(Level level, String className, String methodName, String message, Throwable t)
// 记录一个给定级别、准确的调用者信息和消息的日志记录, 其中可以包括对象或可抛出对象。
void logrb(Level level, String className, String methodName, String message)
void logrb(Level level, String className, String methodName, String message, Object obj)
void logrb(Level level, String className, String methodName, String message, Object[] objs)
void logrb(Level level, String className, String methodName, String message, Throwable t)
// 记录一个给定级别、准确的调用者信息、资源包名和消息的日志记录, 其中可以包括对象或可拋出对象。
Level getLevel()
void setLevel(Level l)
// 获得和设置这个日志记录器的级别。
Logger getParent()
void setParent(Logger l)
// 获得和设置这个日志记录器的父日志记录器。
Handler[] getHandlers()
// 获得这个日志记录器的所有处理器。
void addHandler(Hand1er h)
void removeHandler(Handler h)
// 增加或删除这个日志记录器中的一个处理器。
boolean getUseParentHandlers()
void setUseParentHandlers(boolean b)
// 获得和设置“use parent handler”属性。如果这个属性是true,则日志记录器会将全部的日志记录转发给它的父处理器。
Filter getFi1ter()
void setFi1ter(FiIter f)
// 获得和设置这个日志记录器的过滤器。
java.util.logging.Handler 1.4
abstract void publish(LogRecord record)
// 将日志记录发送到希望的目的地。
abstract void f1ush()
// 刷新所有已缓冲的数据
abstract void close()
// 刷新所有已缓冲的数据, 并释放所有相关的资源。
Filter getFilter()
void setFilter(Filter f)
// 获得和设置这个处理器的过滤器。
Formatter getFormatter()
void setFormatter(Formatter f)
// 获得和设置这个处理器的格式化器。
Level getLevel()
void setLevel(Level l)
// 获得和设置这个处理器的级别。
java.util.logging.ConsoleHandler 1.4
ConsoleHandler( )
// 构造一个新的控制台处理器。
java.util.logging.FileHandler 1.4
FileHandler(String pattern)
FileHandler(String pattern, boolean append)
FileHandler(String pattern, int limit, int count)
FileHandler(String pattern, int limit, int count, boolean append)
// 构造一个文件处理器。
// 参数:pattern 构造日志文件名的模式。参见表7-2 列出的模式变量
// limit 在打开一个新日志文件之前, 日志文件可以包含的近似最大字节数
// count 循环序列的文件数量
// append 新构造的文件处理器对象应该追加在一个已存在的日志文件尾部,则为true
java.util.logging.LogRecord 1.4
Level getLevel()
// 获得这个日志记录的记录级别。
String getLoggerName()
// 获得正在记录这个日志记录的日志记录器的名字。
ResourceBundle getResourceBundle()
String getResourceBundleName()
// 获得用于本地化消息的资源包或资源包的名字。如果没有获得,则返回mill。
String getMessage()
// 获得本地化和格式化之前的原始消息。
Object[] getParameters()
// 获得参数对象。如果没有获得, 则返回null。
Throwable getThrown( )
// 获得被拋出的对象。如果不存在, 则返回null。
String getSourceClassName( )
String getSourceMethodName()
// 获得记录这个日志记录的代码区域。这个信息有可能是由日志记录代码提供的, 也有可能是自动从运行时堆栈推测出来的。如果日志记录代码提供的值有误, 或者运行时代码由于被优化而无法推测出确切的位置,这两个方法的返回值就有可能不准确。
long getMillis()
// 获得创建时间。以毫秒为单位( 从1970 年开始)。
long getSequenceNumber( )
// 获得这个日志记录的唯一序列序号。
int getThreadID()
// 获得创建这个日志记录的线程的唯一ID 这些ID 是由LogRecord 类分配的, 并且与其他线程的ID 无关。
java.util.logging.Filter 1.4
boolean isLoggable(LogRecord record )
// 如果给定日志记录需要记录, 则返回true。
java.util.logging.Formatter 1.4
abstract String format(LogRecord record)
// 返回对日志记录格式化后得到的字符串。
String getHead(Handler h)
String getTail(Handler h)
// 返回应该出现在包含日志记录的文档的开头和结尾的字符串。超类Formatter 定义了这些方法, 它们只返回空字符串。如果必要的话, 可以对它们进行覆盖。
String formatMessage(LogRecord record)
// 返回经过本地化和格式化后的日志记录的消息内容。
评论区