Java 101: The Next Generation

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

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

Java 101: The Next Generation

Show More
1 2 Page 2
Page 2 of 2

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.

1 2 Page 2
Page 2 of 2