目录
- 理解Java异常处理机制的结构体系,理解Throwable、Exception/Error的设计和分类。
- 理解Java语言中操作Throwable元素的原则
- java解惑异常规范解析
- 阿里巴巴异常规范解析
- 常见面试问题
理解Throwable、Exception/Error的设计和分类
结构体系
设计原则
- Exception和Error都是继承了Throwable类,在Java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
- Exception和Error体现了Java平台设计者对不同异常情况的分类。Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
- Error是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。
- Exception又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。前面我介绍的不可查
的Error,是Throwable不是Exception。 - 不检查异常就是所谓的运行时异常,类似NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕
获,并不会在编译期强制要求。
常见子类
非检查性异常类:(RuntimeException)
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
检查性异常类:(IOException)
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
错误(Error)
异常 | 描述 |
---|---|
StackOverflowError | 递归过深,递归没有出口。 |
OutOfMemoryError | JVM空间溢出,创建对象速度高于GC回收速度。 |
自定义异常
除了系统定义的各种"异常"之外,有时候根据程序的需要,开发人员需要定义自己的"异常",也就是"自定义异常"。"自定义异常"有三个原则:
自己定义:"自定义异常"需要继承自Exception类或其子类;
自己抛出:"自定义异常"需要根据情况自己抛出异常对象;
自己处理:需要程序自己来处理"自定义异常情况";
通常来说,自定义异常继承这两个类即可,原则如下:
- 如果希望写一个检查性异常类,则需要继承 Exception 类,此类异常必须处理;
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类,可以不处理;
如果自定义异常继承了 RuntimeException ,处理起来会更简单,但是缺乏了 Exception 类的严谨:
如果在函数内容抛出该异常,函数上不用声明,编译一样通过;
如果在该函数上声明了该异常,调用者可以不用运行处理,编译一样通过
异常处理原则
- try-catch-finally块
- throw
- throws
- try-with-resources
- multiple catch
try-cathc-finally
使用 try 和 catch关键字可以捕获异常,代码块放在异常可能发生的地方。
匹配的原则是:如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。
try-catch体还可以定义一个finally模块,finally模块通常用于资源的回收。
finally体的特点是不论程序有无异常产生都能得到执行,所以我们可以把try块里资源的回收等操作交给它处理(如数据库连接、网络连接和磁盘文件),以便保证其内容一定能得到执行.
注意:只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
throws/throw
如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明,意为抛出异常给父集。但有一点要明白:必须有一级代码需要对异常进行处理,不能一直抛出异常,最终交给JVM处理,因为JVM对检查性异常的处理只有一个方法 – 让系统挂掉。
再说说throw/throws的关系:
- throws是用在方法的定义处,用以声明方法体内部可能会产生的异常类型。如果代码中其他方法调用了这样有异常声明的方法,必须对其进行处理或者接着抛出,坚决不能不处理;
- throw的用法是抛出异常对象,它本身就是一个抛出异常的动作,通常在方法体内进行调用,通常throw和throws是成对出现的。
try-catch-resources
在JDK7以前,Java没有自动关闭外部资源的语法特性,直到JDK7中新增了try-with-resource语法,才实现了这一功能。
那什么是try-with-resource呢?简而言之,当一个外部资源的句柄对象(比如FileInputStream对象)实现了AutoCloseable接口,那么就可以将上面的板式代码简化为如下形式:
public static void main(String[] args) {
try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
System.out.println(inputStream.read());
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。
multiple catch
有时当我们调用一段处理时,需要同时捕获多个异常,但是我们对这些异常处理的代码是相同的。比如:
try {
// do something
} catch (AException e) {
throw new MyException(e);
} catch (BException e) {
throw new MyException(e);
}
jdk1.7后可以把上述代码等价地写成:
try {
// do something
} catch (AException | BException e) {
throw new MyException(e);
}
java解惑异常规范解析
第57条 只针对异常的情况才使用异常
第58条 对可恢复的情况使用受检异常,对编程错误使用运行时异常
- 如果期望调用者能够适当的恢复,使用受检异常。
- 大多数的运行时异常都表示前提违例(precondition violation),如ArrayIndexOutOfBoundsException。
- 错误往往被JVM保留用于表示资源不足、约束失败,或其他无法继续执行的条件。最好不要再实现任何新的Error子类。
第59条 避免不必要的使用受检的异常
第60条 优先使用标准的异常
- 常用异常:IllegalArgumentException、IllegalStatusException、NullPointerException、IndexOutOfBoundsException、ConcurrentModificationException、UnsupportedOperationException等。
第61条 抛出与异常相对于的异常
- 更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法被称为异常转义(exception translation),如AbstractSequentialList类的例子:
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
- 也可以使用异常链(exception chaining)的形式来进行转义,即将底层的异常作为参数传入高层异常。
第62条 每个方法抛出的异常都要有文档
- 始终要单独的声明受检异常,并利用Javadoc的@throws标记准确的记录下抛出异常的每个条件。
- 如果一个类的许多方法出于同样的原因而抛出同一个异常,在该类的文档注释中对这个异常建立文档,是可以接受的。
第63条 在细节消息中包含能捕获失败的信息
第64条 努力使失败保持原子性
- 一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性(failure atomic)。
- 获得失败原子性的方法:
- 在执行操作之前检查参数的有效性。
- 调整计算处理过程的顺序,是的任何可能会失败的计算部分都在对象状态被修改前发生。
- 编写一段恢复代码(不常用)。
- 在对象的一份临时拷贝上执行操作,当操作完成后在用临时拷贝中的结果代替对象的内容。
第65条 不要忽略异常
- 至少,catch块也应该包含一条说明,解释为什么可以忽略这个异常。
阿里巴巴异常规范解析
- 【强制】不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:
IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查
来规避,保证程序健壮性。
正例:if(obj != null) {...}
反例:try { obj.method() } catch(NullPointerException e){...} - 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
- 【强制】对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳
定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分
异常类型,再做对应的异常处理。 - 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请
将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的
内容。 - 【强制】有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回
滚事务。 - 【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明:如果 JDK7,可以使用 try-with-resources 方式。 - 【强制】不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不
会再执行 try 块中的 return 语句。 - 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。 - 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分
说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。
说明:本规约明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用
者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回 null 的情况。 - 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1) 返回类型为包装数据类型,有可能是 null,返回 int 值时注意判空。
反例:public int f(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE。 2) 数据库的查询结果可能为 null。 3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
阿里巴巴 Java 开发手册
——禁止用于商业用途,违者必究—— 20 / 34
4) 远程调用返回对象,一律要求进行 NPE 判断。
5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。 - 【推荐】在代码中使用“抛异常”还是“返回错误码”,对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC调用优先考虑使用 Result 方式,封装 isSuccess、“错误码”、“错误简短信息”。
说明:关于 RPC 方法返回方式使用 Result 方式的理由:
1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 - 【推荐】定义时区分 unchecked / checked 异常,避免直接使用 RuntimeException 抛出,
更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义
过的自定义异常,如:DAOException / ServiceException 等。 - 【参考】避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。
说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副
本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。
正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
private boolean checkParam(DTO dto){...}
常见面试问题
java中内存溢出和内存泄漏的区别
虽然在java中我们不用关心内存的释放,垃圾回收机制帮助我们回收不需要的对象,但实际上不正当的操作也会产生内存问题:如,内存溢出、内存泄漏。
- 内存溢出:out of memory:简单通俗理解就是内存不够用了 。
- 内存泄漏:leak of memory:
一个对象分配内存之后,在使用结束时未及时释放,导致一直占用内存,没有及时清理,使实际可用内存减少,就好像内存泄漏了一样。
NoClassDefFoundError和ClassNotFoundException的区别
- ClassNotFoundException: 当应用程序运行的过程中尝试使用类加载器去加载Class文件的时候,如果没有在classpath中查找到指定的类,就会抛出ClassNotFoundException。一般情况下,当我们使用Class.forName()或者ClassLoader.loadClass以及使用ClassLoader.findSystemClass()在运行时加载类的时候,如果类没有被找到,那么就会导致JVM抛出ClassNotFoundException。
- NoClassDefFoundError:NoClassDefFoundError异常,看命名后缀是一个Error。从类继承层次上看,NoClassDefFoundError是从Error继承的。和ClassNotFoundException相比,明显的一个区别是,NoClassDefFoundError并不需要应用程序去关心catch的问题。当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个NoClassDefFoundError错误。比如当我们在new一个类的实例的时候,如果在运行是类找不到,则会抛出一个NoClassDefFoundError的错误。
资料
- 即可时间-Java核心技术36讲
- Java 异常处理机制,结构体系,原则,方法...一篇带你搞清楚