7 异常、断言和日志
在 Java 中,如果某个方法不能够采用正常的途径完整它的任务,就可以通过另外一个路径退出方法。
在这种情况下,将会立刻退出,并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。
此外,调用这个方法的代码也将无法继续执行,取而代之的是异常处理机制开始搜索能够处理这种异常状况的异常处理器。
7.1 处理错误
所有的异常都是由 Throwable 继承而来,并分为两个分支:Error
和Exception
。
Error 类继承链描述了 Java 运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。这种情况很少出现。
Exception 继承链分为两个分支:由程序错误导致的异常属于RuntimeException
;
而程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于其他异常
。
Java 语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。
编译器将核查是否为所有的受査异常提供了异常处理器。
一个方法必须声明所有可能抛出的受查异常
,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。
如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用。
如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
7.2 捕获异常
7.2.1 捕获一个异常
- try / catch 处理
- throws 传递出去
7.2.2 捕获多个异常
try {
code that might throw exceptions
}
catch (FileNotFoundException e) {
emergency action for missing files
}
catch (UnknownHostException e) {
emergency action for unknown hosts
}
catch (IOException e) {
emergency action for all other I/O problems
}
/// 第一个异常变量 e 隐含为 final 变量
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
}
7.2.3 再次抛出异常与异常链
可以在 catch 里再抛出异常,这样做的目的是改变异常的类型:
try {
access the database
}
catch (SQLException e) {
throw new ServletException("database error: " + e.getMessage());
}
更好的处理办法是将原始异常设置为新异常的“原因”:
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 子句
try 语句可以只有 finally 子句,而没有 catch 子句。
-
finally 执行的几种情况
-
try/catch 和 try/finally 解耦合。里面的 try 语句块只确保输入流被关闭;
外面的 try 语句块只确保报告出现的错误;同时也会报告 finally 子句中出现的错误。
InputStream in = . . .;
try {
try {
code that might throw exceptions
}
finally {
in.close();
}
}
catch (IOException e) {
show error message
}
7.2.5 带资源的 try 子句
try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"), "UTF-8");
PrintWriter out = new PrintWriter("out.txt"))
{
while (in.hasNext())
out.println(in.next().toUpperCase());
}
不论这个块如何退出,in 和 out 都会关闭,就好像使用了 finally 块一样。
之前,如果 try 块抛出一个异常,而且 close 方法也抛出一个异常,这就会带来一个难题。带资源的 try 语句可以很好地处理这种情况。
原来的异常会重新抛出,而 close 方法抛出的异常会“被抑制”。这些异常将自动捕获,并由 addSuppressed 方法增加到原来的异常。
如果对这些异常感兴趣,可以调用 getSuppressed 方法,它会得到从 close 方法抛出并被抑制的异常列表。
7.2.6 分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用
的特定位置。
Throwable t = new Throwable();
t.printStackTrace();
StackTraceElement[] frames = t.getStackTrace();
///
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Thread t : map.keySet())
{
StackTraceElement[] frames = map.get(t);
analyze frames
}
7.3 使用异常机制的tips(略)
7.4 使用断言(暂略)
7.5 记录日志
logging API 解决频繁插入/删除 System.out.println 的问题。
以下仅介绍简单使用:
// Logger log = Logger.getLogger("name");
Logger global = Logger.getGlobal();
global.info("this is a message");
global.setLevel(Level.OFF);
通常有7个日志级别:
SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST
每个级别有对应的记录方法:
logger.warning("message");
logger.fine("message");
...
logger.log(Level.WARNING, "message");
logger.log(Level.FINE, "message");
...
设置日志对象记录的级别。默认日志级别为 INFO,可以记录 INFO 与更高的两个级别的日志。
logger.setLevel(Level.FINE);
...
logger.setLevel(Level.ALL); //开启所有级别的记录
logger.setLevel(Level.OFF); //关闭所有级别的记录
更高级的日志使用暂略。常用日志框架有
Commons-logging
、Slf4j
和log4j
等。
7.6 调试技巧(暂略)
这部分以后可能会补上