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?
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:
finally {
builder = null;
return builder;
}
this new return S would override the original return R.
For builder.append("+1")
in tryFour
, 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
and trySix
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.
Let's start with use case you'll see more often - you have a resource that you must close to avoid a leak.
public void deleteRows(Connection conn) throws SQLException {
Statement statement = conn.createStatement();
try {
statement.execute("DELETE * FROM foo");
} finally {
statement.close();
}
}
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:
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!");
}
}
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:
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With