Objects First with Java
Supplementary Material for Java 5

David J. Barnes and Michael Kölling

Introduction

In this supplementary material we discuss some of the new language features available in Java 5. We do this in the context of a sample of projects to be found in the second edition of Objects First with Java - A Practical Introduction using BlueJ, Pearson Education, 2005 (in the USA: Prentice Hall, 2005), ISBN 0-131-24933-9.

The following topics are discussed:

Typed Collections (Generics)

Collection classes such as ArrayList, LinkedList, and HashMap permit objects of any class to be stored within them. This feature makes them extremely useful for storing arbitrary numbers of objects in many different application contexts.

One drawback, however, is that storing an object into a collection effectively 'loses' details of an object's type, so that retrieval requires the use of a cast:

Lot selectedLot = (Lot) lots.get(lotNumber - 1);

This is understandable, because the compiler has no way of knowing what type of objects have been stored into the collection. Indeed, a particular collection might have objects of several different types stored within it at any one time.

In Java 5, the new Generics feature offers a way around this 'type loss' problem and casting requirement through its support of typed collections.

Creating A Typed Collection

If the notebook1 project of Chapter 4 is compiled with a Java 5 compiler, the following warning message is produced:

Note: ...\projects\chapter04\notebook1\Notebook.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

The message suggests recompiling with an additional compiler option: -Xlint. When this option is added (e.g., via the bluej.defs configuration file) the more detailed explanation is:

Notebook.java:30: warning: [unchecked] unchecked call to add(E) as a member
                           of the raw type java.util.ArrayList
        notes.add(note);
                 ^

This warning is indicating that the compiler cannot check whether or not it is appropriate to add an object of type String to the notes collection.

Java 5 encourages programmers to use typed collections - collections that store objects of a known type - rather than untyped collections - collections that can store objects of mixed types. In this particular case, we are encouraged to indicate to the compiler that notes will store objects of type String.

Java 5 introduces a new piece of syntax to allow us to do this. Instead of declaring notes as follows:

private ArrayList notes;

we can now declare it as

private ArrayList<String> notes;

We have to make a corresponding change where the ArrayList object is created in the constructor. Instead of:

notes = new ArrayList();

we now write:

notes = new ArrayList<String>();

We can read the angle brackets <..> roughly as "of". Thus, we can read the new construct "ArrayList<String>" as "ArrayList of Strings". This new definition declares that all elements of this ArrayList will be of type String. The Java compiler will then enforce this by allowing only String objects to be added.

We say that typed collections are parameterized, but note that the parameter it takes is a type rather than a value. We shall see in the discussion of typed HashMaps, below, that parameterized types can take multiple type parameters.

Using a Typed Collection

A typed collection does not require a cast to be used when objects are retrieved from it, since the type of its elements is now known. The following is acceptable for accessing an individual element:

String note = notes.get(noteNumber);

Typed Iterators

Using typed collections has a knock-on effect with the use of iterators. Consider the Lot class of the auction project of Chapter 4, where we currently iterate over the untyped lots collection as follows:

/**
 * Show the full list of lots in this auction.
 */
public void showLots()
{
    Iterator it = lots.iterator();
    while(it.hasNext()) {
        Lot lot = (Lot) it.next();
        System.out.println(lot.toString());
    }
}

If we replace the untyped collection with a typed collection:

private ArrayList<Lot> lots;

then we can obviously simplify lot retrieval in getLot to

Lot selectedLot = lots.get(lotNumber - 1);

However, we cannot immediately simplify retrieval in showLots because it is via an untyped Iterator rather than via the typed collection. The solution is to type the Iterator, because a typed collection will return a typed Iterator:

/**
 * Show the full list of lots in this auction.
 */
public void showLots()
{
    Iterator<Lot> it = lots.iterator();
    while(it.hasNext()) {
        Lot lot = it.next();
        System.out.println(lot.toString());
    }
}

See also details of the enhanced for loop for further changes to iteration.

Typed HashMaps

In a HashMap, we do not enter single elements, we enter key/value pairs instead. When a typed HashMap is used, it is necessary to supply two type parameters in both the variable declaration and the object creation. The first is the type for the key, and the second is the type for the value. Here are the fields and constructor from the Responder class of the tech-support-complete project of Chapter 5, to illustrate how this is done:

public class Responder
{
    private HashMap<String, String> responseMap;
    private ArrayList<String> defaultResponses;
    private Random randomGenerator;

    /**
     * Construct a Responder
     */
    public Responder()
    {
        responseMap = new HashMap<String, String>();
        defaultResponses = new ArrayList<String>();
        fillResponseMap();
        fillDefaultResponses();
        randomGenerator = new Random();
    }

    ...
}

Adding to this HashMap is then done as before, and - as with the ArrayList - the cast can be left out when retrieving objects.

Recommendation

Typed collections are just one part of the generics feature introduced in Java 5. They are probably the most useful addition to the language and we recommend that they be used as standard whenever you program using collections. Because they allow more rigorous type checking to be applied at compile time, they make use of collections more type safe at run time than was previously possible.

Enhanced For Loop

Prior to Java 5, the standard way to iterate over the complete contents of an array would be as follows:

for(int index = 0; index < array.length; index++) {
    Type element = array[index];
    // Use element in some way.
    ...
}

Java 5 introduces an additional syntax with for loops that simplifies the structure of the loop header when we want to do something with each element of an array. It has the advantage that it helps to avoid common errors with getting the loop terminating condition correct. The new syntax for array iteration is:

for(Type element : array) {
    // Use element in some way.
    ...
}

It helps to read the new for loop if we read the for keyword as "for each" and the colon (:) as "in". The loop pattern above then becomes, "For each element in array do ..."

Note that the new style for loop's header does not contain an index variable. The variable declared there successively stores the value of the array elements.

This example sums the number of items in an array of Products:

public int totalQuantity(Product[] products)
{
    int total = 0;
    for(Product p : products) {
        total += p.getQuantity();
    }
    return total;
}

This new style of loop is not only available for arrays, but also for all other sorts of collections, such as lists and sets. This style does not replace the existing style, because there is no index variable available in the body of the loop. All initialization, testing and incrementation of the index variable has been made implicit. In some cases, an index variable is needed explicitly, and then the original style of for loop must be used.

For loop with Iterator

The enhanced loop syntax is also available for use with Iterators, which means that the following three ways of iterating through a collection are equivalent to one another:

Notice that the new style means that there is no explicit Iterator object.

Recommendation

The enhanced for-loop style is a useful addition to the language in so far as it simplifies the syntax of basic iteration over an array or collection, and reduces the opportunity for making errors in the loop's condition. It does not entirely replace the original style because it provides no access to an index variable or Iterator object, which is sometimes required. We recommend that it be used wherever the basic style of iteration is needed.

Autoboxing and Unboxing

In Chapter 8, wrapper classes such as Integer were discussed as a way of storing primitive-type values in object collections:

int i = 18;
Integer iwrap = new Integer(i);
myCollection.add(iwrap);
...
Integer element = (Integer) myCollection.get(0);
int value = element.intValue();

Java 5 allows primitive-type values to be stored into and retrieved from collections without explicitly wrapping them, via a process called autoboxing. In effect, autoboxing means that the wrapping and unwrapping is performed by the compiler rather than the programmer. This means that we could write the example above as simply:

int i = 18;
myCollection.add(i);
...
int value = (Integer) myCollection.get(0);

When a primitive-type value is passed to a method such as add that expects an object type, the value is automatically wrapped in an appropriate wrapper type. Similarly, when a wrapper-type value is stored into a primitive-type variable, the value is automatically unboxed.

Of course, if myCollection in the example above is a typed collection, then the following retrieval:

int value = (Integer) myCollection.get(0);

could simply have been written as:

int value = myCollection.get(0);

Autoboxing is applied whenever a primitive-type value is passed as a parameter to a method that expects a wrapper type, and when a primitive-type value is stored in a wrapper-type variable. Similarly, unboxing is applied when a wrapper-type value is passed as a parameter to a method that expects a primitive-type value, and when stored in a primitive-type variable.

Recommendation

We make no particular recommendation over the use of autoboxing as it will tend to occur naturally anyway, for instance where it is necessary to maintain an arbitrary-size collection of primitive-type data.

Typesafe Enums

In Java 5, a new syntax is introduced to allow the creation of enumerated types. The full syntax is very close to that of class definitions, but here we shall only explore a basic subset of it.

The following class definition illustrates a common way to associate distinct numerical values with symbolic names without using the new enum construct:

/**
 * Maintain a level attribute.
 */
public class Control
{
    // Three possible level values.
    public static final int LOW = 0, MEDIUM = 1, HIGH = 2;
    // The current level.
    private int level;

    /**
     * Initialise the level to be LOW.
     */
    public Control()
    {
        // initialise instance variables
        level = LOW;
    }

    /**
     * @return The current level (LOW, MEDIUM or HIGH)
     */
    public int getLevel()
    {
        return level;
    }
    
    /**
     * Set the current level.
     * @param level The level to be set (LOW, MEDIUM or HIGH)
     */
    public void setLevel(int level)
    {
        this.level = level;
    }
}

The idea is that a Control object will have its level field store a value of 0, 1 or 2, but that we can refer to those particular values as LOW, MEDIUM or HIGH, respectively, because the particular numbers chosen have no significance in themselves (we might equally well have chosen values of 0, 50 and 100, for instance).

There is a major problem with this particular style of coding related values and this is illustrated by the setLevel method. Because these values are ordinary integers, we have no guarantee that a value passed in to setLevel will definitely match one of the three legitimate values. Of course, we could add tests of the parameter value to ensure that it is one of the three approved values, but enumerated types provide a neat alternative solution that is much better.

In its simplest form, an enumerated type allows us to create a dedicated type for a set of names, such as LOW, MEDIUM and HIGH:

/**
 * Enumerate the set of available levels for a Control object.
 */
public enum Level
{
    LOW, MEDIUM, HIGH,
}

Values of this type are referred to as Level.LOW, Level.MEDIUM, and Level.HIGH.

The Control class can now use this new type in place of the integer type:

public class Control
{
    // The current level.
    private Level level;

    /**
     * Initialise the level to be Level.LOW.
     */
    public Control()
    {
        // initialise instance variables
        this(Level.LOW);
    }
    
    /**
     * @return The current level (Level.LOW, Level.MEDIUM or Level.HIGH)
     */
    public Level getLevel()
    {
        return level;
    }
    
    /**
     * Set the current level.
     * @param level The level to be set (Level.LOW, Level.MEDIUM or Level.HIGH)
     */
    public void setLevel(Level level)
    {
        this.level = level;
    }
}

Notice, in particular, that the type of the parameter to setLevel is now Level rather than int. It is important to appreciate that enumerated types are completely separate from the int type. This means that the setLevel method is now protected from receiving an inappropriate value, because their actual parameter's value must correspond to one of the names listed in the enumerated type. Similarly, any client calling getLevel cannot treat the returned value as an ordinary integer.

The values Method

Every enumerated type defines a public static method called values that returns an array whose elements contain the values of the type. The following statements use this method in order to print out all available levels:

Level[] availableLevels = Level.values();
for(int i = 0; i < availableLevels.length; i++) {
    System.out.println(availableLevels[i]);
}

or, written with the new for loop syntax:

for(Level level : Level.values()) {
    System.out.println(level);
}

Recommendation

Typesafe enums are a useful addition to the language where it is desired to use meaningful names for a related but distinct set of values. Enums mean that it is no longer necessary to associate arbitrary integer values with such names. The fact that enums are distinct from the int type adds an important degree of type safety to their use. We recommend that enums are used in these circumstances.

Static Imports

Static imports are a relatively minor addition in Java 5. They make it possible to import static methods and variables so that they can be used without use of their qualifying class name.

The following method (taken from the Location class in the taxi-company-stage-one project of Chapter 14) uses the static abs and max methods of the Math class to calculate the distance between two locations:

/**
 * Determine the number of movements required to get
 * from here to the destination.
 * @param destination The required destination.
 * @return The number of movement steps.
 */
public int distance(Location destination)
{
    int xDist = Math.abs(destination.getX() - x);
    int yDist = Math.abs(destination.getY() - y);
    return Math.max(xDist, yDist);
}

If the following static import statements are included at the top of the source file:

import static java.lang.Math.abs;
import static java.lang.Math.max;

then the body of the method could be slightly simplied to:

int xDist = abs(destination.getX() - x);
int yDist = abs(destination.getY() - y);
return max(xDist, yDist);

The full set of static members of a class may be imported using the form:

import static java.lang.Math.*;

Recommendation

In practice, the static import feature only really saves a little typing effort when there are many references to the static members of another class. We make no particular recommendation for using it.

Input using Scanner

The new Scanner class in the java.util package provides a way to read and process input that substitutes for what was often previously done using a combination of BufferedReader, StringTokenizer (or String.split), and the parse methods of the wrapper classes.

Scanning and Parsing Strings

Here is an excerpt from a method of the LoglineTokenizer class of the weblog-analyzer project in Chapter 4. It splits a String containing integers into separate int values and stores them in an array:

// Split logline where there are spaces.
StringTokenizer tokenizer = new StringTokenizer(logline);
int numTokens = tokenizer.countTokens();
if(numTokens == dataLine.length) {
    for(int i = 0; i < numTokens; i++) {
        String number = tokenizer.nextToken();
        dataLine[i] = Integer.parseInt(number);
    }
}
else {
    System.out.println("Invalid log line: " + logline);
}

The example creates a StringTokenizer to split the line, and then repeatedly requests the next 'token' to be returned as a String. As long as the String contains a valid integer, the parseInt method of Integer will return it as an int value.

The Scanner class slightly simplifies this process because it is able both to split a String into tokens, and to return those tokens as properly typed values. Here is how the process above might be performed with a Scanner:

// Scan the logline for integers.
Scanner tokenizer = new Scanner(logline);
for(int i = 0; i < dataLine.length; i++) {
    dataLine[i] = tokenizer.nextInt();
}

Scanning Standard Input and Files

The Scanner class can also be used to read and process input from files or the keyboard. Here is how the Parser class of the zuul projects of Chapter 7 might use a Scanner to read text lines containing up to two words:

import java.util.Scanner;

/**
 * ...
 *
 * This parser reads user input and tries to interpret it as an "Adventure"
 * command. Every time it is called it reads a line from the terminal and
 * tries to interpret the line as a two word command. It returns the command
 * as an object of class Command.
 *
 * ...
 */
public class Parser 
{

    private CommandWords commands;  // holds all valid command words
    private Scanner reader;         // returns user input.

    /**
     * Create a Parser for reading user input.
     */
    public Parser() 
    {
        commands = new CommandWords();
        // Scan standard input.
        reader = new Scanner(System.in);
    }

    /**
     * Parse the next user command.
     * @return The user's command.
     */
    public Command getCommand() 
    {
        String inputLine = "";   // will hold the full input line
        String word1;
        String word2;

        System.out.print("> ");     // print prompt

        String line = reader.nextLine();
        Scanner scan = new Scanner(line);
        
        if(scan.hasNext())
            word1 = scan.next();      // get first word
        else
            word1 = null;
        if(scan.hasNext())
            word2 = scan.next();      // get second word
        else
            word2 = null;

        // note: we just ignore the rest of the input line.

        // Create a command with it.
        return new Command(word1, word2);
    }

    /**
     * Print out a list of valid command words.
     */
    public void showCommands()
    {
        commands.showAll();
    }
}

Notice that this class uses two Scanners. The nextLine method of reader is used to return a line of text, and the hasNext and next methods of scan are used to examine and parse a line.

Recommendation

The Scanner class slightly simplifies the parsing and type conversion of arbitrary textual input, when compared with alternatives. We recommend that it be used where appropriate.

Summary

There are some significant and useful new features in the Java 5 release. In particular, typed collections, the enhanced for loop and enumerated types are likely to be those of most use to the majority of programmers.


Copyright 2004-2005, David J. Barnes and Michael Kölling.