Day08:异常处理
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Throwable │
└───────────┘
▲
┌─────────┴─────────┐
│ │
┌───────────┐ ┌───────────┐
│ Error │ │ Exception │
└───────────┘ └───────────┘
▲ ▲
┌───────┘ ┌────┴──────────┐
│ │ │
┌─────────────────┐ ┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘ └─────────────────┘└───────────┘
▲
┌───────────┴─────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────────┐
│NullPointerException │ │IllegalArgumentException │...
└─────────────────────┘ └─────────────────────────┘
Error:
-
OutOfMemoryError:内存耗尽 -
NoClassDefFoundError:无法加载某个Class -
StackOverflowError:栈溢出
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:
-
NumberFormatException:数值类型的格式错误 -
FileNotFoundException:未找到文件 -
SocketException:读取网络失败
还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:
-
NullPointerException:对某个null的对象调用方法或字段 -
IndexOutOfBoundsException:数组索引越界
Exception又分为两大类:
-
RuntimeException以及它的子类; -
非
RuntimeException(包括IOException、ReflectiveOperationException等等)
Java规定:
-
必须捕获的异常,包括
Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。 -
不需要捕获的异常,包括
Error及其子类,RuntimeException及其子类。
捕获异常
捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类:
多catch语句:
-
多个catch语句,每个catch分别捕获对应的Exception及其子类。多个catch只有一个能被执行。
-
存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。
finally语句
-
Java的
try ... catch机制还提供了finally语句,finally语句块保证有无错误都会执行。 -
finally 语句不是必须的,并且总是最后执行。
如果没有发生异常,就正常执行try { ... }语句块,然后执行finally。如果发生了异常,就中断执行try { ... }语句块,然后跳转执行匹配的catch语句块,最后执行finally。
某些情况下,可以没有catch,只使用try ... finally结构。
捕获多种异常
抛出异常
异常的传播
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止:
通过printStackTrace()可以打印出方法的调用栈,对于调试错误非常有用。
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
process2();
}
static void process2() {
Integer.parseInt(null); // 会抛出NumberFormatException
}
}
//parseInt() 转换成整数
抛出异常
在catch中抛出新的异常时,要记得把原始异常的实例传进去。
捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!
在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常。
异常屏蔽
在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出,当catch和finally都抛出了异常时,虽然catch的异常被屏蔽了,但是,finally抛出的异常仍然包含了它。
大型项目需要自定义异常。
自定义异常需要从RuntimeException继承,然后从BaseException派生自定义异常,自定义的BaseException应该提供多个构造方法。
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
NULLPointerException
空指针异常,俗称NPE。
如果一个对象为null,调用其方法或访问其字段就会产生NPE。
处理NULLPointerException
首先,必须明确,NullPointerException是一种代码逻辑错误,遇到NullPointerException,遵循原则是早暴露,早修复,严禁使用catch来隐藏这种编码错误。
尽量在定义成员变量时初始化。
public class Person {
private String name = "";
}
定位NullPointerException
如果产生了NullPointerException,例如,调用a.b.c.x()时产生了NullPointerException,原因可能是a,b,c中的任一个为null.
在Java14中,JVM可以给出详细的信息告诉我们null对象到底是谁。
使用JDK Logging 日志
输出日志,而不是用System.out.println(),有以下几个好处:
-
可以设置输出样式,避免自己每次都写
"ERROR: " + var -
可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
-
可以被重定向到文件,这样可以在程序运行结束后查看日志;
-
可以按包名控制日志级别,只输出某些包打的日志;
-
可以……
总之就是好处很多啦。

浙公网安备 33010602011771号