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.

The ability to switch on strings simplifies source code by eliminating (potentially) lengthy if-else-if-else... sequences. Checking command-line options is one place where you'll find this simplification helpful. Listing 5 demonstrates.

Listing 5. Touch.java


import java.io.File;

import java.util.Date;

public class Touch
{
   static Date current = new Date(0);

   public static void main(String[] args)
   {
      for (String arg: args)
         switch (arg)
         {
            case "-c":
            case "-C": current = new Date();
                       break;

            case "-h":
            case "-H": help();
                       break;

         }
      touch(new File("."));
   }

   static void help()
   {
      String helpInfo = "Touch files with the same timestamp.\n"+
                        "\n"+
                        "Command-line options:\n"+
                        "\n"+
                        "-c | -C use current timestamp\n"+
                        "-h | -H output this help message\n";
      System.out.println(helpInfo);
   }

   static void touch(File start)
   {
      File[] files = start.listFiles();
      for (int i = 0; i < files.length; i++)
      {
         files[i].setLastModified(current.getTime());
         if (files[i].isDirectory())
            touch(files[i]);
      }
   }
}

The Touch application in Listing 5 recursively sets the timestamp of all files in the current directory and its subdirectories to the Unix epoch or the current timestamp. The main() method uses the switch-on-string feature to process command-line arguments.

Be careful to not pass a String variable containing the null reference to switch. Otherwise, java.lang.NullPointerException will be thrown. For example, the following code fragment results in NullPointerException:


String s = null;
switch (s)
{
   default: System.out.println("s is null");
}

Project Coin small change #3: Multi-catch

Java 7 gives you the ability to codify a single catch block to catch more than one type of exception. The purpose of this multi-catch feature is to reduce code duplication and reduce the temptation to catch overly broad exceptions (for instance, catch (Exception ex)).

Suppose you've developed an application that gives you the flexibility to copy data to a database or file. Listing 6 presents a CopyToDatabaseOrFile class that simulates this situation, and demonstrates the problem with catch block code duplication.

Listing 6. CopyToDatabaseOrFile.java


import java.io.IOException;

import java.sql.SQLException;

public class CopyToDatabaseOrFile
{
   public static void main(String[] args)
   {
      try
      {
         copy();
      }
      catch (IOException ex)
      {
         System.out.println(ex.getMessage());
         // additional handler code
      }
      catch (SQLException ex)
      {
         System.out.println(ex.getMessage());
         // additional handler code that's identical to the previous
handler's
         // code
      }
   }

   static void copy() throws IOException, SQLException
   {
      if (Math.random() < 0.5)
         throw new IOException("cannot copy to file");
      else
         throw new SQLException("cannot copy to database");
   }
}

Java 7 overcomes this code duplication problem by letting you specify multiple exception types in a catch block where each successive type is separated from its predecessor by placing a vertical bar (|) between these types:


try
{
   copy();
}
catch (IOException | SQLException ex)
{
   System.out.println(ex.getMessage());
}

Now, when copy() throws either exception, the exception will be caught and handled by the catch block.

When multiple exception types are listed in a catch block's header, the parameter is implicitly regarded as final. As a result, you cannot change the parameter's value. For example, you cannot change the reference stored in the previous code fragment's ex parameter.

Project Coin small change #4: Final re-throw

Java 7's compiler analyzes re-thrown exceptions more precisely than its predecessors, but only when no assignments are made to the re-thrown exception's catch block parameter (the parameter is effectively final). When an exception originates from the preceding try block and is a supertype/subtype of the parameter's type, the compiler throws the actual type of the caught exception instead of throwing the type of the parameter (as is done in previous versions of Java).

The purpose of this final re-throw feature is to facilitate adding a try-catch statement around a block of code to intercept, process, and re-throw an exception without affecting the statically determined set of exceptions thrown from the code. Also, this feature lets you provide a common exception handler to partly handle the exception close to where it's thrown, and provide more precise handlers elsewhere that handle the re-thrown exception. Consider Listing 7.

Listing 7. MonitorEngine.java


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

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

public class MonitorEngine
{
   public static void main(String[] args)
   {
      try
      {
         monitor();
      }
      catch (Exception e)
      {
         if (e instanceof PressureException)
            System.out.println("correcting pressure problem");
         else
            System.out.println("correcting temperature problem");
      }
   }

   static void monitor() throws Exception
   {
      try
      {
         if (Math.random() < 0.1)
            throw new PressureException("pressure too high");
         else
         if (Math.random() > 0.9)
            throw new TemperatureException("temperature too high");
         else
            System.out.println("all is well");
      }
      catch (Exception e)
      {
         System.out.println(e.getMessage());
         throw e;
      }
   }
}

Listing 7 simulates the testing of an experimental rocket engine to see if the engine's pressure or temperature exceeds a safety threshold. It performs this testing via the monitor() helper method.

monitor()'s try block throws PressureException when it detects a pressure extreme, and throws TemperatureException when it detects a temperature extreme. (Because this is only a simulation, random numbers are used.) The try block is followed by a catch block designed to partly handle the exception by outputting a warning message. This exception is then re-thrown so that monitor()'s calling method can finish handling the exception.

Before Java 7 you couldn't specify PressureException and TemperatureException in monitor()'s throws clause because the catch block's e parameter is of type java.lang.Exception and re-throwing an exception was treated as throwing the parameter's type. Starting with Java 7, you can specify these exception types in the throws clause because the compiler determines that the exception thrown by throw e came from the try block, and only PressureException and TemperatureException can be thrown from this block.

Because you can now specify static void monitor() throws PressureException, TemperatureException, you can provide more precise handlers where monitor() is called, as the following code fragment demonstrates:


try
{
   monitor();
}
catch (PressureException pe)
{
   System.out.println("correcting pressure problem");
}
catch (TemperatureException te)
{
   System.out.println("correcting temperature problem");
}

Because of the improved type checking offered by final re-throw, source code that compiled under previous versions of Java might fail to compile under Java 7. For example, consider Listing 8.

Listing 8. BreakageDemo.java


class SuperException extends Exception
{
}

class SubException1 extends SuperException
{
}

class SubException2 extends SuperException
{
}

public class BreakageDemo
{
   public static void main(String[] args) throws SuperException
   {
      try
      {
         throw new SubException1();
      }
      catch (SuperException se)
      {
         try
         {
            throw se;
         }
         catch (SubException2 se2)
         {
         }
      }
   }
}

Listing 8 compiles under Java 6 and earlier. However, it fails to compile under Java 7 (and later), whose compiler detects and reports the fact that SubException2 is never thrown in the body of the corresponding try statement. This is a small problem that you are unlikely to encounter in your programs, and a worthwhile trade-off for having the compiler detect a source of redundant code. Removing redundancies results in cleaner code and smaller classfiles, which is exactly the kind of productivity gain that the small changes in Project Coin were designed for.

In conclusion

Project Coin was cleverly named for its "small changes" theme, emphasizing manageable updates that could add up in terms of developer productivity. I've introduced four of these additions to the Java language in this article:

  • try-with-resources removes the error-prone task of having to explicitly close resources whether an exception is thrown or not;
  • switch-on-string simplifies source code by eliminating (potentially) lengthy if-else-if-else... sequences;
  • multi-catch lets a single catch block catch more than one type of exception to reduce code duplication and reduce the temptation to catch overly broad exceptions;
  • final re-throw facilitates adding a try-catch statement around a block of code to intercept, process, and re-throw an exception without affecting the statically determined set of exceptions thrown from the code.

My next article will complete the JDK 7 leg of the Essential Java language features tour with four more language features introduced in Java 7.

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