clq
[图片]
通过调试可以很容易看出,在 return ret 之前,编译器实际上对 ret 赋值了一份临时变量,为的就是防止 finally 区域中的代码对这个 ret 值的改变。当然这只是 debug 版本的情况,实际上,在 release 版本, return ret 语句直接被编译器编译成 mov eax, 1 指令。
所以说,无论是在 SEH 异常处理模型中,还是 Java 的异常处理模型中, finally 块区域中的代码都是不能够通过重新赋值的方式来改变 try 区域中 return 语句的返回值。
强烈建议不要在 finally 内部使用 return 语句
上面刚刚说了, finally 块区域中的代码不会轻易影响 try 区域中 return 语句的返回值,但是有一种情况例外,那就是在 finally 内部使用 return 语句。示例程序如下:
// 示例程序 1 , Java 程序
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
System.out.println("test 的返回值为: " + test());
}
public static int test()
{
int ret = 1;
try
{
System.out.println("in try block");
return (ret);
}
catch(Exception e)
{
System.out.println("in catch block");
e.printStackTrace();
}
finally
{
ret = 2;
System.out.println("in finally block!");
// 这里添加了一条 return 语句
return ret;
}
}
}
// 示例程序 2 , C 程序
#include "stdio.h"
int test()
{
int ret = 1;
__try
{
printf("in try block\n");
return ret;
}
__finally
{
ret = 2;
printf("in finally block!\n");
return ret;
}
printf(" 多余的 \n");
return 0;
}
void main()
{
printf("test 的返回值为: %d\n", test());
}
上面的程序运行结果如下:
in try block
in finally block!
test 的返回值为: 2
也许大多数朋友都估计到,上面的 test 函数返回值是 2 。也即是说, finally 内部使用 return 语句后,它影响(覆盖了) try 区域中 return 语句的返回值。这真是一种特别糟糕的情况,虽然它表面上看起来不是那么严重,但是这种程序极易给它人造成误解(使得阅读该代码的人总得担心或考虑,是否有其它地方影响了这里的 return 的返回值)。
之所以出现这种现象的真正原因是,由于 finally 区域中的代码先于 return 语句( try 作用域中的)被执行,但是,如果此时在 finally 内部也有一个 return 语句,这将会导致该函数直接就返回了,而致使 try 作用域中的 return 语句再也得不到执行机会(实际就是无效代码,被覆盖了)。
面对上述情况,其实更合理的做法是,既不在 try block 内部中使用 return 语句,也不在 finally 内部使用 return 语句,而应该在 finally 语句之后使用 return 来表示函数的结束和返回,把上面的程序改造一下,代码如下
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
System.out.println("test 的返回值为: " + test());
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static int test() throws RuntimeException
{
int ret = 1;
try
{
System.out.println("in try block");
}
catch(RuntimeException e)
{
System.out.println("in catch block");
e.printStackTrace();
throw e;
}
finally
{
ret = 2;
System.out.println("in finally block!");
}
// 把 return 语句放在最后,这最为妥当
return ret;
}
}
另一种更糟糕的情况
上面刚刚讲到, finally 内部使用 return 语句会覆盖 try 区域中 return 语句的返回值。不仅与此, finally 内部使用 return 语句还会导致出现另外一种更为糟糕的局面。到底是何种糟糕情况呢?还是先看看下面的示例程序再说吧!代码如下:
import java.io.*;
public class Trans
{
public static void main(String[] args)
{
try
{
System.out.println("test 的返回值为: " + test());
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static int test() throws RuntimeException
{
int ret = 0;
try
{
System.out.println("in try block");
// 这里会导致出现一个运行态异常
int i=4,j=0;
ret = i/j;
}
catch(RuntimeException e)
{
System.out.println("in catch block");
e.printStackTrace();
// 异常被重新抛出,上层函数可以进一步处理此异常
throw e;
}
finally
{
System.out.println("in finally block!");
// 注意,这里有一个 return 语句
return ret;
}
}
}
是不是觉得上面示例程序中的代码写的挺好的,挺简洁的,还挺严谨的,应该不会有什么 BUG !阿愚告诉你,错了!绝对错了!而且问题很严重!要不,朋友们在编译运行此程序前,先预期一下它的运行结果,大家是不是觉得运行流程应该是这样子的: 首先在终端输出“ in try block ”,接着,由于程序运行时出现了一个被 0 处的异常( ArithmeticException );于是,进入到 catch block 中,这里的代码将继续向终端输出了“ in catch block ”信息,以及输出异常的堆栈信息等;接着,由于异常在 catch block 中又被重新抛出了,所以控制权返回到 main 函数的 catch block 中;对了,补充一点,也许大家会觉得,由于异常的 rethrow ,使得控制权离开 test 函数作用域的时候, finally 内的代码会被执行,也即“ in finally block ”信息也会被打印到终端上了 。仅仅如此吗?不妨看一下实际的运行结果,如下:
in try block
in catch block
java.lang.ArithmeticException: / by zero
at Trans.test(Trans.java:27)
at Trans.main(Trans.java:10)
in finally block!
test 的返回值为: 0
看了实际的运行结果,是不是觉得大吃一惊!被重新抛出( rethrow )的异常居然“丢弃了”,也即 main 函数中,并没有捕获到 test 函数中的任何异常,这也许大大出乎当初写这段代码的程序员的预料吧!究竟原因何在呢?其实, 罪魁祸首就是 finally block 内部的那条 return 语句 。因为这段程序的运行流程基本如刚才我们预期描述的那样,但是有一点是不对的,那就是当 test 函数中把异常重新抛出后,这将导致又一次的 catch 的搜索和匹配过程,以及 test 函数中的 UnWinding 操作,也即 finally 被调用执行,但是由于 finally 内部的 return 语句,不仅使得它结束了 test 函数的执行(并返回一个值给上层函数),而且这还使得上面的那个对异常进行进一步的操作过程给终止了(也即控制权进入到 catch block 的过程)。瞧瞧!后果严重吧!
是呀!如果 Java 程序员不注意这种问题,养成一个严谨的、好的编程习惯,它将会导致实际的许多 Java 应用系统中出现一些莫名奇妙的现象(总感觉系统中出现了某类异常,有一些问题,但上层的模块中却总是捕获不到相关的异常,感觉一些良好!其实不然,它完全是由于 finally 块中的 return 语句不小心把异常给屏蔽丢弃掉了)。
总结
• finally 区域内的代码总在 return 之前被执行;
• 强烈建议不要在 finally 内部使用 return 语句。它不仅会影响函数的正确返回值,而且它可能还会导致一些异常处理过程的意外终止,最终导致某些异常的丢失。
关于 Java 异常处理模型的阐述暂时就到此为止吧!虽然,有关 Java 的异常处理知识和技巧,还有不少方面需要深入讨论的,例如异常的嵌套,异常的分类,函数接口中有关异常的声明等等,阿愚打算在后面“相爱篇”的一些文章中,将继续讨论这些有关的东东。
接下来一篇文章中,会详细讨论类 Unix 操作系统中对异常处理的支持,方法,以及思想等。感兴趣的朋友, Let's go!
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.