try catch finally 的面试题总是出现在面试题中,此题可以考察初级程序员的基本知识,也可以考察高级程序员的对字节码的理解。下面列举几种情况:
正常的 try catch finally,为什么一定会走finally 呢?
public class Demo { public static void demo(){ try { tryMehtod(); }catch (Exception e){ handlerException(); }finally { handleFinally(); } } public static void tryMehtod(){ } public static void handlerException(){ } public static void handleFinally(){ }}
使用 javap -v Demo 可以生成字节码。
从上面可以看到,字节码包含了三分finally语句块,都在程序正常return和异常throw之前,其中两处在 try 和 catch 调用 return 之前,一处是在异常 throw 之前。
Java 采用方式是复制 finally 代码块的内容,分别放在 try catch 代码块所有正常 return 和 异常 throw 之前。
相当于如下的代码:
public void demo(){ try { tryMehtod(); handleFinally(); }catch (Exception e){ handlerException(); handleFinally(); }catch (Throwable e){ handleFinally(); throw e; } }
catch中有return,finally 会执行吗?
public static int calu() { try { throw new Exception(); }catch (Exception e){ return 0; }finally { System.out.println("finally"); } }
此时一定会执行的:
我们看下生成的字节码:
public static int calu(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=0 0: new #2 // class java/lang/Exception 3: dup 4: invokespecial #3 // Method java/lang/Exception."":()V 7: athrow 8: astore_0 9: iconst_0 10: istore_1 11: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 14: ldc #5 // String finally 执行finally 16: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 19: iload_1 20: ireturn 21: astore_2 22: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 25: ldc #5 // String finally 执行finally 27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: aload_2 31: athrow
从上面的字节码看,才catch 里 return 之前,已经执行了finally。
catch中有return,finally也有return,怎么执行?
public class Demo { public static int calu() { try { throw new Exception(); }catch (Exception e){ return 2; }finally { return 3; } } public static void main( String[] args ) { System.out.println(calu()); }}
执行结果:
我们分析一下字节码:
public static int calu(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=0 0: new # 创建一个Exception对象,并将其引用值压入栈顶 3: dup #复制栈顶数值并将复制值压入栈顶 4: invokespecial #3 // Method java/lang/Exception."":()V 7: athrow #将栈顶的异常抛出 8: astore_0 #将栈顶 引用 型数值存入第一个局部变量 9: iconst_2 #将 int 型 2 推送至栈顶 10: istore_1 #将栈顶 int 型数值存入第二个局部变量 11: iconst_3 #将 int 型 3 推送至栈顶 12: ireturn #从当前方法返回 int 13: astore_2 #将栈顶 引用 型数值存入第三个局部变量 14: iconst_3 #将 int 型 3 推送至栈顶 15: ireturn #从当前方法返回 int
从上面的字节码看,在第一个return之前 ,取出的数是3,所以返回的是3;
catch中return变量,finally对变量做计算,返回结果是啥?
public static int calu() { int i = 0; try { throw new Exception(); }catch (Exception e){ return i; }finally { ++i; } }
结果:
为什么是0,我们还是从字节码的执行来看:
public static int calu(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=0 0: iconst_0 #将 int 型 0 推送至栈顶 1: istore_0 #将栈顶 int 型数值存入第一个局部变量 2: new #2 // class java/lang/Exception 5: dup 6: invokespecial #3 // Method java/lang/Exception."":()V 9: athrow #将栈顶的异常抛出 10: astore_1 #将栈顶 引用 型数值存入第二个局部变量 11: iload_0 #将第一个 int 型局部变量推送至栈顶 12: istore_2 #将栈顶 int 型数值存入第三个局部变量 13: iinc 0, 1 #(M 为非负整数,N 为整数)将局部变量数组的第 M 个单元中的 int 值增加 N,常用于 for 循环中自增量的更新 16: iload_2 #将第三个 int 型局部变量推送至栈顶 17: ireturn #将栈顶的值return 18: astore_3 19: iinc 0, 1 22: aload_3 23: athrow
从上面字节来看ireturn是栈顶的值,此时栈顶的值还是0;需要看12行和16行。