Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development

Java's try-with-resources construct: another step nearer to C++

Because Java uses garbage collection for memory management, it has never been thought necessary to provide a class-level destructor mechanism. In C++, and most other object-oriented programming languages, the destructor is the logical counterpart of the constructor — it does whatever is necessary to clean up an object that was created earlier. When the object manipulates external resources — files, network connections — the destructor conventionally closes and frees these resources.

In C++, an object's destructor is called when the object is explicitly deleted, or when it goes out of scope. Consder the following C++ function, which uses the ifstream class to read and process a file.

using namespace std;

int processFile (const char *filename)
  {
  ifstream file (filename);
  if (!file.fail())
    {
    bool something = false;
    // Read file
    if (something)
      {
      // Finished procssing
      return 0;
      }
    }
  return -1;
  }
Although the program flow is pretty ugly, the marvellous thing about this way of using objects is that it is impossible to make a programming error that leaves the file open. Whether the function processFile exits at return 0, or return 1, or because an exception is thrown, the ifstream destructor is called and the file is closed. This use of complementary constructors and destructors is typical of C++ programming, and makes resource management much easier and less error-prone.

Now consider the Java way of doing the same thing, at least prior to Java 7.

  int processFile (String fileName)
    {
    FileInputStream fis = null;
    try
      {
      fis = new FileInputStream (fileName);
      boolean something = false;
      if (something)
        return 0;
      }
    catch (IOException e)
      {
      // Handle exception
      return -1;
      }
    finally
      {
      try
        {
        if (fis != null) fis.close();
        }
      catch (IOException e)
        {
        // Swallow useless exception
        }
      }
    return -1;
    }
This astonishingly ugly code is necessary because we must specifically close the file object fis once it has been opened, and we must do this whatever the program flow. It may be that an exception is thrown, or it may be that something in the file's contents require that processing be terminated early; but in any case we must keep track of the file and close it. In principle, the object's finalizer will close the file; but we can't predict when, or even if, the finalizer will be called — that depends on the whim of the garbage collector.

Note only is this code ugly, it is highly error-prone: when there are nested try-catch-finally blocks, it is easy to overlook closing some particular resource.

Java 7 has an new exception handling construct which simplifies this situation, and dramatically reduces both the ugliness and the fragility of the code. Here is the Java 7 equivalent:

 int processFile (String fileName)
    {
    try
      (
      FileInputStream fis = new FileInputStream (fileName);
      )
      {
      boolean something = false;
      if (something)
        return 0;
      }
    catch (IOException e)
      {
      // Handle exception
      }
   return -1;
    }
Notice that there is now no finally block, nor any obvious place where the object fis is told to close its open resources. Nevertheless, the method fis.close() will be called, and it will be called however we exit the method.

This new 'try-with-resources' mechanism works because the Java virtual machine (JVM) registers the objects that were instantiated in the opening of the try block (the block in parentheses), and calls the close() method on them when the exception handling block completes, regardless of the outcome. To make this work, the class that is instantiated must implement the AutoCloseable interface. To avoid the inevitable errors that would otherwise arise, the compiler will not allow any object to be instantiated in this part of the construct unless it is of a class that implements the necessary interface. In addition, not Java primitive type can be defined in this location.

It's worth pointing in passing that try-with-resources is not a smoke-and-mirrors implementation tricked up by the compiler (as anonymous inner classes are, for example). It is a new feature of the JVM and, as such, code that uses it won't run on JVMs earlier than Java 7.

Most (perhaps all) the classes defined in the standard Java runtime library do implement the new interface, and can be used in a try-with-resources construct. However, it is illustrative to define a simple class that implements AutoCloseable, and follow the flow of execution.
import java.io.IOException;

/** Class MyCloser demonstrates the use of the AutoCloseable interface */
class MyCloser implements AutoCloseable
  {
  MyCloser () throws IOException
    {
    System.out.println ("MyCloser constructor");
    }

  public void close ()
    {
    System.out.println ("MyCloser.close()");
    }

  public void doSomething() throws IOException
    {
    System.out.println ("Entering MyCloser.doSomething()");
    if (false) throw new IOException(); // Line 19
    System.out.println ("Leaving MyCloser.doSomething()");
    }
  }

/** Class Test uses MyCloser in a try-with-resources block */
public class Test
  {
  void go()
    {
    try
      (
      MyCloser mc = new MyCloser();
      )
      {
      System.out.println ("About to something");
      mc.doSomething();
      System.out.println ("Done something");
      }
    catch (IOException e)
      {
      System.out.println ("Caught IOException");
      }
    finally
      {
      System.out.println ("Finally");
      }
    System.out.println ("Left try construct");
    }

  public static void main (String[] args)
    {
    new Test().go();
    }
  }
As it currently stands, the method MyCloser.doSomething() will not throw an exception, so when the program runs, it produces the following output:
MyCloser constructor
About to something
Entering MyCloser.doSomething()
Leaving MyCloser.doSomething()
Done something
MyCloser.close()
Finally
Left try construct
Finally
What's significant about this output is that MyCloser.close() is automatically closed on exit from the try { ... } block, but before the finally { ... } block. This means that any code in the finally { ... } block that refers to MyCloser will be doing so after its resources have been freed. This is not a problem — it's just something to be aware of.

If we modify line 19 so that now an exception is thrown, the output is as follows:

MyCloser constructor
About to something
Entering MyCloser.doSomething()
MyCloser.close()
Caught IOException
Finally
Left try construct
Again, the close() method is called on exit from the try { ... } block, although this time the exit is because of an exception, not because execution has reached the end of the block.

So what we have here is, in effect, a scope-related destructor, just as we have in C++. The 'destructor', MyCloser.close() is closed as soon as execution leaves the try { ... } scope, regardless of the way or position in which it leaves.

For better or worse, Java is not providing a full scope-related destructor here. Most obviously, the auto-close mechanism only works within the context of a try-catch construct. In C++, we don't need to deal with exceptions at all to make use of scope-related destruction. Is that a limitation? Perhaps not — Java allows a try-with-resources block that does no exception handling at all, and the auto-close mechanism still works. Consider the textbook problem of acquiring a lock on some resource, and ensuring that the lock is released before the method ends, even if it ends abnormally. This has until now been difficult to implement in Java in an elegant way, but the try-with-resources construct makes it far more expressive, and less error-prone:

void process()
    {
    try
      (
      MyLock lock = new MyLock();
      )
      {
      lock.waitFor();
      // Do something
      if (something) return; // Early exit
      }
    // Carry on
    }
In this case, we're using try simply to define the scope of the MyLock instance, and thereby ensure that its close() method (which we arrange to release the lock) will always be closed, even if the method exits early.

Although the example above is not syntactically similar to the kind of code we might write in C++, I would argue that it is semantically very similar indeed.

Copyright © 1994-2013 Kevin Boone. Updated Feb 08 2013