文章目录
前言
这是一篇学习异常的手记一、什么是异常?
如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下
会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用
这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
二、异常的种类和原因
1.异常的种类
异常是Throwable或其子类之一的实例。Throwable及其所有子类统称为异常类。类Exception和Error是 Throwable的直接子类:
- Exception 是普通程序可能希望从中恢复的所有异常的超类。
- Error 是普通程序通常不会从中恢复的所有异常的超类
Exception这种异常又分为两类:运行时异常和编译异常。
1、运行时异常(Unchecked exception):RuntimeException类及其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于运行时,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
2、受检异常(checked exception):Exception中除RuntimeException及其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
2.异常出现的原因
- 执行了一条throw语句
- Java虚拟机同步检测到一个异常执行情况,即:
- 表达式的计算违反了 Java 编程语言的正常语义,例如整数除以零。
- 加载、链接或初始化程序的一部分时发生错误,在这种情况下,会抛出一个LinkageError子类的实例
- 内部错误或资源限制阻止 Java 虚拟机实现 Java 编程语言的语义;在这种情况下,会抛出一个VirtualMachineError子类的实例。
- 发生异步异常
- 大多数异常作为由于它们的线程的操作而同步发生,而异步异常是可以潜在地发生在一个程序的执行的任意位置
- 异步异常仅在以下情况下发生:
- Thread或ThreadGroup类的*stop *(不推荐使用的)方法的调用
- Java 虚拟机中的内部错误或资源限制,阻止其实现 Java 编程语言的语义。在这种情况下,抛出的异步异常是VirtualMachineError 的子类的一个实例。
三.异常的处理
- 当抛出异常时,控制权从导致异常的代码转移到可以处理异常catch子句。
- 特定catch子句是否可以处理异常是通过将抛出的对象的类与该catch子句的可捕获异常类进行比较来确定的。如果其可捕获的异常类之一是异常类或异常类的超类,则该子句可以处理异常。
- 如果找不到 catch可以处理异常的子句,则终止当前线程(遇到异常的线程)。在终止之前,所有finally子句都被执行,未捕获的异常按照以下规则处理:
- 如果当前线程设置了未捕获的异常处理程序,则执行该处理程序
- 否则,调用当前线程所属ThreadGroup的uncaughtException方法
下面是ThreadGroup的uncaughtException方法
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
四.异常的一些代码细节
Throwable类的部分代码
//异常信息
private String detailMessage;
//当前异常是由哪个Throwable所引起的
private Throwable cause = this;
//引起异常的堆栈跟踪信息
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
// 被抑制的异常列表
private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;
- detailMessage 这个变量是描述异常信息,比如new Myexception(“My Exception”),记录的就是我们传进去的描述此异常的描述信息 My Exception。
- cause 记录当前异常是由哪个异常所引起的,默认是this,可通过构造器自定义。可以通过initCase方法进行修改。
- stackTrace 记录当前异常堆栈信息,数组中每一个StackTraceElement表示当前当前方法调用的一个栈帧,表示一次方法调用。StackTraceElement中保存的有当前方法的类名、方法名、文件名、行号信息。
- suppressedExceptions 保存当前异常抑制的异常列表,在finally代码块抛出异常会抑制catch捕捉的异常,此时可以调用addSuppressed方法将信息携带。
cause的使用
cause 一般用来形成异常链
像这样,在catch捕捉异常后抛出一个新的异常,这个新的异常的cause属性catch是捕捉到的异常。所以在使用框架的时候遇到报错直接找最下面的异常,一般那就是bug出现的原因。
stackTrace的使用
stackTrace 的每个元素中都有当前方法的类名、方法名、文件名、行号信息,这些信息被IDE快速定位,只需点一下就可以跳转过去。
去除stackTrace
为什么要去除stackTrace?
爬栈是抛异常开销大的主要原因之一,当滥用异常来实现某些特殊控制流结构的场景,此时stackTrace肯定是没用的,那个异常对象本身其实也没用,只有它的类型和抛出它带来的控制流跳转才有用。
怎么去除stackTrace?
- 重载Throwable#fillInStackTrace为直接返回this。
- 设置writableStackTrace参数为false
springboot对stackTrace的一个有趣的使用方式
SpringApplication类的deduceMainApplicationClass方法手动new了一个异常,然后判断方法的名字是不是main,通过这种方式来推断出MainApplicationClass,我第一次看到是觉得挺有趣的。😁
五.如何正确使用异常
- 阿里巴巴Java开发手册
- Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理
- 异常不要用来做流程控制,条件控制
- catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理
- 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- 不要在 finally 块中使用 return
- 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类
- 在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable类来进行拦截
阿里巴巴Java开发手册github上有
- Effective Java
- 只针对异常的情况才使用异常
- 避免不必要地使用受检异常
- 优先使用标准的异常
- 抛出与抽象对应的异常
- 不要忽略异常
Effective Java 还是很值得读一读的
总结
这应该算是我第一次写点东西,写得不好是正常的。异常是比较简单的,就这么点知识。