1 finally与return
try-catch-finally是很常用的语法结构,用来控制可能发生异常时的程序流程,其中catch和finally至少要有一个。初学try语
法时可能会要问一个问题:如果在try块中return,那么finally还会执行吗?答案是肯定的。这个非常容易验证,就不举例子了。这样带来一些很
好的特性,例如我们可以在try块中尝试打开数据库,然后读取数据,然后直接把得到的数据return出去,关闭数据连接的工作就交给finally来做
——finally中先判断数据库是否正常打开了,打开了就关闭。这样代码写起来很清晰,每个部分各做各的事。这样我们也可以非常肯定的说,无论发生什么
情况(只要不是进程被强行杀掉),finally中的内容一定是要执行的。
那么是不是可以再问一个问题——如果在finally块中也写了return,那么会怎么样呢?试验一下就很容易知道,finally块中是不允许写
return的,如果一定要写,就会得到一个编译期错误:
error CS0157: Control cannot leave the body of a finally clause
2 先return?先finally?
既然finally一定是要执行的,即使try块中有return,那么这两者的执行顺便是怎么样的呢?简单的做一个实验(下面要说明,这个实验看上去的
结果并不这么直观的表现出它的内在):
using System;
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func1());
}
public static int Func1()
...{
int a = 1;
try
...{
return a;
}
finally
...{
a++;
}
}
}
运行这个程序,很容易得到结果为“1”。那么看上去是执行return在先,而finally在后了。真的是这样吗?
例子中我要return的a是一个值类型,那么如果是引用类型,结果又会如何呢?
using System;
public class TestClass2
...{
public int value = 1;
}
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func2().value);
}
public static TestClass2 Func2()
...{
TestClass2 t = new TestClass2();
try
...{
return t;
}
finally
...{
t.value++;
}
}
}
这一次运行的结果并不是1,而是2。显然,运行Func2()返回的结果并不直接是return后面写的t,而是经过finally块执行后值发生变化的
t。如何来解释这种区别呢?
3 CLR的栈
要解释这种区别,就需要看看其IL是什么,从调用函数、参数栈的角度来理解。CLR在执行中也有栈,但这个栈的用途与传统的本地代码中的栈并不完全相同。
本地代码中栈的用处非常大,不但可以用来临时保存寄存器的值,还用来保存局部变量,此外还用来保存部分或全部传给函数的参数,而函数的返回值一般是通过
EAX寄存器来传递的,而不是用栈。但在CLR中,局部变量并非显式的用栈来保存,栈只是用来调用函数时传递参数,此外,函数的返回值也是用栈来保存的。
当调用一个函数时,将函数所需要的参数依次压栈,函数里面直接取用这些参数,在函数返回时将返回值压栈,函数返回后,栈顶即是返回值。如果调用者并不关心
返回值,那么需要执行一下pop语句,把返回值弹出,这样保证函数在调用前后栈顶的位置是相同的。
当通过压栈传递参数时,参数的类型不同,压栈的内容也不同。如果是值类型,压栈的就是经过复制的参数值,如果是引用类型,那么进栈的只是一个引用,这也就
是我们所熟悉的,传递值类型时,函数内修改参数值不会影响函数外,而引用类型的话则会影响。
代码中当我们执行new时,对应的IL是newobj,其结果是创建一个TestClass2类型的对像并返回一个引用放置于栈上,之后的stloc就将
这个引用保存为局部变量,于是栈上没有了其他内容。Try块并没有执行太多操作,只是把刚保存的引用再放到栈上,再保存为另一个局部变量,这个局部变量就
是稍后要返回的引用,此时我们拥有两个局部变量,但它们是指向同一个对象的两个引用。Finally块先拿出开始时保存的引用放到栈上,dup语句使得栈
顶再增加一个完全一样的引用,之后ldfld语句是从栈顶对象取一个成员放到栈上,所取的成员是value,之后再往栈上压一个1,再执行add,就实现
了1+1=2的过程,add从栈上弹出两个值,再向栈压回一个值。此时再调用stfld就把刚刚压栈的2设置给栈上2之下的那个引用所指对象的value
属性上。而在finally之后的部分才是真正的return,它试图取出我们所保存的第二个局部变量压栈,将它作为返回值。但对于引用类型来说,它与先
前所操作的引用所指的是同一对象,因此finally块中的操作会影响到返回值,也就非常好理解了。
4 改编
知道了finally与return的实现原理,也就不难做出进一步的推广。例如把程序改成这样(返回时由直接返回t变为在t上调用一个做一些操作后返回
自己的函数),其执行结果也不难猜出来吧:
using System;
public class TestClass2
...{
public int value = 1;
public TestClass2 Double()
...{
value *= 2;
return this;
}
}
public class TestClass1
...{
public static void Main()
...{
Console.WriteLine("{0}", Func2().value);
}
public static TestClass2 Func2()
...{
TestClass2 t = new TestClass2();
try
...{
return t.Double();
}
finally
...{
t.value++;
}
}
}
分享到:
相关推荐
try、catch、finally、return 执行顺序的规则是:try 语句用于包装可能抛出异常的代码,catch 语句用于捕捉 try 语句中的异常,finally 语句用于执行一些清理工作,return 语句用于从方法中返回值。finally 语句是在...
"Java中try finally return语句的执行顺序浅析" 关于Java中try finally return语句的执行顺序浅析是Java语言中一个重要的知识点,今天我们来探讨一下try finally return语句的执行顺序。 首先,需要了解的是...
需要注意的是,在 finally 中改变 return 的值对返回值没有任何影响,这是因为 finally 中的代码是在 return 前执行的,而 return 语句是在 finally 中执行的。因此,对于基本类型的数据,在 finally 中改变 return ...
try-catch-finally执行顺序验证(左边是.java文件,右边是.class文件) 提示: try、catch块内的return操作编译后会变成把return的值保存到变量var的操作。 总结: try、catch块内的return操作编译后会变成把return的值...
在这个过程中,函数会将`try`或`catch`块中的`return`语句返回的值暂存起来,然后执行`finally`块。 如果在`finally`块中也有`return`语句,那么这个`return`值会覆盖之前暂存的值。也就是说,最终返回给调用者的值...
4. **test4()**: `try` 块的 `return` 语句在 `finally` 块执行前锁定了返回值,但 `finally` 中的变量修改不会影响返回值。`finally` 完成后,返回 `try` 块的 `return` 值。 5. **test5()**: 这是一个循环中的...
1. 如果`try`块中的代码没有抛出异常,那么`finally`块会在`try`块结束时执行,之后控制权将传递给相应的`return`语句。 2. 如果`try`块中的代码抛出一个未捕获的异常,`finally`块仍然会执行,然后再将异常传递给...
在下面的示例中,我们可以看到 finally 语句是在 try 的 return 语句执行之后,return 返回之前执行的。 ```java public class FinallyTest1 { public static void main(String[] args) { System.out.println...
总结一下,`try-catch-finally`块中的`return`语句执行顺序如下: 1. `try`块中的代码被执行。 2. 如果遇到`return`语句,会创建一个临时变量,保存`return`语句要返回的值。 3. `finally`块被执行。 4. `finally`...
2. **`return` 与 `finally` 的执行顺序**:在 `try` 块中遇到 `return` 时,会先执行 `finally` 语句块,然后再返回值。这是因为 `finally` 中的代码优先级高于 `return`,即使 `return` 已经被触发,`finally` ...
这意味着,如果在try块中执行了return语句,finally语句将在return语句执行之前执行。如果finally语句中包含了return语句,即使前面的catch块重新抛出了异常,则调用该方法的语句也不会获得catch块重新抛出的异常,...
即使在`try`或`catch`块中有`return`语句,`finally`块的代码也会被执行。 ```csharp finally { // 无论是否发生异常都会执行的代码 } ``` 在示例中,无论`j`的值是否为0,`finally`块的`Console.WriteLine(j....
例如,如果`try`或`catch`块中有一个`return`语句,`finally`块将在`return`之前执行,但不会阻止`return`语句的执行。 关于字节码层面的解释,根据《深入Java虚拟机》一书的描述,`finally`块的实现是通过`jsr`...
- 当`try-catch-finally`块中包含`return`语句时,`finally`块总是会在`return`之前执行。但是,`finally`块的`return`语句会中断`try`块的`return`,使得控制流跳过`try`块的`return`直接返回`finally`的值。 ...
这段代码中的`testEx2()` 方法在循环中执行除法运算。当循环变量`i` 变为0时,会出现`ArithmeticException`。此时,程序将按照以下步骤执行: 1. `testEx2()` 中的`try` 块抛出异常。 2. `testEx2()` 的`catch` 块...
4. **return** 与 `try-catch-finally` 结合:如果 `try` 或 `catch` 里有 `return` 语句,`finally` 仍然会执行,但是 `return` 之后的代码不会被执行。在示例 `T2` 中,`finally` 里的 `return` 会覆盖掉 `try` 中...
8. try-catch-finally执行顺序:当try块中的return语句执行时,finally块的代码会在return之前执行。因此,选项C正确。 9. 异常处理和返回值:在try-catch-finally结构中,finally块的return语句总是最后执行,所以...
- 题1:`finally`块中的return语句会覆盖try-catch块中的return吗? - 题2:如果`finally`块中有throw语句,会怎样? - 题3:如何在`finally`块中避免覆盖已设置的返回值? 总的来说,Java异常处理机制使得代码更加...
- 当`try`块中的`return`语句执行时,会先执行`finally`块中的代码,然后才返回。因此,选项B是正确的,程序运行后会打印"Finally"。即使有`return`语句,`finally`块仍然会被执行,但是`finally`块之后的`output+=...