BUY THIS BOOK
Add to Cart

Print Book $29.95


Safari Books Online

What is this?

Add to UK Cart

Print Book £20.95

What is this?

Looking to Reprint this content?


Java 5.0 Tiger: A Developer's Notebook
Java 5.0 Tiger: A Developer's Notebook By David Flanagan, Brett McLaughlin
June 2004
Pages: 200

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: What's New?
In this chapter:
  • Working With Arrays
  • Using Queues
  • Ordering Queues Using Comparators
  • Overriding Return Types
  • Taking Advantage of Better Unicode
  • Adding StringBuilder to the Mix
Even with nearly 200 pages before you, it's going to be awfully tough to cover all of Tiger's new features. Whether it's called Java 1.5, 2.0, Java 5, or something altogether different, this version of Java is an entirely new beast, and has tons of meat to offer.
Rather than waste time on introductory text, this chapter jumps right into some of the "one-off" features that are new for Tiger, and that don't fit into any of the larger chapters. These will get you used to the developer's notebook format if it's new for you, and introduce some cool tools along the way. Then Chapter 2 gets downright serious, as generics are introduced, and from there to the last page, it's a race to cram everything in.
Tiger has a pretty major overhaul of its collection classes, most of which have to do with generic types and support for the new for/in loop. Without getting into those details yet, you can get some immediate bang for your buck by checking out the java.util.Arrays class, which is chock-full of static utility methods (many of which are new to Tiger).
The java.util.Arrays class is a set of static methods that all are useful for working with arrays. Most of these methods are particularly helpful if you have an array of numeric primitives, which is what Example 1-1 demonstrates (in varied and mostly useless ways).
Example 1-1. Using the Arrays utility class
package com.oreilly.tiger.ch01;

import java.util.Arrays;
import java.util.List;

public class ArraysTester {

  private int[] ar;

  public ArraysTester(int numValues) {
    ar = new int[numValues];
    
    for (int i=0; i < ar.length; i++) {
      ar[i] = (1000 - (300 + i));
    }
  }

  public int[] get( ) {
    return ar;
  }

  public static void main(String[] args) {
    ArraysTester tester = new ArraysTester(50);
    int[] myArray = tester.get( );

    // Compare two arrays
    int[] myOtherArray = tester.get().clone( );
    if (Arrays.equals(myArray, myOtherArray)) {
      System.out.println("The two arrays are equal!");
    } else {
      System.out.println("The two arrays are not equal!");
    }

    // Fill up some values
    Arrays.fill(myOtherArray, 2, 10, new Double(Math.PI).intValue( ));
    myArray[30] = 98;

    // Print array, as is
    System.out.println("Here's the unsorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );

    // Sort the array
    Arrays.sort(myArray);

    // print array, sorted
    System.out.println("Here's the sorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );
  
    // Get the index of a particular value
    int index = Arrays.binarySearch(myArray, 98);
    System.out.println("98 is located in the array at index " + index);
    
    String[][] ticTacToe = { {"X", "O", "O"},
                             {"O", "X", "X"},
                             {"X", "O", "X"}};
    System.out.println(Arrays.deepToString(ticTacToe));
  
    String[][] ticTacToe2 = { {"O", "O", "X"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    String[][] ticTacToe3 = { {"X", "O", "O"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    if (Arrays.deepEquals(ticTacToe, ticTacToe2)) {
      System.out.println("Boards 1 and 2 are equal.");
    } else {
      System.out.println("Boards 1 and 2 are not equal.");
    }
    
    if (Arrays.deepEquals(ticTacToe, ticTacToe3)) {
      System.out.println("Boards 1 and 3 are equal.");
    } else {
      System.out.println("Boards 1 and 3 are not equal.");
    }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Working with Arrays
Tiger has a pretty major overhaul of its collection classes, most of which have to do with generic types and support for the new for/in loop. Without getting into those details yet, you can get some immediate bang for your buck by checking out the java.util.Arrays class, which is chock-full of static utility methods (many of which are new to Tiger).
The java.util.Arrays class is a set of static methods that all are useful for working with arrays. Most of these methods are particularly helpful if you have an array of numeric primitives, which is what Example 1-1 demonstrates (in varied and mostly useless ways).
Example 1-1. Using the Arrays utility class
package com.oreilly.tiger.ch01;

import java.util.Arrays;
import java.util.List;

public class ArraysTester {

  private int[] ar;

  public ArraysTester(int numValues) {
    ar = new int[numValues];
    
    for (int i=0; i < ar.length; i++) {
      ar[i] = (1000 - (300 + i));
    }
  }

  public int[] get( ) {
    return ar;
  }

  public static void main(String[] args) {
    ArraysTester tester = new ArraysTester(50);
    int[] myArray = tester.get( );

    // Compare two arrays
    int[] myOtherArray = tester.get().clone( );
    if (Arrays.equals(myArray, myOtherArray)) {
      System.out.println("The two arrays are equal!");
    } else {
      System.out.println("The two arrays are not equal!");
    }

    // Fill up some values
    Arrays.fill(myOtherArray, 2, 10, new Double(Math.PI).intValue( ));
    myArray[30] = 98;

    // Print array, as is
    System.out.println("Here's the unsorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );

    // Sort the array
    Arrays.sort(myArray);

    // print array, sorted
    System.out.println("Here's the sorted array...");
    System.out.println(Arrays.toString(myArray));
    System.out.println( );
  
    // Get the index of a particular value
    int index = Arrays.binarySearch(myArray, 98);
    System.out.println("98 is located in the array at index " + index);
    
    String[][] ticTacToe = { {"X", "O", "O"},
                             {"O", "X", "X"},
                             {"X", "O", "X"}};
    System.out.println(Arrays.deepToString(ticTacToe));
  
    String[][] ticTacToe2 = { {"O", "O", "X"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    String[][] ticTacToe3 = { {"X", "O", "O"},
                              {"O", "X", "X"},
                              {"X", "O", "X"}};
                            
    if (Arrays.deepEquals(ticTacToe, ticTacToe2)) {
      System.out.println("Boards 1 and 2 are equal.");
    } else {
      System.out.println("Boards 1 and 2 are not equal.");
    }
    
    if (Arrays.deepEquals(ticTacToe, ticTacToe3)) {
      System.out.println("Boards 1 and 3 are equal.");
    } else {
      System.out.println("Boards 1 and 3 are not equal.");
    }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using Queues
Another cool collection addition is the java.util.Queue class, for all those occasions when you need FIFO (first-in, first-out) action. Using this class is a breeze, and you'll find it's a nice addition to the already robust Java Collection ...er...collection.
Some queues are LIFO (last-in, first-out).
The first thing to realize is that proper use of a Queue implementation is to avoid the standard collection methods add( ) and remove( ). Instead, you'll need to use offer( ) to add elements. Keep in mind that most queues have a fixed size. If you call add( ) on a full queue, an unchecked exception is thrown—which really isn't appropriate, as a queue being full is a normal condition, not an exceptional one. offer( ) simply returns false if an element cannot be added, which is more in line with standard queue usage.
In the same vein, remove( ) throws an exception if the queue is empty; a better choice is the new poll( ) method, which returns null if there is nothing in the queue. Both methods attempt to remove elements from the head of the queue. If you want the head without removing it, use element( ) or peek( ). Example 1-2 shows these methods in action.
Example 1-2. Using the Queue interface
package com.oreilly.tiger.ch01;

import java.io.IOException;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.Queue;

public class QueueTester {

  public Queue q;
  
  public QueueTester( ) {
    q = new LinkedList( );
  }

  public void testFIFO(PrintStream out) throws IOException {
    q.add("First");
    q.add("Second");
    q.add("Third");
    
    Object o;
    while ((o = q.poll( )) != null) {
      out.println(o);
    }
  }

  public static void main(String[] args) {
    QueueTester tester = new QueueTester( );
  
    try {
      tester.testFIFO(System.out);
    } catch (IOException e) {
      e.printStackTrace( );
    }
  }
}
In testFIFO( ), you can see that the first items into the queue are the first ones out:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Ordering Queues Using Comparators
While FIFO is a useful paradigm, there are times when you'll want a queue-like structure, ordered by another metric. This is exactly the purpose of PriorityQueue, another Queue implementation. You provide it a Comparator, and it does the rest.
PriorityQueue works just as any other Queue implementation, and you don't even need to learn any new methods. Instead of performing FIFO ordering, though, a PriorityQueue orders its items by using the Comparator interface. If you create a new queue and don't specify a Comparator, you get what's called natural ordering, which applies to any classes that implement Comparable. For numerical values, for instance, this places highest values, well, highest! Here's an example:
    PriorityQueue<Integer> pq =
      new PriorityQueue<Integer>(20);
      
    // Fill up with data, in an odd order
    for (int i=0; i<20; i++) {
      pq.offer(20-i);
    }
    
    // Print out and check ordering
    for (int i=0; i<20; i++) {
      System.out.println(pq.poll( ));
    }
Since no Comparator implementation is given to PriorityQueue, it orders the numbers lowest to highest, even though they're not added to the queue in that order. So when peeling off elements, the lowest item comes out first:
    [echo] Running PriorityQueueTester...
    [java] 1
    [java] 2
    [java] 3
    [java] 4
    [java] 5
    [java] 6
    [java] 7
    [java] 8
    [java] 9
    [java] 10
    [java] 11
    [java] 12
    [java] 13
    [java] 14
    [java] 15
    [java] 16
    [java] 17
    [java] 18
    [java] 19
    [java] 20
However, this queue starts to really come into its own when you provide your own comparator, as shown in Example 1-3. This is done via the constructor, and a custom implementation of java.util.Comparator.
Example 1-3. Using a PriorityQueue
package com.oreilly.tiger.ch01;

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueTester {

  public static void main(String[] args) {
 
    PriorityQueue<Integer> pq =
      new PriorityQueue<Integer>(20,
        new Comparator<Integer>( ) {
          public int compare(Integer i, Integer j) {
            int result = i%2 - j%2;
            if (result == 0)
              result = i-j;
            return result;
          }
        }
      );

      // Fill up with data, in an odd order
      for (int i=0; i<20; i++) {
        pq.offer(20-i);
      }

      // Print out and check ordering
      for (int i=0; i<20; i++) {
        System.out.println(pq.poll( ));
      }
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Overriding Return Types
One of the most annoying features when you're using Java inheritance is the inability to override return types. This is most commonly desired when you've got a base class, and then a subclass adds a dimension (either literally or figuratively) to the base class. Typically, you're unable to return that extra dimension without defining a new method (and new name), since the method that the base class used probably had a narrower return type. Thankfully, you can solve this problem using Tiger.
Example 1-4 is a simple class hierarchy that demonstrates overriding the return type of a superclass's method.
Keep in mind that none of this code compiles under Java 1.4, or even in Tiger without the "-source 1.5" switch
Example 1-4. Overriding the methods of a superclass
class Point2D {
  protected int x, y;
  
  public Point2D( ) {
    this.x=0;
    this.y=0;
  }
  
  public Point2D(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

class Point3D extends Point2D {
  protected int z;

  public Point3D(int x, int y) {
    this(x, y, 0);
  }
  
  public Point3D(int x, int y, int z) {
    this.x = x;
    this.y = y;
    this.z = z; 
  }
}

class Position2D {
  Point2D location;
  
  public Position2D( ) {
    this.location = new Point2D( );
  }
  
  public Position2D(int x, int y) {
    this.location = new Point2D(x, y);
  }

  public Point2D getLocation( ) {
    return location;
  }
}

class Position3D extends Position2D {
  Point3D location;
  
  public Position3D(int x, int y, int z) {
    this.location = new Point3D(x, y, z);
  }
  
  public Point3D getLocation( ) {
    return location;
  }
}
The key is the line public Point3D getLocation( ), which probably looks pretty odd to you, but get used to it. This is called a covariant return, and is only allowed if the return type of the subclass is an extension of the return type of the superclass. In this case, this is satisfied by Point3D extending Point2D
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Taking Advantage of Better Unicode
While many of the features in this chapter and the rest of the book focus on entirely new features, there are occasions where Tiger has simply evolved. The most significant of these is Unicode support. In pre-Tiger versions of Java, Unicode 3.0 was supported, and all of these Unicode characters fit into 16 bits (and therefore a char). Things are different, now, so you'll need to understand a bit more.
In Tiger, Java has moved to support Unicode 4.0, which defines several characters that don't fit into 16 bits. This means that they won't fit into a char, and that has some far-reaching consequences. You'll have to use int to represent these characters, and as a result methods like Character.isUpperCase( ) and Character.isWhitespace( ) now have variants that accept int arguments. So if you're needing values in Unicode 3.0 that are not available in Unicode 3.0, you'll need to use these new methods..
Most of the new characters in Unicode 4.0 are Han ideographs.
To really grasp all this, you have to understand a few basic terms:
codepoint
A codepoint is a number that represents a specific character. As an example, 0x3C0 is the codepoint for the symbol π.
Basic Multilingual Plan (BMP)
The BMP is all Unicode codepoints from \u0000 through \uFFFF. All of these codepoints fit into a Java char.
supplementary characters
These are the Unicode codepoints that fall outside of the BMP. There are 21-bit codepoints, with hex values from 010000 through 10FFFF, and must be represented by an int.
A char, then, represents a BMP Unicode codepoint. To get all the supplementary characters in addition to the BMP, you need to use an
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Adding StringBuilder to the Mix
As you work through this book, you'll find that in several instances, the class StringBuilder is used, most often in the manner that you're used to seeing StringBuffer used. StringBuilder is a new Tiger class intended to be a drop-in replacement for StringBuffer in cases where thread safety isn't an issue.
Replace all your StringBuffer code with StringBuilder code. Really—it's as simple as that. If you're working in a single-thread environment, or in a piece of code where you aren't worried about multiple threads accessing the code, or synchronization, it's best to use StringBuilder instead of StringBuffer. All the methods you are used to seeing on StringBuffer exist for StringBuilder, so there shouldn't be any compilation problems doing a straight search and replace on your code. Example 1-5 is just such an example; I wrote it using StringBuffer, and then did a straight search-and-replace, converting every occurrence of "StringBuffer" with "StringBuilder".
Example 1-5. Replacing StringBuffer with StringBuilder
package com.oreilly.tiger.ch01;

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

public class StringBuilderTester {

  public static String appendItems(List list) {
    StringBuilder b = new StringBuilder( );
    
    for (Iterator i = list.iterator( ); i.hasNext( ); ) {
      b.append(i.next( ))
       .append(" ");
    }
    
    return b.toString( );
  }

  public static void main(String[] args) {
    List list = new ArrayList( );
    list.add("I");
    list.add("play");
    list.add("Bourgeois");
    list.add("guitars");
    list.add("and");
    list.add("Huber");
    list.add("banjos");

    System.out.println(StringBuilderTester.appendItems(list));
  }
}
	
You'll see plenty of other code samples using StringBuilder in the rest of this book, so you'll be thoroughly comfortable with the class by book's end.
...all the new formatting stuff in Tiger, like printf( ) and format( )? StringBuilder, as does
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Generics
In this chapter:
  • Using Type-Safe Lists
  • Using Type-Safe Maps
  • Iterating Over Parameterized Types
  • Accepting Parameterized Types as Arguments
  • Returning Parameterized Types
  • Using Parameterized Types as Type Parameters
  • Checking for Lint
  • Generics and Type Conversions
  • Using Type Wildcards
  • Writing Generic Types
  • Restricting Type Parameters
Without any further ado, I'm going to dive right into the deep end of the pool. More than any other feature, Tiger (or whatever version it ends up being labeled as) brings to the table generics. While the name might throw you, generics actually bring a greater degree of type safety to Java than anything you could imagine. It's finally possible to create parameterized types, lists that only accept Strings, and ditch all that annoying class-casting code. Even better, you can limit types that your custom classes and methods accept, removing a huge amount of tedious errorchecking and type-checking code.
Additionally, generics are foundational to many of the other features specific to Tiger. Generics have a bearing on varargs, annotations, enumerations, collections, and even some of the new concurrency utilities of the language. While you may want to browse through other parts of this book, you'd do well to take your time and really work through this chapter, lab by lab. There, that's enough introduction for a few chapters—let's get to it.
One of Java's greatest strengths is its typing. Everything is an object, and, in fact, every class either explicitly or implicitly descends from
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using Type-Safe Lists
One of Java's greatest strengths is its typing. Everything is an object, and, in fact, every class either explicitly or implicitly descends from Object. This provides a tremendous amount of type-safety—your methods can take Integers, Strings, Lists, Maps, or your own custom objects as parameters, and know at the outset what they'll have to work with.
With all this type-safety, Java has a gaping hole that Tiger finally fills—the ability to create type-safe arrays and lists, ensuring that collections of objects only allow for a certain type to be inserted.
One of the most annoying tasks in Java is having to cast objects pulled out of a List, when you already know what's in the List (such as when you fill it yourself, or a trusted source handles populating it):
Generics don't apply to primitive types.
    List listOfStrings = getListOfStrings( );
    for (Iterator i = listOfStrings.iterator( ); i.hasNext( ); ) {
      String item = (String)i.next( );
      
      // Work with that string
    }
Remove that cast, though—pull out (String)—and you'll get a compiler error:
This particular code sample is in com.oreilly. tiger.ch02. GenericsTester.
         [javac] Compiling 1 source file to code\classes
         [javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:17:
                   incompatible types
         [javac] found   : java.lang.Object
         [javac] required: java.lang.String
         [javac]       String item = i.next( );
         [javac]                           ^
         [javac] Note: code\src\com\oreilly\tiger\ch02\GenericsTester.java uses
                   unchecked or unsafe operations.
         [javac] Note: Recompile with -Xlint:unchecked for details.
         [javac] 1 error
No matter how much you trust the getListOfStrings( ) method, the compiler doesn't trust it one bit. It assumes the worst, and if you've ever had anyone else work with you, you realize the compiler is often right more than you are.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using Type-Safe Maps
As cool as generics make the List class, it wouldn't be much good if that was the only collection that could be parameterized. All of the various collection classes are now generic types, and accept type parameters. Since most of these behave like List, I'll spare you the boring prose of covering each one. It is worth looking at Map, though, as it takes two type parameters, instead of just one. You use it just as you use List, but with two types at declaration and initialization.
java.util.Map has a key type (which can be any type) and a value type (which can be any type). While it's common to use a numeric or String key, that's not built into the language, and you can't depend on it—at least, not until Tiger came along:
        Map<Integer, Integer> squares = new HashMap<Integer, Integer>( );

        for (int i=0; i<100; i++) {
          squares.put(i, i*i);
        }
        
        for (int i=0; i<10; i++) {
          int n = i*3;
          out.println("The square of " + n + " is " + squares.get(n));
        }
This is a simple example of where a new Map is declared, and both its key and value types are defined as Integer. This ensures that you don't have to do any casting, either in putting values into the Map or pulling them out. Pretty easy stuff, isn't it? Of course, you could use any of the following lines of code as well:
    // Key and value are Strings
    Map<String, String> strings = new HashMap<String, String>( );
    
    // Key is a String, value is an Object
    Map<String, Object> map = new HashMap<String, Object>( );
    
    // Key is a Long, value is a String
    Map<Long, String> args = new HashMap<Long, String>( );
As briefly mentioned in Using Type-Safe Lists, autoboxing helps when you want to stuff primitives into a collection. In this case, even though the Map is defined to take Integers, it's the int counter i that is used to create values. Without getting into the details covered in Chapter 4, Java autoboxes the int value of i
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Iterating Over Parameterized Types
Although the for/in loop provides a means of almost completely avoiding the java.util.Iterator class, that particular feature of Tiger isn't covered until Chapter 7. But until you get to that chapter (and probably occasionally after that), it's still useful to know how generic collection types affect Iterator. You'll need to perform an extra step to get the full power of generics.
It would seem that once you've parameterized your collections, grabbing an Iterator and using it would be trivial:
    List<String> listOfStrings = new LinkedList<String>( );
    listOfStrings.add("Happy");
    listOfStrings.add("Birthday");
    listOfStrings.add("To");
    listOfStrings.add("You");
    
    for (Iterator i = listOfStrings.iterator( ); i.hasNext( ); ) {
      String s = i.next( );
      out.println(s);
    }
However, all is not well. Here's what the compiler spits back to you:
       [javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:54:
                 incompatible types
       [javac] found   : java.lang.Object
       [javac] required: java.lang.String
       [javac]       String s = i.next( );
       [javac]                        ^
       [javac] Note: code\src\com\oreilly\tiger\ch02\GenericsTester.java
                 uses unchecked or unsafe operations.
       [javac] Note: Recompile with -Xlint:unchecked for details.
       [javac] 1 error
The problem here is that while you've parameterized your List, you haven't parameterized your Iterator. It's still spitting out Objects, and doesn't know that it should only expect to receive and respond with String types. Just like the collections, Iterator is a generic type in Java, and is declared as public interface Iterator<E>. Its next( ) method, then, returns E (which is a placeholder, as detailed in "Using Type-Safe Lists"). To parameterize it, you use the same syntax as you did for collection classes:
    List<String> listOfStrings = new LinkedList<String>( );
    listOfStrings.add("Happy");
    listOfStrings.add("Birthday");
    listOfStrings.add("To");
    listOfStrings.add("You");
    
    for (Iterator
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Accepting Parameterized Types as Arguments
So far, all of this parameterization has occurred in the same code block. However, that's unrealistic, and you'll quickly want to write methods that take advantage of parameterized types. This is where generics start to really become powerful. First, you need to understand how a method can tell the compiler that it only accepts a specific parameterization of a generic type.
Just use the same syntax you've been using (and which should be getting oddly comfortable by this point) in your argument list:
     private void printListOfStrings(List<String> list, PrintStream out)
       throws IOException {
       
       for (Iterator<String> i = list.iterator( ); i.hasNext( ); ) {
       out.println(i.next( ));
       }
     }
This allows your method body to act on that parameterization, avoiding class casts and the like. In this example, it's possible to parameterize the Iterator as well, because the compiler ensures that only List<String> is passed into the method. Any other List types are refused (at compiletime).
...trying to pass in a plain old List, without any parameterization, even if it has only Strings in it? This actually will work, with the caveat that you're left to your own devices in ensuring that the List has in it what it's supposed to. If not, you'll get more ClassCastExceptions than you can shake a stick at, all at runtime. In either case, you'll get lint warnings, which are described in "Checking for Lint," later in this chapter.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Returning Parameterized Types
In addition to accepting parameterized types as arguments, methods in Tiger can return types that are parameterized.
Remember the getListOfStrings( ) method, referred to in "Using Type-Safe Lists"? Here is the actual code for that method:
      private List getListOfStrings( ) {
        List list = new LinkedList ( );
        list.add("Hello");
        list.add("World");
        list.add("How");
        list.add("Are");
        list.add("You?");
        
        return list;
      }
While this is a workable method, it's going to generate all sorts of lint warnings (see Checking for Lint for details) because it doesn't specify a type for the List. Even more importantly, code that uses this method can't assume that it is really getting a List of Strings. To correct this, just parameterize the return type, as well as the List that is eventually returned by the method:
      private List<String> getListOfStrings( ) {
        List<String> list = new LinkedList<String>();
        list.add("Hello");
        list.add("World");
        list.add("How");
        list.add("Are");
        list.add("You?");
        
        return list;
      }
Pretty straightforward, isn't it? The return value of this method can now be used immediately in type-safe ways:
    List<String> strings = getListOfStrings( );

    for (String s : strings) {
      out.println(s);
    }
This isn't possible, without compile-time warnings, unless getListOfStrings( ) has a parameterized return value.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using Parameterized Types as Type Parameters
Collections in Tiger are generic types, and accept type parameters. However, these collections can store collections themselves, which are in turn also generics. This means that a parameterized type can be used as the type parameter to another generic type.
The Map interface takes two type parameters: one for the key, and one for the value itself. While the key is usually a String or numeric ID, the value can be anything—including a generic type, like a List of Strings.
So List<String> becomes a parameterized type, which can be supplied to the Map declaration:
    Map<String, List<String>> map = new HashMap<String, List<String>>( );
If that's not enough angle brackets for you, here's yet another layer of generics to add into the mix:
    Map<String, List<List<int[]>>> map = getWeirdMap( );
Of course, where things get really nuts is actually accessing objects from this collection:
    int value = map.get(someKey).get(0).get(0)[0];
What's cool about this is that all the casting is handled for you—you don't need to do any casting to List, but instead can just let the compiler unravel all your parameterized types for you.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Checking for Lint
Several times in this chapter, you've heard about lint warnings, which sounds more like something you get out of a dryer than a compiler. These warnings are a new feature of Tiger, though, and important in figuring out how to really bulletproof your code.
Take a simple piece of code that used a type that can be parameterized, but without type parameters:
    private List getList( ) {
      List list = new LinkedList( );
      list.add(3);
      list.add("Blind");
      list.add("Mice");
      
      return list;
    }
If you compile this in Tiger, with the -source 1.5 flag, you'll get this message:
You can compile all of the examples for this book with "-Xlint:unchecked" by using the Ant target "compilecheck".
    Note: GenericsTester.java uses unchecked or unsafe operations.
    Note: recompile with -Xlint:unchecked for details.
If you recompile with the suggested flag, you are telling the compile to show lint warnings (-Xlint), and specifically to show those warnings that are unchecked. .
Here's some sample output with these warnings turned on:
              [javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:63: warning:
              [unchecked] unchecked call to add(E) as a member of
              the raw type java.util.List
      [javac]     list.add(3);
      [javac]         ^
      [javac] src\com\oreilly\tiger\ch02\GenericsTester.java:64: warning:
              [unchecked] unchecked call to add(E) as a member of
              the raw type java.util.List
      [javac]     list.add("Blind");
      [javac]         ^
      [javac] src\com\oreilly\tiger\ch02\GenericsTester.java:65: warning:
              [unchecked] unchecked call to add(E) as a member of
              the raw type java.util.List
      [javac]     list.add("Mice");
      [javac]         ^
      [javac] 3 warnings
These warnings indicate that the compiler isn't able to ensure that the values added to the list (named list in this case) are the intended type. That's because
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Generics and Type Conversions
Now that you have all your nifty parameterized types, you'll probably want to perform all sorts of nifty type conversions. This List of Integers gets tossed into that Map of Numbers...then again, it's not quite that easy. You'll need to take great care if you want these conversions to actually work.
The key in casting generic types is to understand that as with normal, non-generic types, they form a hierarchy. What's unique about generics, though, is that the hierarchy is based on the base type, not the parameters to that type. For example, consider this declaration:
    LinkedList<Float> floatList = new LinkedList<Float>( );
The conversion is based on LinkedList, not Float. So this is legal:
    List<Float> moreFloats = floatList;
However, the following is not:
    LinkedList<Number> numberList = floatList;
While Float is indeed a subclass of Number, it's the generic type that is important, not the parameter type.
There are two key things to understand if you want to know why type conversions work like this; the first is seeing how type conversions can be abused, and the second is erasure. First, consider this sample code, which is actually illegal in Tiger; it demonstrates why converting a LinkedList<Float> to a LinkedList<Number> (or even to a LinkedList<Object>) should indeed be illegal:
    List<Integer> ints = new ArrayList<Integer>( );
    ints.add(1);
    ints.add(2);
    
    // This is illegal, but use it for illustration purposes
    List<Number> numbers = ints;
    
    // Now a float is being added to a list of ints! This results in a
    //   ClassCastException when the item is retrieved from the
    //   list and used as an int (instead of a float)
    numbers.add(1.2);
    
    // This is even worse
    List<Object> objects = ints;
    objects.add("How are you doing?");
Clearly, it needs to be the base type that is considered in type conversions, not the parameter type.
The second concept you'll want to grasp is
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using Type Wildcards
While I'm sure plenty of folks disagree, I think production code shouldn't issue warnings.
So now you've got generic types figured out, and even understand all the unchecked warnings your code is generating. Still, here are times when you really do want a plain old List, or Map, or whatever, without parameterization. This is going to result in unchecked errors, unless you employ the generics wildcard.
To illustrate the problem, here's a really simple method that prints out all the members of a List:
    public void printList(List list, PrintStream out) throws IOException {
      for (Iterator i = list.iterator( ); i.hasNext( ); ) {
        out.println(i.next( ).toString( ));
      }
    }
Since writing this, later versions of the compiler don't throw warnings here. Still, it makes a good point, so I've left it in for you.
The problem is that this generates unchecked warnings, something you should avoid whenever possible. What you're really saying, though, is that printList( ) takes any List. This is where the wildcard operator comes in, which for generics is a question mark (?). Make the following change:
    public void printList(List<?> list, PrintStream out) throws IOException {
      for (Iterator<?> i = list.iterator( ); i.hasNext( ); ) {
        out.println(i.next( ).toString( ));
      }
    }
You've now expressed in syntax what you meant—any type is acceptable, and the unchecked warnings go away.
...using List<Object> to get around this same problem? You might want to review Generics and Type Conversions, and see if you really want to do that. A List<Integer> cannot be passed to a method that takes a List<Object>, remember? So your printList( ) method would be limited to collections defined as List<Object>, which isn't much use at all. In these cases, the wildcard really is the only viable solution.
You should also be thinking about the declaration of methods in classes like this:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Writing Generic Types
With an arsenal of generic terminology under your belt, you're probably wondering about writing your own generic types. I'm wondering about it, too, so I figure it's worth covering. They're actually pretty simple to write, and you've already got the tools from earlier labs.
If you need to define some sort of collection, or container, or other custom object that deals directly with another type, generics add a ton of options to your programming toolkit. For example, Example 2-2 is a basic container structure useful mostly for illustrating important generic concepts.
You can use anything you want for the type parameter, although a single letter is most common.
Example 2-2. A basic generic type
package com.oreilly.tiger.ch02;
	
import java.util.ArrayList;
import java.util.List;

public class Box<T> {

  protected List<T> contents;
  
  public Box( ) {
    contents = new ArrayList<T>( );
  }
  
  public int getSize( ) {
    return contents.size( );
  }
  
  public boolean isEmpty( ) {
    return (contents.size( ) == 0);
  }
  
  public void add(T o) {
    contents.add(o);
  }
  
  public T grab( ) {
    if (!isEmpty( )) {
      return contents.remove(0);
    } else
      return null;
    }
  }
Just as you've seen in Tiger's pre-defined generic types, a single letter is used as the representative for a type parameter.
You create a new instance of this type exactly as you might expect:
    Box<String> box = new Box<String>( );
This effectively replaces all the occurrences of T with String for that specific instance, and suddenly you've got yourself a String Box, so to speak.
...static variables? Static variables are shared between object instances, but parameterization occurs on a per-instance basis. So you could feasibly have a Box<Integer>, a Box<String>, and a Box<List<Float>>, all with a shared static variable. That variable, then, cannot make assumptions about the typing of any particular instance, as they may be different. It also cannot use a parameterized type—so the following is illegal:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Restricting Type Parameters
Suppose that you want a version of Box that only accepts numbers—and further, that based on that, you want to add some functionality that's specific to numbers. To accomplish this, you need to restrict the types that are allowed.
This is pretty simple—you can actually insert an className onto your type variable, and voila! Check out Example 2-3.
Example 2-3. Restricting the parameterization type
package com.oreilly.tiger.ch02;
	
import java.util.Iterator;

public class NumberBox<N extends Number> extends Box<N> {

  public NumberBox( ) {
    super( );
  }
  
  // Sum everything in the box
  public double sum( ) {
    double total = 0;
    for (Iterator<N> i = contents.iterator( ); i.hasNext( ); ) {
      total = total + i.next( ).doubleValue( );
    }
    return total;
  }
}
The only types allowed here are extensions of the class Number (or Number itself). So the following statement is illegal:
    NumberBox<String> illegal = new NumberBox<String>( );
The compiler indicates that the bound of this type is not met. The bound, of course, is the restriction put upon typing, and that's exactly what the error message indicates:
       [javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:118:
               type parameter java.lang.String is not within its bound
       [javac]       NumberBox<String> illegal = new NumberBox<String>( );
       [javac]                 ^
       [javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:118:
               type parameter java.lang.String is not within its bound
       [javac]       NumberBox<String> illegal = new NumberBox<String>( );
       [javac]                                                 ^
You can use this same syntax in method definitions:
    public static double sum(Box<? extends Number> box1,
                             Box<? extends Number> box2) {
      double total = 0;
      for (Iterator<? extends Number> i = box1.contents.iterator( );
           i.hasNext( ); ) {
        total = total + i.next( ).doubleValue( );
      }
      for (Iterator<? extends Number> i = box2.contents.iterator( );
           i.hasNext( ); ) {
        total = total + i.next( ).doubleValue( );
      }
      return total;
    }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Enumerated Types
In this chapter:
  • Creating an Enum
  • Declaring Enums Inline
  • Iterating Over Enums
  • Switching on Enums
  • Maps of Enums
  • Sets of Enums
  • Adding Methods to an Enum
  • Implementing Interfaces with Enums
  • Value-Specific Class Bodies
  • Manually Defining an Enum
  • Extending an Enum
In Java 1.4 and below, there were two basic ways to define new types: through classes and interfaces. For most object-oriented programming, this would seem to be enough. The problem is that there are still some very specific cases where neither is these is sufficient, most commonly when you need to define a finite set of allowed values for a specific data type. For instance, you might want a type called Grade that can only be assigned values of A, B, C, D, F, or Incomplete. Any other values are illegal for this type. This sort of construct is possible prior to Tiger, but it takes a lot of work, and there are still some significant problems.
Since we're good developers and try our best to avoid a lot of work whenever possible, Sun finally helped us out with the new enumerated type (generally referred to simply as an enum). This chapter deals with enums: how to create, use, and program with them.
Creating an enumerated type involves three basic components, at a minimum:
  • The enum keyword
  • A name for the new type
  • A list of allowed values for the type
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Creating an Enum
Creating an enumerated type involves three basic components, at a minimum:
  • The enum keyword
  • A name for the new type
  • A list of allowed values for the type
There are several optional components that may be defined as well:
  • An interface or set of interfaces that the enum implements
  • Variable definitions
  • Method definitions
  • Value-specific class bodies
These optional components are detailed in the labs throughout this chapter; this lab covers the most basic concepts of enumerated types.
Example 3-1is about as basic of an enum as you'll find, representing a simple Grade object.
Example 3-1. A simple enumerated type
package com.oreilly.tiger.ch03;

public enum Grade { A, B, C, D, F, INCOMPLETE };
Enums allow you to dump most of your "public static final" variable declarations.
You can then define a class that refers to this enum just as it would to any other Java class or interface, as shown in Example 3-2.
More often than not, you'll only need the basic enum functionality.
The convention is to use all capital letters for enumerated type identifiers.
"Grade" is used just like anyother Java type.
Example 3-2. Referring to an enum in another class
package com.oreilly.tiger.ch03;

public class Student {

  private String firstName;
  private String lastName;
  private Grade grade;

  public Student(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }
  
  public String getFirstName( ) {
    return firstName;
  }
  
  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  
  public String getLastName( ) {
    return lastName;
  }
  
  public String getFullName( ) {
    return new StringBuffer(firstName)
           .append(" ")
           .append(lastName)
           .toString( );
  }
  
  
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Declaring Enums Inline
While it's useful to create a separate enum class, defined in its own source file, sometimes its also useful to just define an enum, use it, and throw it away. This is possible through member types.
Just define the enum within your class, as you would any other member variable. You might need a DownloadStatus enum, for example, but only within a Downloader class:
   public class Downloader {
   
      public enum DownloadStatus { INITIALIZING, IN_PROGRESS, COMPLETE };
      
      // Class body
   }
Oddly enough, this same code may be written as follows:
   public class Downloader {
   
     public static enum DownloadStatus { INITIALIZING, IN_PROGRESS, COMPLETE };
      
     // Class body
   }
In this case, the static modifier has been added. This has no effective change on the enum, as nested enums are implicitly static. In other words, it's sort of like declaring an interface abstract—it's redundant. Because of this redundancy, I'd recommend against using the static keyword in these declarations.
Examples in the enum specification also omit "static" in nested declarations.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Iterating Over Enums
Ever been given a class with lousy documentation, no source code, and little instruction on its use? Welcome to the loosely knit organization of real-world programmers. In many cases, you can resort to reflection to figure out what a class has to offer in lieu of source code, and of course JavaDoc is always helpful. In the case of enumerated types, though, there's a nice built-in feature: the values( ) method. This method provides access to all of the types within an enum.
Invoking the values( ) method on an enum returns an array of all the values in the type:
         public void listGradeValues(PrintStream out) throws IOException {
           Grade[] gradeValues = Grade.values( );
           for (Grade g : Grade.values( )) {
             out.println("Allowed value: '" + g + "'");
           }
         }
This is a nice way to get a quick dump of all the allowed values for a particular enum:
      run-ch03:
           [echo] Running Chapter 3 examples from Java 5.0: A Developer's Notebook

           [echo] Running GradeTester...
           [java] Allowed value: 'A'
           [java] Allowed value: 'B'
           [java] Allowed value: 'C'
           [java] Allowed value: 'D'
           [java] Allowed value: 'F'
           [java] Allowed value: 'INCOMPLETE'
Run this sample with the Ant target "run-ch03".
First, note that type-safety is employed. values( ) doesn't return an array of String values—instead it returns an array of Grade instances. In the out.println( ) statement, each Grade has its toString( ) method executed, which in turn does provide a String name for the value. At no point are you working with integer constants or even String values—the Grade object hides all these implementation details from you, and allows strict compile-time checking.
...using a for/in loop? Well, you're ahead of me—for/in isn't covered until Chapter 7. Still, for those of you who are curious, you can indeed perform the same iteration with Tiger's new
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Switching on Enums
As you begin to integrate enums into your own programs, one of the first tasks you'll want to accomplish is using an enum with a switch statement. This is a pretty obvious application; there's little value in using enums if you can't easily react to the set of values available.
Prior to Java 1.4, switch only worked with int, short, char, and byte values. However, since enums have a finite set of values, Tiger adds switch support for them. Here's an example of using an enum in a switch statement:
   public void testSwitchStatement(PrintStream out) throws IOException {
     StringBuffer outputText = new StringBuffer(student1.getFullName( ));
      
     switch (student1.getGrade( )) {
       case A:
         outputText.append(" excelled with a grade of A");
         break;
       case B: // fall through to C
       case C:
         outputText.append(" passed with a grade of ")
                    .append(student1.getGrade( ).toString( ));
         break;
       case D: // fall through to F
       case F:
         outputText.append(" failed with a grade of ")
                    .append(student1.getGrade( ).toString( ));
         break;
       case INCOMPLETE:
         outputText.append(" did not complete the class.");
         break;
     }
         
     out.println(outputText.toString( ));
   }
This code assumes that student1 has already been created; this is taken care of in the test class, "GradeTester".
The argument to switch must be an enumerated value; in this case, the return type of getGrade( ) is Grade, which meets these requirements. However, there is another requirement that makes this code a little odd--did you catch it? Note the format of each case clause:
   case A:
   case B:
   case C:
   case D:
   case F:
   case INCOMPLETE:
See anything missing? How about the enum class identifier:
   case Grade.A:
   case Grade.B:
   case Grade.C:
   case Grade.D:
   case Grade.F:
   case Grade.INCOMPLETE:
For those of you up on Tiger, this may make you think about the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Maps of Enums
Once you've gotten your fingers used to typing public enum, you'll start to find all sorts of interesting uses for enums. Once you've gotten past the very basic constant-replacement, you'll start to see that they also serve as great keys, or indices, in collection-type structures. Apparently the Sun guys thought the same thing, and provided a nice facility for working with enums as indices.
In the old, archaic, pre-Tiger days (snicker, snicker), you might have used a constants class like OldAntStatus (you'll remember a similar class from Example 3-3):
   public class OldAntStatus {
     public static final int INITIALIZING = 0;
     public static final int COMPILING    = 1;
     public static final int COPYING      = 2;
     public static final int JARRING      = 3;
     public static final int ZIPPING      = 4;
     public static final int DONE         = 5;
     public static final int ERROR        = 6;
   }
You might then write a simple array of messages that are associated with each of these status codes:
   String[] antMessages = new String