理解“最后”堵塞
我编写了七个测试用例来理解 finally
块的行为。 finally
工作背后的逻辑是什么?
package core;
public class Test {
public static void main(String[] args) {
new Test().testFinally();
}
public void testFinally() {
System.out.println("One = " + tryOne());
System.out.println("Two = " + tryTwo());
System.out.println("Three = " + tryThree());
System.out.println("Four = " + tryFour());
System.out.println("Five = " + tryFive());
System.out.println("Six = " + trySix());
System.out.println("Seven = " + trySeven());
}
protected StringBuilder tryOne() {
StringBuilder builder = new StringBuilder();
try {
builder.append("Cool");
return builder.append("Return");
}
finally {
builder = null;
}
}
protected String tryTwo() {
String builder = "Cool";
try {
return builder += "Return";
}
finally {
builder = null;
}
}
protected int tryThree() {
int builder = 99;
try {
return builder += 1;
}
finally {
builder = 0;
}
}
protected StringBuilder tryFour() {
StringBuilder builder = new StringBuilder();
try {
builder.append("Cool");
return builder.append("Return");
}
finally {
builder.append("+1");
}
}
protected int tryFive() {
int count = 0;
try {
count = 99;
}
finally {
count++;
}
return count;
}
protected int trySix() {
int count = 0;
try {
count = 99;
}
finally {
count = 1;
}
return count;
}
protected int trySeven() {
int count = 0;
try {
count = 99;
return count;
}
finally {
count++;
}
}
}
为什么 builder = null
不起作用?
为什么 builder.append("+1")
可以工作,而 count++
( in trySeven()) 却不能工作?
I've written seven test cases for understanding the behavior of the finally
block. What is the logic behind how finally
works?
package core;
public class Test {
public static void main(String[] args) {
new Test().testFinally();
}
public void testFinally() {
System.out.println("One = " + tryOne());
System.out.println("Two = " + tryTwo());
System.out.println("Three = " + tryThree());
System.out.println("Four = " + tryFour());
System.out.println("Five = " + tryFive());
System.out.println("Six = " + trySix());
System.out.println("Seven = " + trySeven());
}
protected StringBuilder tryOne() {
StringBuilder builder = new StringBuilder();
try {
builder.append("Cool");
return builder.append("Return");
}
finally {
builder = null;
}
}
protected String tryTwo() {
String builder = "Cool";
try {
return builder += "Return";
}
finally {
builder = null;
}
}
protected int tryThree() {
int builder = 99;
try {
return builder += 1;
}
finally {
builder = 0;
}
}
protected StringBuilder tryFour() {
StringBuilder builder = new StringBuilder();
try {
builder.append("Cool");
return builder.append("Return");
}
finally {
builder.append("+1");
}
}
protected int tryFive() {
int count = 0;
try {
count = 99;
}
finally {
count++;
}
return count;
}
protected int trySix() {
int count = 0;
try {
count = 99;
}
finally {
count = 1;
}
return count;
}
protected int trySeven() {
int count = 0;
try {
count = 99;
return count;
}
finally {
count++;
}
}
}
Why builder = null
is not working?
Why does builder.append("+1")
work whereas count++
( in trySeven()) does not work?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
一旦你执行了返回,覆盖它的唯一方法是执行另一个返回(如 从 Java 中的 finally 块返回,这几乎总是一个坏主意),或者突然完成。你的测试永远不会从finally中返回。
JLS §14.1 定义突然完成。突然完成类型之一是返回。 1、2、3、4 和 7 中的 try 块由于返回而突然完成。正如 §14.20.2,如果 try 块由于除 throw 之外的原因 R 突然完成,则立即执行 finally 块。
如果finally块正常完成(这意味着没有返回等),“try语句由于原因R而突然完成”。换句话说,由 try 发起的返回保持不变;这适用于您的所有测试。如果从finally返回,“try语句由于原因S突然完成(并且原因R被丢弃)”。 (这里的 S 是新的最重要的回报)。
因此,在 tryOne 中,如果您这样做:
这个新的返回 S 将覆盖原始返回 R。
对于
tryFour
中的builder.append("+1")
,请记住 StringBuilder是可变的,因此您仍然返回对 try 中指定的同一对象的引用。你只是在做最后一刻的突变。tryFive
和trySix
很简单。由于try中没有return,所以try和finally都正常完成,执行起来就和没有try-finally一样。Once you do the return, the only way to override that is to do another return (as discussed at Returning from a finally block in Java, this is almost always a bad idea), or otherwise complete abruptly. Your tests don't ever return from a finally.
JLS §14.1 defines abrupt completion. One of the abrupt completion types is a return. The try blocks in 1,2,3,4, and 7 abruptly complete due to returns. As explained by §14.20.2, if the try block completes abruptly for a reason R besides a throw, the finally block is immediately executed.
If the finally block completes normally (which implies no return, among other things), "the try statement completes abruptly for reason R.". In other words, the return initiated by the try is left intact; this applies to all your tests. If you return from the finally, "the try statement completes abruptly for reason S (and reason R is discarded)." (S here being the new overriding return).
So in tryOne, if you did:
this new return S would override the original return R.
For
builder.append("+1")
intryFour
, keep in mind StringBuilder is mutable, so you're still returning a reference to the same object specified in the try. You're just doing a last minute mutation.tryFive
andtrySix
are straight-forward. Since there is no return in the try, the try and finally both complete normally, and it executes the same as if there was no try-finally.让我们从您经常看到的用例开始 - 您有一个必须关闭以避免泄漏的资源。
在这种情况下,我们必须在完成后关闭语句,这样就不会泄漏数据库资源。这将确保在抛出异常的情况下,我们始终会在函数退出之前关闭语句。
try { ... } finally { ... } 块旨在确保在方法终止时始终执行某些内容。它对于异常情况最有用。如果你发现自己在做这样的事情:
你并没有真正正确地使用finally。这会带来性能损失。当您遇到必须清除的异常情况时,请坚持使用它。尝试将上面的内容重构为:
Let's start with use case you'll see more often - you have a resource that you must close to avoid a leak.
In this case, we have to close the statement when we're done, so we don't leak database resources. This will ensure that in the case of an Exception being thrown, we will always close our Statement before the function exits.
try { ... } finally { ... } blocks are meant for ensuring that something will always execute when the method terminates. It's most useful for Exception cases. If you find yourself doing something like this:
You're not really using finally properly. There is a performance penalty to this. Stick to using it when you have Exception cases that you must clean up from. Try refactoring the above to this:
当你离开 try 块时,finally 块就会被执行。 “return”语句做了两件事,一是设置函数的返回值,二是退出函数。通常这看起来像一个原子操作,但在 try 块内,它将导致finally 块在设置返回值之后、函数退出之前执行。
返回执行:
示例一(原始):
示例二(参考):
示例三(参考):
示例四(返回):
The finally block is executed when you leave the try block. The "return" statement does two things, one it sets the return value of the function and two it exits the function. Normally this would look like an atomic operation but within a try block it will cause the finally block to execute after the return value was set and before the function exits.
Return execution:
Example one (primitive):
Example two(reference):
Example three (reference):
Example four (return):
builder = null
和builder.append("+1")
正在工作。只是它们不会影响您返回的内容。该函数返回return
语句所包含的内容,无论之后发生什么。存在差异的原因是
builder
是通过引用传递的。builder=null
更改builder
的本地副本。builder.append("+1")
影响父级保存的副本。builder = null
andbuilder.append("+1")
are working. It's just that they're not affecting what you're returning. The function returns what thereturn
statement has, regardless of what happens afterward.The reason there is a difference is because
builder
is passed by reference.builder=null
changes the local copy ofbuilder
.builder.append("+1")
affects the copy held by the parent.为什么
builder = null
不起作用?因为您将本地引用设置为null,这不会更改内存的内容。所以它是有效的,如果你尝试在finally块之后访问构建器,那么你会得到null。
为什么
builder.append("+1")有效?
因为你是使用引用修改内存的内容,这就是它应该工作的原因。
为什么
count++
在 testFive() 中不起作用?它对我来说工作得很好。它按预期输出 100。
Why
builder = null
is not working?Because you are setting the local reference to null which will not change the content of the memory. So it is working, if you try to access the builder after finally block then you'll get null.
Why
builder.append("+1") work?
Because you are modifying the content of the memory using the reference,that's why it should work.
Why
count++
does not work in testFive()?It is working fine with me. It outputs 100 as expected.
考虑一下编译器实际上对 return 语句做了什么,例如在 tryOne() 中:它将对
builder
的引用复制回调用函数的环境。完成此操作后,但在控制权返回到调用函数之前,finally 块将执行。因此,在实践中,您的情况更像是这样的:或者,就语句实际执行的顺序而言(当然,忽略可能的异常),它看起来更像是这样:
所以设置
builder = null
确实运行了,它只是没有做任何有用的事情。但是,运行builder.append("something")
将会产生明显的效果,因为 temp 和 builder 都引用同一个(可变)对象。同样,trySeven() 中实际发生的情况更像是这样:
在这种情况下,由于我们处理的是 int,因此副本是独立的,因此递增一个不会影响另一个。
尽管如此,事实仍然是,将 return 语句放在 try-finally 块中显然很令人困惑,因此,如果您在这件事上有任何选择,最好重写一些内容,以便所有 return 语句位于任何 try-finally 块之外。
Consider what the compiler is actually doing for the return statement, for instance in tryOne(): it copies a reference to
builder
back to the calling function's environment. After it's done this, but before control goes back to the calling function, the finally block executes. So you have something more like this, in practice:Or, in terms of the order that statements actually get executed (ignoring possible exceptions, of course), it looks more like this:
So setting
builder = null
does run, it just doesn't do anything useful. However, runningbuilder.append("something")
will have a visible effect, since both temp and builder refer to the same (mutable) object.Likewise, what's really happening in trySeven() is something more like this:
In this case, since we're dealing with an int, the copies are independent, so incrementing one doesn't affect the other.
All that said, the fact remains that putting return statements in a try-finally block is quite clearly confusing, so if you've got any kind of choice in the matter, you'd be better off rewriting things so that all your return statements are outside any try-finally blocks.