Java 101: The next generation: The essential Java language features tour, Part 4

project coin

Project Coin: Small language changes that cut the cost of productivity in JDK 7

Before JDK 7's debut, Oracle invited the Java community to submit proposals for new language features to its Project Coin mailing list. The open call ran for one month and nearly 70 proposal forms were submitted. Eight features were then selected for inclusion in JDK 7:

  • Automatic resource management (try-with-resources)
  • Switch-on-string
  • Multi-catch
  • Final re-throw
  • Binary literals
  • Underscores in numeric literals
  • Type inference changes
  • Simplified varargs method invocation

This installment in the Essential Java language features tour addresses the first four small language features from that list -- try-with-resources, switch-on-string, multi-catch, and final re-throw. I'll address the remaining four features in a follow-up article.

Code download
Download the source code for the JDK 7 language features tour.
Created by Jeff Friesen for JavaWorld.

Project Coin small change #1: The try-with-resources block

Java applications typically manipulate resources like files, database connections, and sockets, which means that the Java runtime must be able to access related system resources like file handles behind the scenes.

The scarcity of system resources like file handles implies that they must eventually be released, even when an error occurs. When system resources aren't freed, the application eventually fails when attempting to acquire other resources, because no more system resources are available.

The try-with-resources syntax is a small change that makes a big difference in Java programs. To understand why, let's first consider the pre-JDK 7 traditional approach.

Freeing resources in Java programs: The traditional approach

Java developers have been conditioned to invoke a resource object's close() method in the finally block of a try/catch/finally construct. However, this practice is often followed incorrectly, which can lead to resource leaks.

It's not hard to see why freeing resources is error prone when you consider that you often need to nest multiple try/catch/finally blocks, and the resulting boilerplate can make anyone's eyes glaze over. Consider the file-copy example in Listing 1.

Listing 1. Copy.java (version 1)


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Copy
{
   public static void main(String[] args)
   {
      if (args.length != 2)
      {
         System.err.println("usage: java Copy src dst");
         return;
      }

      try
      {
         copy(args[0], args[1]);
      }
      catch (FileNotFoundException fnfe)
      {
         System.err.printf("%s could not be found or is a directory, "+
                           "or %s couldn't be created because it "+
                           "might be a directory%n", args[0], args[1]);
      }
      catch (IOException ioe)
      {
         System.err.printf("%s could not be read or %s could not be "+
                           "written%n", args[0], args[1]);
      }
   }

   static void copy(String src, String dst) throws IOException
   {
      FileInputStream fis = new FileInputStream(src);
      try
      {
         FileOutputStream fos = new FileOutputStream(dst);
         try
         {
            int _byte;
            while ((_byte = fis.read()) != -1)
               fos.write(_byte);
         }
         finally
         {
            try
            {
               fos.close();
            }
            catch (IOException ioe)
            {
               System.err.printf("unable to close %s%n", dst);
            }
         }
      }
      finally
      {
         try
         {
            fis.close();
         }
         catch (IOException ioe)
         {
            System.err.printf("unable to close %s%n", src);
         }
      }
   }
}

The main() method above verifies that two command-line arguments (representing filenames) were specified and passes them to the copy() class method, which attempts to copy a source file to a destination file.

The copy() method first attempts to create a file input stream to the source file. If unsuccessful, an I/O exception is thrown and no system resources are allocated. It then attempts to create a file output stream to the destination file.

If a file output stream cannot be created, an I/O exception is thrown and no system resources are allocated. When the file input stream was created, however, system resources were allocated for that stream, so these must be freed. Resources for the input stream are freed in the outer finally block.

Assuming all is well, the file-copy code is executed. If an I/O error occurs, the inner finally block executes, which closes the file output stream. Then, the outer finally block is executed, which closes the file input stream.

When no I/O errors occur, the file streams and their underlying files are closed with the file output stream being closed first and the file input stream being closed last. System resources are released when these files are closed.

Although this code works, it's tedious to write and easy to get wrong; as I mentioned, bugs frequently cause resource leaks as well as other problems. While resource leaks aren't a big problem in a short-lived application, they are a serious problem in long-running applications such as web servers.

Automatic resource management

Automatic resource management (ARM) automatically closes open resources when execution leaves the scope in which those resources were opened and used. Java 7 implements ARM mainly via its try-with-resources statement, which has the following syntax:


try (resource acquisitions)
{
   // resource usage
}

The try keyword is parameterized by a semicolon-separated list of resource-acquisition statements, where each statement acquires a resource. Each acquired resource is available to the body of the try block, and is automatically closed when execution leaves this body.

Unlike the regular try statement, try-with-resources doesn't require catch blocks and/or a finally block, although they can be specified.

Consider the following JDBC-oriented example:


try (Connection con = DriverManager.getConnection(url))
{
   // do something with the connection
}

In this example, a database connection resource is acquired. The try block does something with this resource, and the connection is closed (and the resource released) upon exit from the try block.

Listing 2 demonstrates try-with-resources in an alternate version of the file-copy example, which demonstrates the acquisition of two file resources.

Listing 2. Copy.java (version 2)


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Copy
{
   public static void main(String[] args)
   {
      if (args.length != 2)
      {
         System.err.println("usage: java Copy src dst");
         return;
      }

      try
      {
         copy(args[0], args[1]);
      }
      catch (FileNotFoundException fnfe)
      {
         System.err.printf("%s could not be found or is a directory, "+
                           "or %s couldn't be created because it "+
                           "might be a directory%n", args[0], args[1]);
      }
      catch (IOException ioe)
      {
         System.err.printf("%s could not be read or %s could not be "+
                           "written%n", args[0], args[1]);
      }
   }

   static void copy(String src, String dst) throws IOException
   {
      try (FileInputStream fis = new FileInputStream(src);
           FileOutputStream fos = new FileOutputStream(dst))
      {
         int _byte;
         while ((_byte = fis.read()) != -1)
            fos.write(_byte);
      }
   }
}

The copy() method in Listing 2 includes a try-with-resources statement. The code within the round brackets following try attempts to create file input and output streams. Assuming success, the body of the try block executes, copying the source file to the destination file.

Whether an exception is thrown or not, try-with-resources ensures that both files are closed when execution leaves the try block. Because the boilerplate file-closing code that was shown in Listing 1 isn't needed, Listing 2's copy() method is much simpler.

Using automatic resource management with your classes

The try-with-resources statement requires that a resource class implement the java.lang.Closeable interface or its new java.lang.AutoCloseable superinterface. Pre-Java 7 classes like java.io.FileInputStream implemented Closeable, which offers a void close() method that throws java.io.IOException or a subclass.

Starting with Java 7, classes can implement AutoCloseable, whose single void close() method can throw java.lang.Exception or a subclass. The throws clause has been expanded to accommodate situations where you might need to add close() methods that can throw exceptions outside of the IOException hierarchy; for example, java.sql.SQLException.

Listing 3 presents a CustomARM application that shows you how to configure a custom class so that you can use it in a try-with-resources context.

Listing 3. CustomARM.java


public class CustomARM
{
   public static void main(String[] args)
   {
      try (USBPort usbp = new USBPort())
      {
         System.out.println(usbp.getID());
      }
      catch (USBException usbe)
      {
         System.err.println(usbe.getMessage());
      }
   }
}

class USBPort implements AutoCloseable
{
   USBPort() throws USBException
   {
      if (Math.random() < 0.5)
         throw new USBException("unable to open port");
      System.out.println("port open");
   }

   @Override
   public void close()
   {
      System.out.println("port close");
   }

   String getID()
   {
      return "some ID";
   }
}

class USBException extends Exception
{
   USBException(String msg)
   {
      super(msg);
   }
}

Listing 3 simulates a USB port in which you can open and close the port and return the port's ID. I've employed Math.random() in the constructor so that you can observe try-with-resources when an exception is thrown or not thrown.

Compile Listing 3 (javac CustomARM.java) and run the application (java CustomARM). If the port is open, you'll see the following output:


port open
some ID
port close

If the port is closed, you might see this:


unable to open port

Suppressing exceptions in try-with-resources

If you've had some programming experience, you might have noticed a potential problem with the try-with-resources statement: Suppose the try block throws an exception. This statement responds by invoking a resource object's close() method to close the resource. However, the close() method might also throw an exception.

When close() throws an exception (e.g., FileInputStream's void close() method can throw IOException), this exception masks or hides the original exception. It seems that the original exception is lost.

In fact, this isn't the case: try-with-resources suppresses close()'s exception. It also adds the exception to the original exception's array of suppressed exceptions by invoking java.lang.Throwable's void addSuppressed(Throwable exception) method.

Listing 4 presents a SupExDemo application that demonstrates how to repress an exception in a try-with-resources context.

Listing 4. SupExDemo.java


import java.io.Closeable;
import java.io.IOException;

public class SupExDemo implements Closeable
{
   @Override
   public void close() throws IOException
   {
      System.out.println("close() invoked");
      throw new IOException("I/O error in close()");
   }

   public void doWork() throws IOException
   {
      System.out.println("doWork() invoked");
      throw new IOException("I/O error in work()");
   }

   public static void main(String[] args) throws IOException
   {
      try (SupExDemo supexDemo = new SupExDemo())
      {
         supexDemo.doWork();
      }
      catch (IOException ioe)
      {
         ioe.printStackTrace();
         System.out.println();
         System.out.println(ioe.getSuppressed()[0]);
      }
   }
}

Listing 4's doWork() method throws an IOException to simulate some kind of I/O error. The close() method also throws the IOException, which is suppressed so that it doesn't mask doWork()'s exception.

The catch block accesses the suppressed exception (thrown from close()) by invoking Throwable's Throwable[] getSuppressed() method, which returns an array of suppressed exceptions. Only the first element is accessed because only one exception is suppressed.

Compile Listing 4 (javac SupExDemo.java) and run the application (java SupExDemo). You should observe the following output:


doWork() invoked
close() invoked
java.io.IOException: I/O error in work()
        at SupExDemo.doWork(SupExDemo.java:16)
        at SupExDemo.main(SupExDemo.java:23)
        Suppressed: java.io.IOException: I/O error in close()
                at SupExDemo.close(SupExDemo.java:10)
                at SupExDemo.main(SupExDemo.java:24)

java.io.IOException: I/O error in close()

Project Coin small change #2: Switch-on-string

Before Java 7, the switch statement could only switch on integral values and (starting with Java 5) enum constants. Java 7 enhanced switch to also support switching on strings. This extension involves specifying a string expression for switch and string literals for its cases.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more