Java笔记_10
异常
- 所有的异常都是由
Throwable继承而来, 下一层有两个分支,Error和ExceptionError类描述了Java运行时系统的内部错误和资源耗尽问题, 一般不要抛出这个类型Exception层次分为两个分支,RuntimeException和其他异常- 编程错误导致的是
RuntimeException - 由于
IO错误导致的是其他异常 - 继承自
RuntimeException异常包括: 错误的强制类型转换; 数组越界; 访问null指针 - 不继承自
RuntimeException异常包括: 打开不存在的文件; 越过文件末尾读取数据; 根据给定字符串查找Class, 但是这个类并不存在
- 编程错误导致的是
- 所有派生于
Error和RuntimeException的异常是非检查型异常, 其他异常都是检查型异常
抛出异常的情况
- 调用了某个会抛出异常的方法, 比如
FileInputStream构造器 - 检测到一个错误, 使用
throw语句抛出异常 - 程序出现错误, 比如
a[-1]抛出一个非检查型异常 JVM内部错误
- 一个方法必须声明所有可能抛出的检查型异常, 也可以捕获一个异常, 这样也不需要抛出了
- 如果子类覆盖了超类的一个方法, 子类抛出的异常不能比超类的更通用, 如果超类没有抛出异常, 则子类也不能抛出异常
创建异常
- 定义一个派生于
Exception或者他子类的一个类, 包含两个构造器, 一个是无参构造器, 另一个是包含详细信息的构造器1
2
3
4
5
6class FileFormatException extends IOException {
public FileFormatException(){};
public FileFormatException(String gripe) {
super(gripe);
}
}
捕获异常
- 如果
try语句块中任何代码抛出了catch指定的一个异常类- 跳过
try语句块剩余执行内容 - 执行
catch语句块代码
- 跳过
- 如果没有抛出异常, 则直接跳过
catch部分 - 如果抛出了异常, 但是不在
catch中, 则方法会直接退出 - 一般是捕获知道如何处理的异常, 抛出不知道如何处理的异常
- 一个
try语句块可能抛出多种不同的异常, 每个异常需要一个catch语句块- 如果两个异常的捕获动作一样的话, 可以使用
catch(Exception e1 | Exception e2)合并 - 捕获多个变量时, 异常变量银行了
final
- 如果两个异常的捕获动作一样的话, 可以使用
再次抛出
- 有时候只想记录一个异常, 再次抛出
1
2
3
4
5
6
7
8
9
10try {
...
} catch (Exception e) {
logger.log(level, message, e);
throw e;
}
// 如果这段代码存在于 public void update() throws SQLException中
// 在Java 7之前会报错, 因为 throw e可能抛出其他类型的异常, 而不是SQLException
// 现在改变了, 编译器会跟踪到e来自try代码块, try代码块中仅有的检查型异常是SQLException实例, 并且
// e在catch块中没有改变, 那么外围方法声明为throws SQLException就是合法的
finally子句
-
代码抛出异常, 剩下的代码就不会运行, 如果这时候已经获取到了一些资源, 在退出之前需要释放.
- 可以先捕获所有异常, 然后释放资源, 再重新抛出异常
- 也可以使用
finally子句, 无论是否抛出异常,finally子句部分一定都会执行 Java 7之后可以使用try-with-resources, 这个更常用
-
try语句可以只有finally, 没有catch -
finally语句中不要放throw, continue, break, return这种改变程序执行顺序的语句
try-with-resources
AutoCloseable接口有一个方法void close() throwa ExceptionCloseable接口是AutoCloseable接口的子接口, 同样只包含close(), 但是抛出的是IOExceptiontry-with-resources语句的最简单形式是:1
2
3
4
5
6
7
8
9
10
11try (Resources res = ...) {
work with res
}
// 这里try块退出时, 会自动调用res.close()
try (var in = new Scanner(Path.of("1.txt"), StandardCharsets.UTF_8);
var out = new PrintWriter("2.txt", StandardCharsets.UTF_8)) {
while (in.hasNext()) {
out.println(in.next().toUpperCase());
}
}
// 上述代码不论是如何退出的, 都一定会自动关闭in和out- 如果是
try-catch-finally语句, 在try中抛出了异常, 然后finally调用in.close()又抛出了异常就会产生问题- 此时使用
try-with-resources就可以解决这个问题 - 原来的异常会重新抛出,
close()产生的异常会被抑制, 自动捕获, 由addSuppressed()添加到原来的异常方法中 - 可以调用
getSuppressed(), 会生成一个数组, 包含其中从close()方法中抛出的被抑制的异常
- 此时使用
try-with-resources语句本身可以有catch, finally语句, 这些子句会在关闭资源以后才执行
栈轨迹
- 栈轨迹是程序执行中某个特定点所有挂起的方法调用的一个列表
Throwable类的printStackTrace()可以打印1
2
3
4
5
6
7var t = new Throwable();
var out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String des = out.toString();
// Java 9之前, Throwable.printStackTrace()会生成一个StackTraceElement[]
// 数组中包含了和StackWalker.StackFrame类似的信息, 效率较低
// 因为会得到整个栈, 但是调用者只需要几个栈帧, 并且只允许访问挂起方法的类名, 不能访问类对象- 还可以使用
StackWalker类, 生成一个StackWalker.StackFrame实例流, 其中每个实例表示一个栈帧1
2StackWalker walker = StackWalker.getInstance();
walker.forEach(frame -> ananlyze frame)
- 栈轨迹一般显示在
System.err上, 如果想要记录栈轨迹, 可以捕获到字符串中 - 也可以记录到文件中, 不过如果是错误的话, 就会发送到
System.err中, 所以就不能使用下面的代码java MyApp > errors.txt, 而是应该使用java MyApp 2> errors.txt- 如果需要在同一个文件中同时保存
System.out, System.err可以使用如下代码 java MyApp 1> errors.txt 2>&1
- 可以使用静态方法
Thread.setDefaultUncaughtExceptionHandler改变没有捕获异常的处理器 - 启动
JVM可以使用-verbose看到类加载器加载过程 Xlint选项可以告诉编译器找出常见的代码问题, 比如javac -Xlint sourceFilesJDK提供了jconsole可以显示JVM性能统计结果
使用异常技巧
- 异常不能代替简单测试, 使用捕获异常会导致程序耗时大大增加, 因此只在异常情况下使用异常
- 不要过分细化异常, 否则一个异常一个
catch会导致代码量激增 - 合理使用异常层次
- 不要只抛出
RuntimeException, 应该需要寻找一个合适的子类, 或者创建自己的异常类 - 不要只捕获
Throwable异常, 否则代码会很难读懂 - 考虑检查型异常和非检查型异常, 检查型异常本质上开销较大
- 不要只抛出
- 不要压制异常, 可以使用
- 使用标准方法报告
null指针和越界异常 - 不要向用户展示最终的栈轨迹
断言
- 断言允许在测试期间在代码中插入一些检查, 在生产代码中自动删除这些
1
2
3
4assert condition;
assert condition : expression;
// 两个写法都会计算condition, 如果为false, 会抛出AssertionError异常
// 第二个语句会将expression传入到AssertionError构造器中, 转换为一个消息字符串 - 默认情况下禁用断言, 运行是使用
java -enableassertions MyApp或者java -ea MyApp启用 - 不需要重新编译启用断言, 因为断言是类加载器的功能, 禁用断言的时候类加载器会自动删去断言的代码, 不会降低速度
- 可以在特定的类或整个包中打开断言
java -ea:MyClass -ea:com.mycompany.mylib MyApp- 这样会在
MyClass类,com.mycompany.mylib包及其子包中的所有类打开断言 - 同样可以使用
-da或者-disableassertions禁用断言 -ea和-da不能应用于没有类加载器的系统类, 需要使用-esa或者enablesystemassertions开启系统类断言
- 这样会在
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Sangs Blog!