貌似很多人对下面的方法的返回值都比较迷糊:
package cc.lixiaohui.demo;
public class ReturnValueTest {
public int test() {
int a;
try {
a = 1;
//int b = 1 / 0;
return a;
} catch (Exception e) {
a = 2;
return a;
} finally {
a = 3;
}
}
}
test方法的返回值自然是1,如果把注释那行去掉,那就是2.
为什么?
用javap -verbose ReturnValueTest 查看字节码:
重点查看test()方法指令:下载
Compiled from "ReturnValueTest.java"
public class cc.lixiaohui.demo.ReturnValueTest extends java.lang.Object
SourceFile: "ReturnValueTest.java"
minor version: 0
major version: 49
Constant pool: --常量池
const #1 = class #2; // cc/lixiaohui/demo/ReturnValueTest
const #2 = Asciz cc/lixiaohui/demo/ReturnValueTest;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz <init>;
const #6 = Asciz ()V;
const #7 = Asciz Code;
const #8 = Method #3.#9; // java/lang/Object."<init>":()V
const #9 = NameAndType #5:#6;// "<init>":()V
const #10 = Asciz LineNumberTable;
const #11 = Asciz LocalVariableTable;
const #12 = Asciz this;
const #13 = Asciz Lcc/lixiaohui/demo/ReturnValueTest;;
const #14 = Asciz test;
const #15 = Asciz ()I;
const #16 = class #17; // java/lang/Exception
const #17 = Asciz java/lang/Exception;
const #18 = Asciz a;
const #19 = Asciz I;
const #20 = Asciz e;
const #21 = Asciz Ljava/lang/Exception;;
const #22 = Asciz SourceFile;
const #23 = Asciz ReturnValueTest.java;
{
public cc.lixiaohui.demo.ReturnValueTest(); --构造方法就不分析了
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcc/lixiaohui/demo/ReturnValueTest;
public int test();
Code:
Stack=1, Locals=5, Args_size=1 -- [Stack=1貌似表示栈深度为1(不确定),]Locals=5表示局部变量表长度为5, Args_size=1表示该方法有1个参数(this)
0: iconst_1 --将int值 1 压栈
1: istore_1 --将栈顶值(即1)弹出保存至局部变量表第2个位置(局部变量表下标是从0开始的,但是0位置被this变量占用了)
2: iload_1 --将局部变量表第2个位置的值压栈
3: istore 4 --将栈顶的值弹出并保存至局部变量表第5个位置(这里可以看到)
5: iconst_3 --(这里开始为finally块)将int值3压栈
6: istore_1 --将栈顶的值(即3)弹出并存储至局部变量表第2个位置
7: iload 4 --将局部变量表第5个位置(即1)压栈
9: ireturn --返回栈顶的值(即1), 结束方法调用(该路径为try(未抛异常) -> finally)
10: astore_2 --将栈顶的引用(这里即为catch块中捕捉到的异常e)存储至局部变量表的第3个位置
11: iconst_2 --将int值2压栈
12: istore_1 --将栈顶值(即2)弹出并存储至局部变量表第2个位置
13: iload_1 --将局部变量表第2个位置的值(即2)压栈
14: istore 4 --将栈顶值弹出(即2)并保存至局部变量表第5个位置,原来第五个位置是1,现在1被覆盖了,变为2
16: iconst_3 --(这里开始为finally块)将int值3压栈
17: istore_1 --将栈顶的值(即3)弹出并存储至局部变量表第2个位置
18: iload 4 --将局部变量表第5个位置(即2)压栈
20: ireturn --返回栈顶的值(即2),结束方法调用(该路径为try(抛Exception或其子类异常) -> catch -> finally)
21: astore_3 --将栈顶的引用(这里为非Exception的子类异常)存储至局部变量表的第4个位置
22: iconst_3 --将int值3压栈
23: istore_1 --将栈顶值(即3)弹出并存储至局部变量表第二个位置
24: aload_3 --将局部变量表第4个位置(即为异常引用)压栈
25: athrow --将栈顶的异常抛出(该路径为try(抛非Exception或其子类异常) -> finally, 或者try(抛Exception或其子类异常) -> catch(抛任何异常) -> finally )
Exception table:
from to target type
0 5 10 Class java/lang/Exception --若执行到0-5行(即在try块中)抛出java.lang.Exception或其子类则跳转至第10行执行(即catch块)
0 5 21 any --若执行到0-5行(即在try块中)抛出非java.lang.Exception或其子类则跳转至第21行执行(即finally块)
10 16 21 any --若执行到10-16行(即在catch块中)抛出任何异常则跳转至21行执行(即finally块)
LineNumberTable: --这个表为源码行数与字节码行数的映射
line 13: 0
line 14: 2
line 19: 5
line 14: 7
line 15: 10
line 16: 11
line 17: 13
line 19: 16
line 17: 18
line 18: 21
line 19: 22
line 20: 24
LocalVariableTable: --这个即为局部变量表, start和length结合起来就可以确定该变量的作用范围, 例如this作用范围为整个方法
Start Length Slot Name Signature
0 26 0 this Lcc/lixiaohui/demo/ReturnValueTest; --占用第1个slot(一个slot应该是32bits)
2 8 1 a I --I标识int, 占用第2个slot
13 8 1 a I --占用第2个slot
24 2 1 a I --占用第2个slot
11 10 2 e Ljava/lang/Exception; --占用第3个slot
}
可以发现jvm始终把返回值放在最后一个局部变量表的位置,而且在finally中改变x并不影响返回值.