在日常编程中,我们经常遇到各种异常,比如NullPointerException、ClassNotFoundException、ArrayIndexOutOfBoundsException、ClassCastException、NumberFormatException等各种异常,因此,try-catch模块就成了我们代码的常客。如果try模块中所有语句正常执行完毕,那么finally块就会被执行;如果try模块在执行过程抛出异常,且无论异常是否可以被catch到,finally模块的代码都被执行到。但是就这样被我们每天都在使用的try-catch真的如我们想象的流程中走吗?今天我们就针对try-catch-finally中的一些细节问题分析一下。
方法1:
方法2:
对比上面两个方法,方法1的输出结果不难猜,直接输出“测试结果:finally”;但是方法2的输出结果可能出乎有些人的想象,输出“测试结果:try”,无需怀疑的是finally模块确实执行过的,那么为什么方法2会返回“try”呢,最有可能一种原因就是test方法在try模块return的时候,return是result的一份copy,然后随虽然正常执行了finally模块,修改了result的值,但是不会改变try模块的return值。
当然上面结果都是猜测,如何证明呢? 这里我们使用JBE(Java Bytecode Editor)工具(下载地址:http://set.ee/jbe/)查看一下方法2的字节码:
简单分析一下字节码命令,其中:
ldc:Push item from run-time constant pool,也就是把常量池变量推到栈顶,该命令负责把数值常量或String常量值从常量池中推送至栈顶,后面需要给一个表示常量在常量池中位置索引的参数;
astore_:Store reference into local variable,即将栈顶的objectref存入lvs,这里lvs表示局部标量表(local variable slot),其容量以变量槽(Variable Slot)为最小单位;
aload_:Load reference from local variable,从lvs读取一个objectref,存入栈顶;
areturn:Return reference from method,更加明确的说,是引用返回指令,代表返回栈顶的objectref。其它的返回指令还有ireturn(int)、lreturn(long)、dreturn(double)、freturn(float)和return(void)。
从字节码中我们可以看到,整个方法中只有两个return,我们只需分析到一个return即可,注意这里的栈都是指栈帧中的操作数栈:
将常量池中2号位置的字符串信息“try”推到栈顶;
将栈顶的内容“try”出栈并加载到局部变量表的0号Slot;
将局部变量表的0号Slot中的字符串“try”复制加载到栈顶;
将栈顶字符串内容“try”出栈并加载到局部变量表的1号Slot;
将常量中3号位置字符串“finally”推到栈顶;
将栈顶的内容“finally”出栈并加载到局部变量表的0号Slot;
将局部变量表中的1号Slot内容“try”复制到栈顶;
最后areturn结束方法执行,并将字符串引用“try”返回给方法调用者;
至此,我们的tes()方法调用结束了,虽然在finally模块中对result进行了赋值,但最终返回的确是“try”的一份赋值版本,所以后续大家需要注意一下return在try-catch-finally模块中到底如何使用,尽量在try-catch中使用return,避免出现一些自己想象不到的代码影响业务执行。