题
我编写了七个测试用例来理解 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++
( 在 尝试七()) 做 不是 工作?
解决方案
一旦你执行了返回,覆盖它的唯一方法是执行另一个返回(如讨论 从 Java 中的 finally 块返回, ,这几乎总是一个坏主意),或者突然完成。你的测试永远不会从finally中返回。
JLS§14.1 定义突然完成。突然完成类型之一是返回。1、2、3、4 和 7 中的 try 块由于返回而突然完成。正如§所解释的14.20.2, ,如果 try 块由于除 throw 之外的原因 R 突然完成,则立即执行 finally 块。
如果finally块正常完成(这意味着没有返回等),“try语句由于原因R而突然完成”。换句话说,由 try 发起的返回保持不变;这适用于您的所有测试。如果您从最后返回,“ Try语句由于原因S突然完成(以及Reason R被丢弃)。” (这是新的重大回报)。
所以在 tryOne 中,如果你这样做了:
finally {
builder = null;
return builder;
}
这个新的回报 S 将覆盖原来的回报 R。
为了 builder.append("+1")
在 tryFour
, ,请记住 StringBuilder 是可变的,因此您仍然返回对 try 中指定的同一对象的引用。你只是在做最后一刻的突变。
tryFive
和 trySix
都是直截了当的。由于try中没有return,所以try和finally都正常完成,执行起来和没有try-finally一样。
其他提示
让我们从您更常看到的用例开始 - 您拥有一个资源 必须 关闭以避免泄漏。
public void deleteRows(Connection conn) throws SQLException {
Statement statement = conn.createStatement();
try {
statement.execute("DELETE * FROM foo");
} finally {
statement.close();
}
}
在这种情况下,我们必须在完成后关闭语句,这样就不会泄漏数据库资源。这将确保在抛出异常的情况下,我们始终会在函数退出之前关闭语句。
尝试 { ...} 最后 { ...} 块旨在确保方法终止时始终执行某些内容。它对于异常情况最有用。如果你发现自己在做这样的事情:
public String thisShouldBeRefactored(List<String> foo) {
try {
if(foo == null) {
return null;
} else if(foo.length == 1) {
return foo.get(0);
} else {
return foo.get(1);
}
} finally {
System.out.println("Exiting function!");
}
}
你并没有真正正确地使用finally。这会带来性能损失。当您遇到必须清除的异常情况时,请坚持使用它。尝试将上面的内容重构为:
public String thisShouldBeRefactored(List<String> foo) {
final String result;
if(foo == null) {
result = null;
} else if(foo.length == 1) {
result = foo.get(0);
} else {
result = foo.get(1);
}
System.out.println("Exiting function!");
return result;
}
当你离开 try 块时,finally 块就会被执行。“return”语句做了两件事,一是设置函数的返回值,二是退出函数。通常这看起来像一个原子操作,但在 try 块内,它将导致finally 块在设置返回值之后、函数退出之前执行。
返回执行:
- 分配返回值
- 最后运行块
- 退出功能
示例一(原始):
int count = 1;//Assign local primitive count to 1
try{
return count; //Assign primitive return value to count (1)
}finally{
count++ //Updates count but not return value
}
示例二(参考):
StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
return sb;//return a reference to StringBuilder
}finally{
sb.append("hello");//modifies the returned StringBuilder
}
示例三(参考):
StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
return sb;//return a reference to StringBuilder
}finally{
sb = null;//Update local reference sb not return value
}
示例四(返回):
int count = 1; //assign count
try{
return count; //return current value of count (1)
}finally{
count++; //update count to two but not return value
return count; //return current value of count (2)
//replaces old return value and exits the finally block
}
builder = null
和 builder.append("+1")
是 在职的。只是它们不会影响您返回的内容。该函数返回什么 return
声明已经发生,无论之后发生什么。
之所以存在差异是因为 builder
通过引用传递。 builder=null
改变了 当地的 备份 builder
. builder.append("+1")
影响父母持有的副本。
为什么 builder = null
不管用?
因为您将本地引用设置为 null,这不会更改内存的内容。所以它正在工作,如果您尝试在finally块之后访问构建器,那么您将得到null。
为什么 builder.append("+1") work?
因为您正在使用引用修改内存的内容,所以它应该起作用。
为什么 count++
在 testFive() 中不起作用?
它对我来说工作得很好。它按预期输出 100。
考虑编译器实际上对 return 语句做了什么,例如在 tryOne() 中:它复制了对的引用 builder
返回到调用函数的环境。完成此操作后,但在控制权返回到调用函数之前,finally 块将执行。所以在实践中你有更多类似这样的东西:
protected StringBuilder tryOne() {
StringBuilder builder = new StringBuilder();
try {
builder.append("Cool");
builder.append("Return");
StringBuilder temp = builder;
return temp;
} finally {
builder = null;
}
}
或者,就语句实际执行的顺序而言(当然忽略可能的异常),它看起来更像是这样:
protected StringBuilder tryOne() {
StringBuilder builder = new StringBuilder();
builder.append("Cool");
builder.append("Return");
StringBuilder temp = builder;
builder = null;
return temp;
}
所以设置 builder = null
确实运行了,它只是没有做任何有用的事情。然而,运行 builder.append("something")
将要 具有可见的效果,因为 temp 和 builder 都引用相同的(可变)对象。
同样,trySeven() 中实际发生的情况更像是这样:
protected int trySeven() {
int count = 0;
count = 99;
int temp = count;
count++;
return temp;
}
在本例中,由于我们处理的是 int,因此副本是独立的,因此递增一个副本不会影响另一个副本。
尽管如此,事实仍然是,将 return 语句放在 try-finally 块中显然很令人困惑,因此,如果您在这件事上有任何选择,最好重写一些内容,以便所有 return 语句位于任何 try-finally 块之外。