Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the 'finally' block

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?

like image 353
jai Avatar asked Jul 12 '10 05:07

jai


2 Answers

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.

like image 97
Matthew Flaschen Avatar answered Sep 23 '22 03:09

Matthew Flaschen


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;
}
like image 45
Robert Roland Avatar answered Sep 23 '22 03:09

Robert Roland