前言
本文主要讲解java的异常体系及有关finally和return的返回问题。
继承体系
java中所有异常类的基类都是Throable类。它的两大子类分别是Error和Exception。Error表示错误,是程序处理不了的。
Exception类又分为运行时异常(RuntimeException异常)和非运行时异常(或称为受检异常)。顾名思义,运行时异常是程序在运行时才能被检查出来的异常,可以不强制抛出。而受检异常则必须要处理,或通过try-catch捕获,或抛出。
关于finally和return
在异常块中,有finally
块,表示在返回前的清理工作,常和return
搭配使用。这里先要介绍有关java栈的有关知识
java栈是JVM的一个线程私有的区域。栈中每个栈帧为每个方法执行的线程模型,每个栈帧的入栈出栈代表方法的的执行与退出。简单说来,一个方法执行,在JVM中就是一个栈帧被放在栈顶执行的过程。
栈帧中包含 操作数栈
,局部变量表
。return
操作就是将操作数栈栈顶的值返回。在此之前已经将计算结果放到了操作数栈顶)
return [expression] 的执行过程为
- 计算return表达式值,即 [expression],将计算结果放入栈顶
- 执行return,返回栈顶值。
加上finally后,return的逻辑变为
- 计算return表达式值,放入栈顶
- 将栈顶值拿出来放入局部变量表中,以便执行finally
- 执行finally语句
- 将第二步操作数栈的值拿回来,放回栈顶
- 执行return,返回栈顶元素
由此可知,return的逻辑没有变,只是由于finally的存在,将return的值暂存起来,以便finally语句能使用栈,等使用完后再将返回值替换回来。finally语句块设计出来的目的只是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的
所以, finally一般不能修改返回值,原因如上,在执行finally时,返回值就确定了,已经存起来了。可是,下面两种情况除外,
- 如果在finally语句中有
return
,结果就直接返回了。 - 返回的是引用类型,在finally中修改了引用指向的内容。
下面是示例
- return和finally的先后顺序
//基本类型,finally修改了值,但没影响返回值
// 返回 10
@Test
public void testReturnValue() {
System.out.println(testValue1());
}
public int testValue1() {
int a = 0;
try {
a = 10;
return a;
} catch (Exception e) {
a = 20;
return a;
} finally {
a = 30;
}
}
- finally中有返回值
// finally中有返回语句,返回30,当finally有return时,就直接返回了
@Test
public void testReturnValue() {
System.out.println(testValue1());
}
public int testValue1() {
int a = 0;
try {
a = 10;
return a;
} catch (Exception e) {
a = 20;
return a;
} finally {
a = 30;
return a;
}
}
- 返回值为引用类型,在finally修改了
// 创建一个测试用的引用类
class Node {
private int value;
public Node(int value) {
this.value = value;
}
}
@Test
public void testReturnValue() {
System.out.println(testValue1().value);
}
public Node testValue1() {
Node node = null;
try {
// 创建引用
node = new Node(100);
return node;
} finally {
// 在finally中修改值
node.value = 300;
}
}
大家先想想返回值是什么,可以知道,按照之前的说法,在return会把返回值暂存起来,然后执行finally,这里finally执行修改了实际引用所指向的内容,所以返回值为300
。
- 返回值为引用,在finally中修改了引用指向
// 创建一个测试用的引用类
class Node {
private int value;
public Node(int value) {
this.value = value;
}
}
@Test
public void testReturnValue() {
System.out.println(testValue4().value);
}
public Node testValue4() {
Node node = null;
try {
// 创建引用
node = new Node(100);
return node;
} finally {
// 在finally中修改引用
node = new Node(300);
}
}
这个示例又输出什么呢?可以看到它和上面的那个示例不一样的地方,示例3修改了引用指向的对象,而示例4只是修改了引用。还记得上面说的原理吗?在return返回前,会将结果复制到局部变量表,此时该引用就不变了,后续finally的修改已经和它没关系了。类似于值传递和引用传递的关系(java都是值传递,对于引用只是复制了一份而已,这个也一样)。
输出 300
总结
- finally有返回值,返回结果为finally中的值,try的return值会被覆盖掉,一般不建议这样做。
- finally没有返回值,try或catch的return会被寄存起来,一般finally不会修改到返回值,除非finally修改了该引用指向的实际内存内容。