Java核心技术卷一 5. java异常断言和日志
Posted lovezyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java核心技术卷一 5. java异常断言和日志相关的知识,希望对你有一定的参考价值。
处理错误
由于出现错误而使得某些操作没有完成,程序因该:
- 返回到一种安全状态,并能够让用户执行一些其他命令
- 允许用户保存所有操作的结果,并以适当的方式终止程序
需要关注的问题:
- 用户输入错误
- 设备错误
- 物理限制
- 代码错误
当某个方法不能够采用正常的路径完成它的任务,就可以通过另外一个一个路径退出方法。这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。要注意这个方法将会立刻退出,并不返回任何值。调用这个方法的代码也将无法继续执行,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)。
异常分类
全都派生于 Throwable 类,分解为两个分支层次结构:Error 和 Exception
- Error 类层次结构描述 Java 运行时系统的内部错误和资源耗尽错误。应用程序不该抛出这种错误。
- Exception 类层次结构分为两个分支。
- 派生于 RuntimeException 异常,由程序错误导致的异常。
- 其他不是程序导致的异常。
派生于 RuntimeException 异常的几种情况:
- 错误的类型转换
- 数组访问越界,
ArrayIndexOutOfBoundsException
异常 - 访问空指针,
NullPointerException
异常
其他异常的几种情况:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在
Java 将派生于 Error 类或 RuntieException 类额所有异常成为非受查异常,其他的异常成为受查异常。
声明受查异常
一个方法不仅要告诉编译器要返回什么值,还要告诉编译器可能发生什么错误。
例子:
public FileInputStream(String name) throws FileNotFoundException {
...
}
这个声明表示可能抛出异常,如果发生了这个异常构造器将不会初始化一个新的 FileInputStream 对象,而是抛出一个 FileNotFoundException 类对象。抛出异常类对象后,运行时系统就会搜索异常处理器,以便指定如何处理 FileNotFoundException 对象。
需要抛出异常的情况:
- 调用一个抛出受查异常的方法。
- 程序运行过程中发现错误,利用 throw 语句抛出一个受查异常。
- 程序出现错误,一些非受查对象。
- Java 虚拟机和运行时库出现的内部错误。
前两种情况,必须告诉调用这个方法的程序员有可能抛出异常。防止程序遇到异常停止线程。
根据异常规范在方法首部声明这个方法可能抛出的异常,多个受查异常用逗号隔开:
class MyAnimation{
...
public Image loadImage(String s) throws IOException, FileNotFoundException {
...
}
}
如果方法没有声明受查异常,编译器会发出一个错误信息。
除了声明异常外,还可以捕获异常,抛出让异常处理器处理。
另外:声明一个异常,可能抛出这个异常的子类异常。
抛出异常
抛出异常的语句:
throw new EOFException();
String readData(Scanner in) throws EOFException {
...
while (...) {
if (!in.hasNext()) { //EOF encountered
if(n < len) throw new EOFException();
}
}
}
抛出以存在的异常类:
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
一旦方法抛出了异常,这个方法就不可能返回到调用者。
创建异常类
标准异常无法充分的描述清楚问题,可以创建自己的异常类:
class FileFormatException extends IOException {
public FileFormatException(){}
public FileFormatException(String gripe){
super(gripe);//构造一个带描述信息的异常
}
}
//抛出自己定义的异常类型
String readDate(BufferedReader in) throws FileFormatException {
...
while (...) {
if (ch == -1) { //EOF encounteered
if (n < len) throw new FileFormatException();
}
...
}
return s;
}
api:
//java.lang.Throwable
Throwable() 构造一个 Throwable 对象,没有描述信息。
Throwable(String message) 构造一个 Throwable 对象,带描述信息。
String getMessage() 获取描述信息。
# 捕获异常
捕获异常
异常发生,没有捕获,程序就会终止执行,在控制台打印异常信息。
捕获一行,必须设置 try/catch 语句块:
try {
code
more code
} catch (ExceptionType e) {
handler for this type
}
如果 try 内有代码抛出了 catch 中定义的异常:
- 程序将跳过 try 语句块的其余代码。
- 程序将执行 catch 子句中的处理器代码。
注意:如果 try 内的异常 catch 中没有,则程序终止。
将异常传递给调用者:
public void read(String filename) throws IOException {...}
将不知道怎样处理的异常继续进行传递,传递异常使用 throws 说明符,告知调用者这个方法可能出现异常。
例外:如果编写一个覆盖超类的方法,这个方法没有抛出异常,那么这个方法必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。
捕获多个异常
有两种方式,第一种方式为每个异常类使用单独的 catch 子句。
try {
code;
} catch (FileNotFoundException e) {
emergency action;
} catch (UnknownHostException e) {
emergency action;
} catch (IOException e) {
emergency action;
}
第二种方式可以对动机一样的异常进行合并 catch 子句。
try {
code;
} catch (FileNotFoundException | UnknownHostException | IOException e) {
emergency action;
}
获得对象的更多信息,可以:
e.getMessage();
e.getClass().getName();
注意:捕获多个异常时,异常变量隐含为 final 变量,不可修改。
再次抛出异常与异常链
有些异常我们并不想指定发送错误的细节原因,但希望明确的指定它是否有问题:
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();
这个受查异常,不允许抛出它,包装技术十分有用,可以捕获这个受查异常,把它包装成一个运行时异常。
finally 子句
不管是否有异常被捕获,finally 子句中的代码都被执行:
InputStream in = new FileInputStream(...);
try {
core 1;
core 2;
} catch (IOException e) {
core 3;
} finally {
core 4;
}
会遇到3种情况:
- 代码没有异常。执行完 try 块,然后执行 finally 块。
- 抛出一个异常。执行 try 块知道异常为止,跳过剩余 try 代码,转去执行与异常匹配的 catch 子句中的代码,最后执行 finally 子句中的代码。
- 代码抛出异常,但是 catch 没有匹配的异常。执行 try 块知道异常为止,跳过剩余 try 代码,转去执行 finally 子句中的代码,并将异常抛给这个方法的调用者。
解耦合try/catch
和try/finally
,提高代码的清晰度:
try {
try {
code;
} finally {
in.close();
}
} catch (IOException e) {
show error message;
}
内层确保关闭输入流;外层确保报告出现的错误。外层也会报告 finally 子句中出现的错误。
return的各种场景:
- return 在 try 块中时,方法返回前 finally 子句的内容将被执行。
- return 在 try 块 finally 子句中都存在时,finally 子句中的返回值将会覆盖原始的返回值。
finally子句的坏处
try {
code 1;
} finally {
in.close();
}
如果 try 语句抛出了一些非 IOEception 的异常,这些异常只有调用者才能处理。执行 finally 语句块,并调用 close 方法,有可能抛出 IOException 异常。有这种情况时,原始的异常将会丢失,转而抛出 close 方法的异常。
带资源的 try 语句
资源属于实现了 AutoCloseable 接口的类时:
//接口的一个方法
void close() throws Exception
//最简形式
try (Resource res = ...) {
work with res;
}
try 块退出时,会自动调用 res.close() 。并且可以指定多个资源。
分析堆栈轨迹元素
堆栈轨迹元素是一个方法调用过程的列表,包含了程序执行过程中方法调用的特定位置。
调用 Throwable 类的 printStackTrace 方法访问堆栈轨迹的文本描述信息。
使用异常机制的技巧
- 异常处理不能代替简单的测试
- 不要过分地细化异常
- 利用异常层次结构
- 不要压制异常
- 在检测错误时,“苛刻”要比放任更好
- 不要羞于传递异常
使用断言
断言的概念
断言机制允许在测试期间向代码插入一些检查语句。当代码发布时,这些插入的检测语句会被自动地移走。
assert 条件;
和
assert 条件 : 表达式;
这两种形式都会对条件进行检测, 如果结果为 false,则抛出一个 AssertionError 异常。 在第二种形式中,表达式将被传人 AssertionError 的构造器,并转换成一个消息字符串。 表达式部分的目的是产生一个消息字符串。
例子,断言 x 是一个非负数值:
assert x >= 0;
assert x >= 0 : x;
启用和禁用断言
启用:java -enableassertions MyApp
启用某个类中的断言:java -ea:MyClass -ea:com.mycompany.mylib... MyApp
禁用特定类和包的断言:java -ea:... -da:MyClass MyApp
不能应用没有类加载器的系统类上,要使用:-enablesystemassertions/-esa
使用断言完成参数检查
使用断言的场景:
- 断言失败是致命的、不可恢复的错误
- 断言用于开发和测试阶段
不可通告可恢复性的错误,不该作为程序向用户通告问题的手段。
不允许用 null 数组调用这个方法,并在这个方法的开头使用断言:assert a != null;
为文档假设使用断言
if (i % 3 == 0) ...
else if (i % 3 == 1) ...
else //i % 3 ==2
assert i >= 0;
if (i % 3 == 0) ...
else if (i % 3 == 1) ...
else assert i % 3 ==2;
记录日志
基本日志
使用全局日志记录器并调用 info 方法:
Logger.getGlobal().info("File->Open menu item selected");
内容:
May 10, 2013 10:23:43 PM LoggingImageViewer fileOpen
INFO: File->Open menu item selected
在适当的地方(如 main 开始)以下将会取消所有日志:
Logger.getGlobal().setLevel(Level.OFF);
调试技巧
- 打印任意变量的值:
System.out.println("x=" + x);
- 在每个类中放置一个单独的 main 方法,做单元测试。
- 可以使用 JUnit 单元测试框架。
以上是关于Java核心技术卷一 5. java异常断言和日志的主要内容,如果未能解决你的问题,请参考以下文章