两万字详解Java异常,面试再也不怕被问到! (qq.com)
Java的异常都是Throwable
的子类,他包含两个子类
- 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
RuntimeException
类及其子类,Java 编译器不会检查它。属于非受检异常。当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。- 编译时异常,Exception 中除 RuntimeException 及其子类之外的异常。属于受检查异常。要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。比如 IO相关的异常,ClassNotFoundException(没有找到指定的类异常),SQLException。
Error
类及其子类,代表程序中无法处理的错误,表示运行应用过程中出现了严重的错误。一般是JVM出现问题。这些错误是非受检异常,非代码性错误,此类错误发生时,应用程序不应该去处理。我们不应该实现任何新的Error子类。eg. Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。
追问:什么是受检查异常什么是非受检异常?
受检异常:编译器要求必须处理的异常。除 RuntimeException
及其子类外,其他的 Exception 异常都属于受检异常。
非受检异常:编译器不会进行检查并且不要求必须处理的异常,包括运行时异常(RuntimeException
极其子类)和错误(Error
)。
追问:throw和throws的区别是什么?
throw关键字用在方法内部,只能用于抛出一种异常。用来抛出方法或者代码块中的异常,受检查异常和非受检异常都可以被抛出。
throws关键字用在方法声明上,可以抛出多个异常,用来表示该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
追问:JVM是如何处理异常的?
在一个方法中发生异常的话,这个方法会创建一个异常对象,并转交给JVM,该异常对象包含异常名称、异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。
可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM会顺着调用栈会查找是否有可以处理异常的代码,如果有,则调用异常处理代码。当JVM发现可以处理异常的代码时,会把发生的异常传递给他。如果JVM没有找到可以处理该异常的代码块,就会将该异常转交给默认的异常处理器,默认异常处理器打印异常信息并终止应用程序。
追问:try-catch-finally 如何使用?
try
块:用于捕获异常。其后可接零个或多个catch
块,如果没有catch
块,则必须跟一个finally
块。catch
块:用于处理 try 捕获到的异常。finally
块:无论是否捕获或处理异常,finally
块里的语句都会被执行。当在try
块或catch
块中遇到return
语句时,finally
语句块将在方法返回之前被执行。
追问:下面代码输出?
public static void main(String[] args) {
System.out.println(f(2));
}
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
输出0,
当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
追问:try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
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;
}
// 执行结果:30
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
//如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
return a;
}
}
// 执行结果:40
会执行,在 return 前执行。
在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。
追问:finally 中的代码什么情况下不会执行?
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 终止当前正在运行的Java虚拟机
System.exit(1);
} finally {
System.out.println("Finally");
}
输出:
Try to do something
Catch Exception -> RuntimeException
以下情况finally 中的代码不会执行。
- 虚拟机终止运行
- 程序所在的线程死亡。
- 关闭 CPU。
追问:知道try-with-resources
吗
为了实现资源的自动释放,要求使用的类实现了AutoCloseable
接口。
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。