Java 101: The Next Generation

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

Autoboxing and unboxing, enhanced for loops, static imports, varargs, and covariant return types

4-5-6 hopscotch
Credit: Thinkstock

Java 101: The Next Generation

Show More

Complete your tour of JDK 5's language features by exploring autoboxing and unboxing, the enhanced for loop, static imports, varargs, and covariant return types.

This article wraps up my three-part focus on new features introduced in JDK 5, this time looking at a number of features that have improved the overall productivity of coding in Java. Keep reading to learn more about autoboxing and unboxing, the enhanced for loop, static imports, varargs, and covariant return types.

As in previous articles, I'll start with a quick introduction, followed by at least one programming example and tips for making the most of these features in your programs. I also reveal some pitfalls that you'll want to avoid.


Source code for "Java 101: The Next Generation: The essential Java language features tour, Part 3." Created by Jeff Friesen for JavaWorld.

Autoboxing and unboxing

Java 5's autoboxing and unboxing language feature facilitates storing primitive type values in collections. A primitive value is stored in a collection by boxing (wrapping) the value in a suitable wrapper class object (e.g., an int is wrapped in a java.lang.Integer), which is stored. A primitive value is retrieved from a collection by retrieving the wrapper class object and unboxing (unwrapping) its value.

Behind the scenes, autoboxing places a primitive value into an object of the appropriate wrapper class type whenever a primitive type is specified but a reference is required. For example, the following code fragment boxes true and 27 into java.lang.Boolean and Integer wrapper objects, respectively, and assigns these objects' references to variables b and i:

Boolean b = true; // implemented as Boolean b = Boolean.valueOf(true);
Integer i = 27; // implemented as Integer i = Integer.valueOf(27);

Unboxing, meanwhile, obtains a primitive value from a wrapper object whenever a reference is specified but a primitive type is required. This task is accomplished by invoking a suitable wrapper method, such as Boolean's booleanValue() method and Integer's intValue() method. The code fragment below uses these methods to unbox a Boolean and an Integer:

boolean b2 = b; // implemented as boolean b2 = b.booleanValue();
int i2 = i; // implemented as int i2 = i.intValue();

The code fragments reveal that primitive values appear to be equivalent to objects. There is less source code, which is easier to read. These benefits are more pronounced when working with the Java Collections Framework. For example, consider Listing 1's word-counting application, which counts words read from the standard input stream and stores unique words and their frequency counts in a map.

Listing 1. BoxDemo.java (version 1)

import java.io.IOException;

import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

public class BoxDemo
{
   public static void main(String[] args) throws IOException
   {
      int ch;
      Map<String, Integer> m = new TreeMap<String, Integer>();

      // Read each character from standard input until a letter
      // is read. This letter indicates the start of a word.

      while ((ch = System.in.read()) != -1)
      {
         // If character is a letter then start of word detected.

         if (Character.isLetter((char) ch))
         {
             // Create StringBuilder object to hold word letters.

             StringBuilder sb = new StringBuilder();

             // Place first letter character into StringBuilder object.

             sb.append((char) ch);

             // Place all subsequent letter characters into StringBuilder
             // object.

             do
             {
                ch = System.in.read();
                if (Character.isLetter((char) ch))
                    sb.append((char) ch);
                else
                    break;
             }
             while (true);

             // Insert word into map.

             String word = sb.toString();
             if (m.get(word) == null)
                 m.put(word, 1);
             else
                 m.put(word, m.get(word)+1);
         }
      }

      // Output map in ascending order.

      Iterator i = m.entrySet().iterator();
      while (i.hasNext())
         System.out.println(i.next());
   }
}

Listing 1's Map<String, Integer> m = new TreeMap<String, Integer>(); statement uses generics to map String keys to Integer values, where a word is the key and a frequency count is the value. The code fragment that adds words to this java.util.Map instance is simplified because of autoboxing and unboxing:

if (m.get(word) == null)
   m.put(word, 1);
else
   m.put(word, m.get(word)+1);

The code fragment first determines if an instance of the word already exists in the map. This is the case when m.get(word), which returns the Integer value associated with the given word, returns the null reference. In this situation, m.put(word, 1); boxes 1 into an Integer object and creates a new Map entry that stores both the word and this object.

If m.get(word) returns an Integer, the code fragment invokes m.put(word, m.get(word)+1); to unbox the Integer that m.get(word) returns, add 1 to the resulting int, box the new sum into an Integer, and store this updated frequency count object in the appropriate Map entry.

Although you can largely ignore the distinction between a primitive type and its wrapper type, you need to remember these three things:

  • Unboxing throws java.lang.NullPointerException when the wrapper object is null.
  • The == and != operators perform reference identity comparisons on wrapper expressions and value equality comparisons on primitive type expressions.
  • There are performance implications to autoboxing and unboxing when used excessively.

Autoboxing/unboxing and arithmetic/comparison operators

Java automatically unboxes wrapper objects in expressions involving arithmetic and comparison operators (except for == and !=). This behavior can lead to confusion unless you know what's happening. Consider Listing 2.

Listing 2. BoxDemo.java (version 2)

public class BoxDemo
{
   public static void main(String[] args)
   {
      Long l1 = 127L;
      Long l2 = 127L;
      System.out.println(l1 == l2);
      System.out.println(l1 < l2);
      System.out.println(l1 > l2);
      System.out.println(l1+l2);
      l1 = 128L;
      l2 = 128L;
      System.out.println(l1 == l2);
      System.out.println(l1 < l2);
      System.out.println(l1 > l2);
      l2 = 129L;
      System.out.println(l1 < l2);
      System.out.println(l1+l2);
   }
}

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

true
false
false
254
false
false
false
true
257

The false value that's output by System.out.println(l1 == l2); where each of l1 and l2 is assigned 128L is surprising. After all, true is output when these objects contain 127L.

The discrepancy arises because == compares references (whereas < and > compare primitive values) and the valueOf() method (used behind the scenes in the Long l1 = 127L; and Long l2 = 127L; assignments) employs caching.

Each of the java.lang.Byte, java.lang.Short, Integer, and java.lang.Long wrapper classes provides a valueOf() method that stores its primitive argument value in a wapper object of the appropriate type and returns a reference to this object.

To minimize object creation, valueOf() first examines its argument to see if it falls within a specified range (e.g., Long's valueOf() method checks its argument to see if it's greater than or equal to -128 and less than or equal to 127). If so, the wrapper object that wraps this value is returned from the cache. Otherwise, a new wrapper object is created that wraps the value and is returned. The following code fragment reveals this behavior for Long's valueOf() method (in the Java 7 update 6 implementation):

public static Long valueOf(long l)
{
   final int offset = 128;
   if (l >= -128 && l <= 127) { // will cache
      return LongCache.cache[(int)l + offset];
   }
   return new Long(l);
}

In Listing 2 (above), 127L is cached so l1 and l2 reference the same wrapper object, and l1 == l2 evaluates to true. In contrast, 128L isn't cached so l1 and l2 reference different wrappers objects, and l1 == l2 evaluates to false.

Enhanced for loop

Java 5 introduced the enhanced for loop as a more compact way to iterate over a collection. Instead of explicitly obtaining the collection's iterator and invoking its hasNext() and next() methods to perform the iteration, which clutters source code and introduces opportunities for errors, you can more easily iterate over a collection via the following syntax:

for (type id: c)

Reference type type matches the element type of collection c. Also, variable id receives the next element from c during each loop iteration. Because the colon can be interpreted as in, the syntax reads "for each id in c."

Listing 3 uses the enhanced for loop to iterate over a list of employees.

Listing 3. EnForLoopDemo.java (version 1)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

class Employee
{
   private String name;
   private int age;

   Employee(String name, int age)
   {
      this.name = name;
      this.age = age;
   }

   public String toString()
   {
      return name+" "+age;
   }
}

public class EnForLoopDemo
{
   public static void main(String[] args)
   {
      List<Employee> employees = new ArrayList<Employee>();
      employees.add(new Employee("John Doe", 25));
      employees.add(new Employee("Sally Smith", 32));

      // Traditional iteration.

      for (Iterator i = employees.iterator(); i.hasNext();)
           System.out.println(i.next());

      System.out.println();

      // Enhanced for loop iteration.

      for (Employee employee: employees)
           System.out.println(employee);
   }
}

Listing 3 reveals the verbosity of the traditional approach to iteration. It also reveals the three places where an error can be introduced: the assignment, the hasNext() method call, and the next() method call. Because the enhanced for loop hides the iterator, these three opportunities for error disappear.

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

John Doe 25
Sally Smith 32

John Doe 25
Sally Smith 32

The enhanced for loop also can be used in an array context via a similar syntax to that shown earlier. In this context, you can specify a primitive type for the loop variable, and you can also specify the array to be iterated over. Listing 4 provides a simple demonstration.

Listing 4. EnForLoopDemo.java (version 2)

public class EnForLoopDemo
{
   public static void main(String[] args)
   {
      int[] x = { 10, 30, 500, -1 };

      for (int i: x)
           System.out.println(i);

      System.out.println();

      // The above code is implemented in
      // terms of a loop such as this loop:

      for (int j = 0; j < x.length; j++)
      {
         int i = x[j];
        System.out.println(i);
      }
   }
}

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

10
30
500
-1

10
30
500
-1

The enhanced for loop has limitations. For one thing, it cannot be used where access to the iterator is required in order to remove an element from a collection. Also, it's not usable where you need to replace elements in a collection or an array during a traversal. Finally, you cannot use the enhanced for loop where you need to iterate over multiple collections or arrays in parallel.

Static imports

Item 17 in Joshua Bloch's Effective Java Programming Language Guide mentions that interfaces should only be used to declare types. They shouldn't be used to declare constant interfaces, which are interfaces that only exist to export constants. Listing 5's Switchable constant interface provides an example.

Listing 5. Switchable.java

public interface Switchable
{
   boolean OFF = false;
   boolean ON = true;
}

Developers resort to constant interfaces to avoid having to prefix the constant's name with the name of its class (e.g., Math.PI). For example, consider Listing 6's Light class, which implements the Switchable interface so that the developer is free to specify constants OFF and ON without having to include class prefixes (if they were declared in a class).

Listing 6. Light.java (version 1)

public class Light implements Switchable
{
   private boolean state = OFF;

   public void printState()
   {
      System.out.printf("state = %s%n", (state == OFF) ? "OFF" : "ON");
   }

   public void toggle()
   {
      state = (state == OFF) ? ON : OFF;
   }
}

A constant interface provides constants that are to be used in a class's implementation. These constants are an implementation detail that you shouldn't leak into the class's exported API because they could confuse the users of your class. Furthermore, in order to preserve binary compatibility, you're committed to support them even when they're no longer used by the class.

1 2 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.