核心技术:异常、断言和日志
错误处理
异常分类
异常对象都是派生于Throwable 类的一个实例:
- Error:类层次结构描述了Java 运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。
- Exception:
- RuntimeException:由程序错误导致的异常属于RuntimeException;
- 其他异常:由于像I/O 错误这类问题导致的异常;
派生于RuntimeException 的异常包含下面几种情况:
- 错误的类型转换。
- 数组访问越界i
- 访问null 指针
不是派生于RuntimeException 的异常包括:
- 试图在文件尾部后面读取数据。
- 试图打开一个不存在的文件。
- 试图根据给定的字符串查找Class 对象, 而这个字符串表示的类并不存在。
非受查异常 与 受查异常:
- 非受查(unchecked) :异常派生于Error 类或RuntimeException 类的所有异常称为;
- 受查(checked) :所有其他的异常称为。
声明受査异常
使用throws子句声明异常:
- 调用一个抛出受査异常的方法, 例如, FilelnputStream 构造器。
- 程序运行过程中发现错误, 并且利用throw 语句抛出一个受查异常(下一节将详细地介绍throw 语句)。
- 程序出现错误, 例如,a[-l]=0 会抛出一个ArraylndexOutOffloundsException 这样的非受查异常。
- Java 虚拟机和运行时库出现的内部错误。
- 如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常类。
class MyAnimation
{
public Image loadlmage(String s) throws FileNotFoundException, EOFException
}
- 如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方法中声明的异常更通用。
如何抛出异常
对于一个已经存在的异常类, 将其抛出非常容易:
- 找到一个合适的异常类。
- 创建这个类的一个对象。
- 将对象抛出。
- 抛出的时异常类对象;
throw new EOFExceptionQ ;
- 一旦方法抛出了异常, 这个方法就不可能返回到调用者。
创建异常类
定义一个派生于 Exception 的类, 或者派生于 Exception 子类的类:
- 习惯上,定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息的构造器
class FileFormatException extends IOException
{
public FileFormatExceptionO {}
public FileFormatException(String gripe)
{
super(gripe);
}
}
相关方法
javaJang.Throwabie
- Throwable( )
构造一个新的Throwabie 对象, 这个对象没有详细的描述信息。
- Throwable(String message )
构造一个新的throwabie 对象, 这个对象带有特定的详细描述信息。习惯上, 所有派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。
- String getMessage( )
获得Throwabie 对象的详细描述信息。
捕获异常
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
}
finally
{
...
}
捕获异常(try-catch finally)
- 如果在try 语句块中的任何代码抛出了一个在catch 子句中说明的异常类:
- 程序将跳过try 语句块的其余代码。
- 程序将执行catch 子句中的处理器代码。
- 如果在try 语句块中的代码没有拋出任何异常,那么程序将跳过catch 子句。
- finally 子句不是必须的。
- 不允许在子类的throws 说明符中出现超过超类方法所列出的异常类范围。
捕获多个异常
- 假设两个异常的动作是一样的(且不是子类关系时),就可以合并catch 子句:
catch (FileNotFoundException | UnknownHostException e) { emergency action for missing files and unknown hosts }
- 捕获多个异常时, 异常变量隐含为final变量
再次抛出异常与异常链
再次抛出异常:
try
{
// access the database
}
catch (SQLException e)
{
throw new ServletException("database error: " + e.getMessageO) ;
}
异常链:
- 包装异常,可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节
try
{
// access the database
}
catch (SQLException e)
{
Throwable se = new ServletException ("database error");
se.initCause(e);
throw se;
}
// 捕获异常, 获取原始异常:
Throwable e = se.getCauseO ;
finally 子句
无论在try 语句块中是否遇到异常, finally 子句中语句都会被执行:
- 代码没有抛出异常。
- 抛出一个在catch 子句中捕获的异常。
- 如果catch 子句没有抛出异常, 程序将执行try 语句块之后的第一条语句。
- 如果catch 子句抛出了一个异常, 异常将被抛回这个方法的调用者。
- 代码抛出了一个异常, 但这个异常不是由catch 子句捕获的。
- try 语句可以只有finally 子句,而没有catch 子句。
- finally 中出现异常时,异常会覆盖原异常;
当finally 子句包含return 语句时:
- finally 块,会在 try 的 return之前执行;
- 如果finally 中也有 return,则其会覆盖 try中的 return;
public static int f(int n)
{
try
{
int r = n * n;
return r;
}
finally
{
if (n = 2) return 0;
}
}
带资源的 try 语句
- 资源必须属于一个实现了 AutoCloseable 接口的类
带资源的try 语句(try-with-resources ) 的最简形式为:
try (Resource res = . . .)
{
work with res
}
指定多个资源:
try (Scanner in = new Scanne「(new FileInputStream('7usr/share/dict/words"). "UTF-8");
PrintWriter out = new Pri ntWriter("out.txt"))
{
while (in.hasNext())
out.pri ntl n(in.next().toUpperCase()) ;
}
分析堆栈轨迹元素
堆栈轨迹(stack trace) 是一个方法调用过程的列表, 它包含了程序执行过程中方法调用的特定位置:
- Throwable 类的 printStackTrace 方法访问堆栈轨迹的文本描述信息
- getStackTrace 方法得到StackTraceElement 对象的一个数组,可以在你的程序中分析这个对象数组。
- 静态的 Thread.getAllStackTrace 方法, 它可以产生所有线程的堆栈轨迹。
使用异常机制的技巧
使用断言
断言的概念
断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测语句将会被自动地移走。
Java 语言引人了关键字assert。这个关键字有两种形式:
assert 条件;
和
assert 条件:表达式;
- 这两种形式都会对条件进行检测, 如果结果为false, 则抛出一个AssertionError 异常。
- 在第二种形式中,表达式将被传人AssertionError 的构造器, 并转换成一个消息字符串
- 表达式” 部分的唯一目的是产生一个消息字符串。
- AssertionError 对象并不存储表达式的值, 因此, 不可能在以后得到它。
启用和禁用断言
在默认情况下, 断言被禁用。可以在运行程序时用-enableassertions 或-ea 选项启用:
java -enableassertions MyApp
也可以在某个类或整个包中使用断言, 例如:
java -ea:MyClass -ea:com.mycompany.inylib... MyApp
也可以用选项-disableassertions 或-da 禁用某个特定类和包的断言:
java -ea:... -da:MyClass MyApp
- 在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器( class loader ) 的功能。
- 启用和禁用所有断言的-ea 和-da 开关不能应用到那些没有类加载器的“系统类”上。对于这些系统类来说,需要使用-enablesystemassertions/-esa 开关启用断言。
使用断言完成参数检查
在Java 语言中, 给出了3 种处理系统错误的机制:
- 抛出一个异常
- 日志
- 使用断言
什么时候应该选择使用断言呢?
- 断言失败是致命的、不可恢复的错误。
- 断言检查只用于开发和测阶段
为文档假设使用断言
记录日志
基本日志
要生成简单的日志记录,可以使用全局日志记录器(global logger) 并调用其info 方法:
Logger.getClobal().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") :
- 未被任何变量引用的日志记录器可能会被垃圾回收。所以,可以用一个静态变量存储日志记录器的一个引用。
通常, 有以下7 个日志记录器级别:(在默认情况下,只记录前3个级别)
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
- 使用“logger,setLevel (Level .FINE) ;”设置级别
- 还可以使用“Level.ALL” 开启所有级别的记录, 或者使用“Level.OFF” 关闭所有级别的记录。
修改日志管理器配置
默认情况下, 配置文件存在于:
jre/lib/1ogging.properties
就要将java.utiUogging.config.file 特性设置为配置文件的存储位置, 并用下列命令启动应用程序:
java -Djava.util.logging.config.file=configFileMainClass
- 日志管理器在VM 启动过程中初始化,在main 执行之前完成。
- 如果在main中调用“System.setProperty("java.util_logging.config_file",file)”,也会调用“LogManager.readConfiguration()”来重新初始化日志管理器
- 要想在控制台上看到FINE 级别的消息, 就需要进行下列设置:
java.util.logging.ConsoleHandler.level=FINE
本地化
处理器
在默认情况下t 日志记录器将记录发送到ConsoleHandler 中, 并由它输出到System.err流中。
- 与日志记录器一样, 处理器也有日志记录级别。
- 对于一个要被记录的日志记录,它的日志记录级别必须高于日志记录器和处理器的阈值。
日志管理器配置文件设置的默认控制台处理器的日志记录级别为
java.uti1.1ogging.ConsoleHandler.level =INF0