Taming Tiger, Part 1

An introduction to Java 1.5

Welcome to the first of a three-part series on Sun Microsystems' latest release of the Java 2 Platform, Standard Edition (J2SE). J2SE 1.5—code-named Tiger—is the most significant revision to the Java language since its origination. With version 1.5, Java has been extended with several new features, and my goal with this series is to introduce you to the most important features.

Each article presents a detailed discussion of a handful of distinct features and provides practical examples of how they can be used in the real world. Part 1 is a quick introduction to J2SE 1.5 and covers many new additions. Part 2 will be devoted to generics, Java's counterpart to templates in C++ and a similar facility (also called generics) in C# 2.0. Part 3 focuses exclusively on J2SE 1.5's new metadata facility called annotations, which allow programmers to decorate Java code with their own attributes. These custom attributes can be used for code documentation, code generation, and, during runtime, to provide special services such as enhanced business-level security or special business logic.

Read the whole series: "Taming Tiger," Tarak Modi (JavaWorld):

As I mentioned before, J2SE 1.5 represents the largest overhaul of Java since its inception and contains many new and exciting features. The additions provide developers with a host of new capabilities. For example, the addition of enumerations and generics provides Java with compile-time type safety. That means programming or logic errors are caught during program compilation instead of during runtime. This may not seem to be a great benefit on the surface, especially for smaller projects. However, as your code's complexity increases and your project grows larger, it becomes more likely that parts of a program may not have been unit tested (I know this never happens on your project!), and after three months of smooth sailing in production, you answer a call at 2 a.m. that informs you of a java.lang.ClassCastException. Compile-time type safety catches all these errors during program compilation.

In this article, I discuss some of the new language features introduced in J2SE 1.5, including autoboxing and unboxing of primitives, the enhanced for loop, variable method arguments (varargs), the much anticipated enumerations, static imports, and the new formatted input method printf (a concept borrowed from C and C++).

All of these features are part of Java Specification Request 201. Without further ado, let's start with the first feature—autoboxing and unboxing.

Autoboxing and unboxing

Consider the following code fragment:

// A list that stores numbers
// Generics will make this obvious...
List numbers = new ArrayList();
numbers.add(89);

Prior to version 1.5, this code would not compile since ArrayList's add method expects an object. To make it work, you must modify the second line as follows:

   numbers.add(new Integer(89));

That seems unnecessary and, with J2SE 1.5, it is. With J2SE 1.5, the compiler automatically adds the code to convert the integer value (such as 89) into the proper class (Integer in our example). That is called boxing. The opposite process of converting an object (such as of type Integer) into a value (such as an int) is called unboxing. Boxing and unboxing eliminate the (frequent and often burdensome) need to explicitly convert data of primitive type to reference and vice versa. Such explicit required conversions are verbose and induce clutter in the program text. The following table shows the rules for boxing and unboxing.

Primitive type Reference type
booleanBoolean
byteByte
doubleDouble
shortShort
intInteger
longLong
floatFloat

The enhanced for loop

A common programming task is iterating over the elements of a collection or an array. An example is shown in the code fragment below:

// numbers is a list of Numbers
for (Iterator it = numbers.iterator(); it.hasNext(); ) 
{          
Integer number = (Integer) it.next();
        
    // Do something with the number...
}

The above code fragment, although effective, is cumbersome. Version 1.5 enhances the for statement to accept a more evolved syntax (the previous code still works) such as:

for(Integer number: numbers)
{
   // Do something with the number...
}

The above code actually compiles into code similar to the first code fragment. The enhanced for loop also works with arrays. Personally, I would have preferred a new keyword such as "foreach." This is actually a FAQ, and Sun has responded to it by saying that creating a new keyword would be too "costly." Sun has, however, added keywords before, such as the assert keyword in 1.4 and the new enum keyword (we'll see this one shortly) in 1.5. So, I am not too convinced by this answer. Finally, remember that if you wish to modify the collection (such as remove elements) while traversing it, you cannot use the enhanced for loop.

Variable method arguments and printf

Often, having a method that can operate on a variable number of parameters is convenient. A commonly accepted way of accomplishing that is to define the method so it accepts an array or collection of objects. An example code fragment is shown below:

// An example method that takes a variable number of parameters
int sum(Integer[] numbers)
{
   int mysum = 0;   
   for(int i: numbers)
      mysum += i;
      return mysum;
}
// Code fragment that calls the sum method
sum(new Integer[] {12,13,20});

While this proves effective in simulating a method that can take a variable number of parameters, it seems clunky and involves slightly more (unnecessary) work by the programmer. Fortunately, that is no longer required in version 1.5 thanks to the new variable arguments (varargs) feature. Let's rewrite the above code fragments using varargs.

// An example method that takes a variable number of parameters
int sum(Integer... numbers)
{
      int mysum = 0;    
      for(int i: numbers)
         mysum += i;
      return mysum;
} 
// Code fragment that calls the sum method
sum(12,13,20);

Note the change in the method signature. Instead of explicitly taking an array of Integer objects, we inform the compiler that a variable number of Integer objects will be passed using an ellipsis (...). Note that the actual code inside the method does not change. Finally, notice how much cleaner (and simpler) the method call becomes. A perfect example of where this feature has been used by version 1.5 itself is in the implementation of the C-style formatted output method printf:

// Using System.out.println and System.out.printf
int x = 5;
int y = 6;
int sum = x + y;
// Pre 1.5 print statement
System.out.println(x + " + " + y + " = " + sum);
// Using 1.5 printf
System.out.printf("%d + %d = %d\n", x, y, sum);

Both statements print the same line to the console—5 + 6 = 11. You can do a lot more with the extremely powerful and versatile printf method. Look at the following code fragment:

// Demonstrating printf versatility
System.out.printf("%02d + %02d = %02d\n", x, y, sum);

This line of code ensures that each number prints as two digits and, if the number is a single digit, then a zero (0) precedes it. So the output on the console now looks as follows: 05 + 06 = 11.

Enumerations

Consider the following class:

public Class Color
{
   public static int Red = 1;
   public static int White = 2;
   public static int Blue = 3;
}

You could use the class as shown in the following code fragment:

int myColor = Color.Red;

But, what is to prevent someone from doing the following?

int myColor = 999;

Obviously the compiler will not (and cannot) catch this error, which quite possibly will lead to a runtime exception.

Here's an alternative implementation of the Color class that solves the problem mentioned above:

// The new Color class
public class Color
{
    // Color value;
    int _color;
    
    // Constructor
    protected Color (int color) 
    {
        _color = color;
    }
    public static final int _Red = 1;
    public static final int _White = 2;
    public static final int _Blue = 3;
    public static final Color Red = new Color(_Red);
    public static final Color White = new Color(_White);
    public static final Color Blue = new Color(_Blue);
}

And here is how you would use it:

Color myColor = Color.Red;

The above implementation ensures that invalid colors cannot be specified. However, we did a lot of work and our solution remains limited. J2SE 1.5 provides a better solution with its addition of enumerations; a concept C and C++ programmers are already familiar with. Let's rewrite the Color class using enumerations:

// The color enumeration
public enum Color
{
   Red,
   White,
   Blue 
}

Notice the usage of the keyword enum instead of class. Also, notice how simple the implementation has become. Here's how you could use the Color enumeration in your code:

// Using the Color enumeration
Color myColor = Color.Red;

The usage is the same as the usage of our second example (above), but the enumeration is much more capable than our simple Color class implementation. For example, all enumerations have a static method called values() that returns all the enumeration values in a collection as shown in the code fragment below:

// Cycling through the values of an enumeration
for (Color c : Color.values())
            System.out.println(c);

The above code fragment produces the following output:

Red
White
Blue

All enumerations also have another static method called valueOf() that returns the enumeration constant corresponding to a string parameter. For example Color.valueOf("Red") returns Color.Red.

In addition, each enumeration has several useful instance methods. The name() method returns the enumeration constant's name, exactly as declared in its declaration. For example, Color.Red.name() returns Red. The toString() method returns the same value as name(), but can be overridden to provide a better and more friendlier or meaningful (and even internationalized) name. The ordinal() method returns the zero-based position of the declared constant. For example, Color.White.ordinal() returns 1. Finally, the compareTo() method compares the current enumeration object (i.e., this) with the specified enumeration object and returns a negative integer, zero, or a positive integer, depending on whether the current enumeration object is less than, equal to, or greater than the specified object, respectively. The comparison is based on the ordinal of the declared enumeration constants. For example, Color.Red.compareTo(Color.White) will return -1.

Enumerations can have constructors and methods as well. For example, let's say I wanted to provide a friendlier name than offered by the default toString() implementation. Here's an example that accomplishes that:

// The revised color enumeration
public enum Color
{
      Red("Red (#FF0000)"),
      White("White (#000000)"),
      Blue("Blue (#0000FF)"),
      Green("Green (#00FF00)")
      {
         public boolean isInUSFlag()
            {
               return false;
            }
      };
   
      private String newName;
      
      Color(String s)  
      {
         newName = s;
      }
      public String toString()
      {
         return newName;
      }
      public boolean isInUSFlag()
      {
         return true;
      }
}

Note that I also added a new color, Green, and a new method called isInUSFlag() that by default returns true. Since green is not in the US flag, I override the isInUSFlag() method when I declare the constant Green and return false from the overridden method implementation.

Static imports

The last new language feature explained in this article is the static import. Static imports are yet another convenience feature added to version 1.5 that extends the way imports work in Java. For example, consider the code fragment shown below that calls the static ceil() method on the java.lang.Math class

// x is a number of type double such as 5.345
double y = Math.ceil(x);

With static imports in 1.5, you can ask the Java compiler to import only a class's static portions, as shown, for example, in the rewritten code fragment below:

// Import declaration at the top of the class along with the other imports
import static java.lang.Math.ceil;
// And then somewhere in the code...
// x is a number of type double such as 5.345
double y = ceil(x);

In the above fragment, I used the new static import feature to import the static method ceil() from the Math class. Now when I call the method, I don't have to qualify it with Math. If I wanted to use multiple static methods from the Math class, I could import them individually or all at once as shown below:

// Import all static methods from Math
import static java.lang.Math.*;

This also applies to any constants declared within Math, such as E and PI. With the above declaration, I can use these constants as if they were declared locally within my class (or superclass).

Finally, static imports can be used with enumerations as well. For example, consider the following enumeration definition:

package myEnumerations;
public enum Color
{
   Red,
   White,
   Blue 
}

You may access this enumeration's values in (at least) one of the following two ways:

  1. Import myEnumerations.Color and reference Color.Red in your code
  2. Statically import myEnumerations.Color.* and reference Red in your code
1 2 Page 1