一、Java异常概述
1. 异常简介
- Java异常时Java提供的一种识别以及响应错误的一致性机制。
- 异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。
- 在有效使用异常的情况下,能够解决异常的三个问题
- where --> 异常是在哪抛出的
- what --> 什么异常被抛出
- why --> 异常为什么会抛出
2. Java异常架构
(1) Throwable(父类)
- Throwable是所有错误和异常的父类
- Throwable包含两个子类:Error(错误)和Exception(异常)
(2) Error(错误)
- Error及其子类;程序运行中无法处理的错误,表示出现了严重的错误
- 这些错误是不受检异常,非代码性错误。因此当此类错误发生时,应用程序不应该去处理此类错误。
(3) Exception(异常)
- 程序本身可以捕获并且可以处理的异常。
- Exception这种异常又分为两类
-
编译时异常
- Exception中除RuntimeException及其子类之外的异常
- Java编译器会检测此类异常;如果检测到,要么通过throws进行声明抛出,要么通过try/catch代码块捕获处理,否则不能通过编译。
- 常见:ClassNotFoundException、IOException
-
运行时异常
- RuntimeException类及其子类,表示在运行期间可能出现的异常
- Java编译器不会检查它,可以编译通过(一般是由逻辑错误引起的);但我们也可以通过throws进行声明抛出,也可以通过try/catch块进行捕获。
- 常见:NullPointerException、ClassCastException、ArrayIndexOutBandException
-
3. 受检异常与非受检异常
是否受检是一个宏观的概念,其对象包含了Throwable下的所有,也就是还会包含Error
- 受检异常:
- 编译器要求必须处理的异常
- Exception中除了RuntimeException及其子类的其他异常,都是受检异常
- 非受检异常:
- 编译器不会进行检查并且不要求处理的异常;
- 该类异常包括运行时异常(RuntimeException及其子类)和错误(Error)
二、异常处理方法
1. 声明异常(throws)
- 通常,我们对知道如何处理的异常 --> 进行捕获处理,对不知道如何处理的异常 --> 继续传递下去
- 在方法签名处可以使用throws关键字声明可能抛出的异常,并且将该异常传递给方法调用者处理
- 非受检异常不可使用throws关键字抛出
2. 抛出异常(throw)
- 如果一个异常无法解决,但又不需要方法调用者处理,可以通过throw关键字直接抛出异常
- 代码进行到throw,之后的代码不会再执行
3. 捕获异常(try/catch)
- 可以用try/catch代码块捕获并且处理异常
三、常见问题
1. JVM如何处理异常
- JVM会沿着调用栈查找是否有可以处理异常的代码,如果有则使用;如果没有,JVM将异常传递给默认的异常处理器(为JVM的一部分),默认异常处理器打印出异常信息并且中止程序。
2. NoClassDefFoundError 和 ClassNotFoundException 区别
-
NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致 -
ClassNotFoundException是一个受检异常,需要显示的使用try/catch或者throws等对其进行处理。
当使用Class.forName、ClassLoader.loadClass等动态加载类到内存时,如果在指定的路径下没有找到该类,就会抛出异常。
3. try/catch代码块中的return问题
- 首先,finally代码块中的代码一定会执行
- 如果catch中有return,会先执行finally代码块中的代码,最后返回catch中return
- 如果catch和finally中都有return,在finally中就会return,覆盖掉catch中的return
- 在catch中执行return之前,需要被返回的值已经确定,如果finally中更改了返回值,return的值不会发生改变
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
* 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
*/
} finally {
a = 40;
}
return a;
}
4. try/catch/finally中哪个部分可以被省略
- catch可以省略
- 原因:
- try --> 处理 运行时异常
- try/catch --> 处理 运行时异常 + 普通异常
- 编译器规定,普通异常如果选择捕获,必须使用catch显示声明以便进一步处理
- 而运行时异常在编译时没有此规定,所以catch可以省略