Friday, November 2, 2012

Java Generic


Replace the specific type String with a type parameter such as T, and we add the type parameter to the name of the class:

class Queue<T> {
   private LinkedList<T> items = new LinkedList<T>();
   public void enqueue(T item) {
      items.addLast(item);
   }
   public T dequeue() {
      return items.removeFirst();
   }
   public boolean isEmpty() {
      return (items.size() == 0);
   }
}
the type parameter T is used just like any regular type name. It's used to declare the return type for dequeue, as the type of the formal parameter item in enqueue, and even as the actual type parameter in LinkedList<T>. Given this class definition, we can use parameterized types such as Queue<String> and Queue<Integer> and Queue<JButton>. That is, the Queue class is used in exactly the same way as built-in generic classes like LinkedList and HashSet.

Note that you don't have to use "T" as the name of the type parameter in the definition of the generic class. Type parameters are like formal parameters in subroutines. You can make up any name you like in the definition of the class. The name in the definition will be replaced by an actual type name when the class is used to declare variables or create objects. If you prefer to use a more meaningful name for the type parameter, you might define the Queue class as:

class Queue<ItemType> {
   private LinkedList<ItemType> items = new LinkedList<ItemType>();
   public void enqueue(ItemType item) {
      items.addLast(item);
   }
   public ItemType dequeue() {
      return items.removeFirst();
   }
   public boolean isEmpty() {
      return (items.size() == 0);
   }
}
Changing the name from "T" to "ItemType" has absolutely no effect on the meaning of the class definition or on the way that Queue is used.

Generic interfaces can be defined in a similar way. It's also easy to define generic classes and interfaces that have two or more type parameters, as is done with the standard interface Map<T,S>. A typical example is the definition of a "Pair" that contains two objects, possibly of different types. A simple version of such a class can be defined as:
class Pair<T,S> {
   public T first;
   public S second;
   public Pair( T a, S b ) {  // Constructor.
      first = a;
      second = b;
   }
}


This class can be used to declare variables and create objects such as:

Pair<String,Color> colorName = new Pair<String,Color>("Red", Color.RED);
Pair<Double,Double> coordinates = new Pair<Double,Double>(17.3,42.8);

Note that in the definition of the constructor in this class, the name "Pair" does not have type parameters. You might have expected "Pair<T,S>. However, the name of the class is "Pair", not "Pair<T,S>, and within the definition of the class, "T" and "S" are used as if they are the names of specific, actual types.

 Note in any case that type parameters are never added to the names of methods or constructors, only to the names of classes and interfaces.
We need to replace the specific type String in the definition of the method with the name of a type parameter, such as T. However, if that's the only change we make, the compiler will think that "T" is the name of an actual type, and it will mark it as an undeclared identifier. We need some way of telling the compiler that "T" is a type parameter. That's what the "<T>" does in the definition of the generic class "class Queue<T> { ...". For a generic method, the "<T>" goes just before the name of the return type of the method:
public static <T> int countOccurrences(T[] list, T itemToCount) {
   int count = 0;
   if (itemToCount == null) {
      for ( T listItem : list )
         if (listItem == null)
            count++;
   }
   else {
      for ( T listItem : list )
         if (itemToCount.equals(listItem))
            count++;
   }
   return count;
}  
The "<T>" marks the method as being generic and specifies the name of the type parameter that will be used in the definition. Of course, the name of the type parameter doesn't have to be "T"; it can be anything. (The "<T>" looks a little strange in that position, I know, but it had to go somewhere and that's just where the designers of Java decided to put it.)
The countOccurrences method operates on an array. We could also write a similar method to count occurrences of an object in any collection:
public static <T> int countOccurrences(Collection<T> collection, T itemToCount) {
   int count = 0;
   if (itemToCount == null) {
      for ( T item : collection )
         if (item == null)
            count++;
   }
   else {
      for ( T item : collection )
         if (itemToCount.equals(item))
            count++;
   }
   return count;
}
Since Collection<T> is itself a generic type, this method is very general. It can operate on an ArrayList of Integers, a TreeSet of Strings, a LinkedList of JButtons, ....
Type Wildcard
Let's start with a simple example in which a wildcard type is useful. Suppose that Shape is a class that defines a method public void draw(), and suppose that Shape has subclasses such asRect and Oval. Suppose that we want a method that can draw all the shapes in a collection of Shapes. We might try:
public static void drawAll(Collection<Shape> shapes) {
   for ( Shape s : shapes )
      s.draw();
}
This method works fine if we apply it to a variable of type Collection<Shape>, or ArrayList<Shape>, or any other collection class with type parameter Shape. Suppose, however, that you have a list of Rects stored in a variable named rectangles of type Collection<Rect>. Since Rects are Shapes, you might expect to be able to call drawAll(rectangles). Unfortunately, this will not work; a collection of Rects is not considered to be a collection of Shapes! The variable rectangles cannot be assigned to the formal parameter shapes. The solution is to replace the type parameter "Shape" in the declaration of shapes with the wildcard type

"? extends Shape":


public static void drawAll(Collection<? extends Shape> shapes) {
   for ( Shape s : shapes )
      s.draw();
}

The wildcard type "? extends Shape" means roughly "any type that is either equal to Shape or that is a subclass of Shape". When the parameter shapes is declared to be of typeCollection<? extends Shape>, it becomes possible to call the drawAll method with an actual parameter of type Collection<Rect> since Rect is a subclass of Shape and therefore matches the wildcard. We could also pass actual parameters to drawAll of type ArrayList<Rect> or Set<Oval> or List<Oval>. And we can still pass variables of type Collection<Shape> orArrayList<Shape>, since the class Shape itself matches "? extends Shape". We have greatly increased the usefulness of the method by using the wildcard type.

One final remark: The wildcard type "<?>" is equivalent to "<? extends Object>". That is, it matches any possible type. For example, the removeAll() method in the generic interfaceCollections<T> is declared as
public boolean removeAll( Collection<?> c ) { ...
This just means that the removeAll method can be applied to any collection of any type of object.

No comments:

Post a Comment