理解’finally’块
我写了七个测试用例来理解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++
(在trySeven ()中)不起作用?
一旦你做了返回,覆盖它的唯一方法是做另一个返回(如在Java中的finally块中返回 ,这几乎总是一个坏主意),或者突然完成。 你的测试永远不会从最终返回。
JLS§14.1定义了突然完成。 突然完成类型之一是返回。 由于返回,1,2,3,4和7中的try块突然完成。 正如§14.20.2所解释的那样 ,如果try块除了throw之外突然因R而完成,则立即执行finally块。
如果finally块正常完成(这意味着没有返回,除其他外),“try语句因为原因R而突然完成”。 换句话说,尝试启动的返回保持不变; 这适用于所有测试。 如果你从finally返回,“try语句因为原因S而突然完成(原因R被丢弃)。” (这里是新的重要回报)。
所以在tryOne中,如果你这样做:
finally { builder = null; return builder; }
这个新的返回S将覆盖原始返回R.
对于tryFour
builder.append("+1")
,请记住StringBuilder是可变的,因此您仍然会返回对try中指定的同一对象的引用。 你只是在做最后一分钟的突变。
tryFive
和trySix
都是直截了当的。 由于try中没有返回,try和finally都正常完成,并且执行方式就像没有try-finally一样。
让我们从更常见的用例开始 – 您必须关闭一个资源以避免泄漏。
public void deleteRows(Connection conn) throws SQLException { Statement statement = conn.createStatement(); try { statement.execute("DELETE * FROM foo"); } finally { statement.close(); } }
在这种情况下,我们必须在完成后关闭语句,因此我们不会泄漏数据库资源。 这将确保在抛出exception的情况下,我们将在函数退出之前始终关闭Statement。
try {…} finally {…}块用于确保在方法终止时始终执行某些操作。 它对Exception案例最有用。 如果你发现自己做了这样的事情:
public String thisShouldBeRefactored(List 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!"); } }
你最后没有正确使用。 对此有性能损失。 如果您有必须清理的exception情况,请坚持使用它。 尝试重构以上内容:
public String thisShouldBeRefactored(List 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”语句执行两个操作,一个设置函数的返回值,另一个退出函数。 通常这看起来像一个primefaces操作,但在try块中,它将导致finally块在设置返回值之后和函数退出之前执行。
返回执行:
- 分配返回值
- 最后跑块
- 退出function
示例一(原始):
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=null
的本地副本。 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; } }
或者,就语句实际执行的顺序而言(当然忽略可能的exception),它看起来更像是:
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块中显然令人困惑,所以如果你在这个问题上有任何选择,你最好重写一些东西以便所有你的返回语句在任何try-finally块之外。
StringBuilder不能为null,它需要一个字符串值。
使用字符串时,空参数通常很糟糕。 count ++未声明??? builder.append(“”)你要追加一个字符串 – 好。 计数=计数++;