Generic Types
Type Parameters/Type Variables
“Type parameters (a.k.a. type variables) are used as placeholders to indicate that a type will be assigned to the class at runtime.”(Juneau, 2014)
By convention, type parameters are a single uppercase. Following list is the standard type parameters:
- E: Element
- K: Key
- N: Number
- T: Type
- V: Value
- S,U,V in a multiparameter situation
Wildcard Types
There is a limitation on the sort of generic classes and methods that we have looked at so far: The type parameter in our examples, usually named T, can be any type at all. This is OK in many cases, but it means that the only things that you can do with T are things that can be done with every type, and the only things that you can do with objects of type T are things that you can do with every object. With the techniques that we have covered so far, you can’t, for example, write a generic method that compares objects with the compareTo() method, since that method is not defined for all objects. The compareTo() method is defined in the Comparable interface. What we need is a way of specifying that a generic class or method only applies to objects of type Comparable and not to arbitrary objects. With that restriction, we should be free to use compareTo() in the definition of the generic class or method.
There are two different but related syntaxes for putting restrictions on the types that are used in generic programming. One of these is bounded type parameters, which are used as formal type parameters in generic class and method definitions; a bounded type parameter would be used in place of the simple type parameter T in “class GenericClass<T> …” or in “public static <T> void genericMethod(…”. The second syntax is wildcard types, which are used as type parameters in the declarations of variables and of formal parameters in method definitions; a wildcard type could be used in place of the type parameter String in the declaration statement “List<String> list;” or in the formal parameter list “void concat(Collection<String> c)”. We will look at wildcard types first, and we will return to the topic of bounded types later in this section.
Wildcard types are used only as type parameters in parameterized types, such as Collection<? extends Boxable>. Wildcard types appear in the angle brackets that specify the parameterized type. The place where a wildcard type is most likely to occur, by far, is in a formal parameter list, where the wildcard type is used in the declaration of the type of a formal parameter.
Test Yourself
Consider the following method signatures:
public static <T extends Comparable> void max(T[] array);
public <E> E findMax(List<? extends E> list);
Which method uses a Bounded Type? Which Method uses a Wildcard Type?
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 as Rect 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 formal parameter, shapes, is declared to be of type Collection<? 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> or ArrayList<Shape>, since the class Shape itself matches “? extends Shape”. We have greatly increased the usefulness of the method by using the wildcard type.
(Although it is not essential, you might be interested in knowing why Java does not allow a collection of Rects to be used as a collection of Shapes, even though every Rect is considered to be a Shape. Consider the rather silly but legal method that adds an oval to a list of shapes:
public static void drawAll(Collection<Shape> shapes) { for (Shape s : shapes ) s.draw(); }
Suppose that rectangles is of type List<Rect>. It’s illegal to call addOval(rectangles,oval), because of the rule that a list of Rects is not a list of Shapes. If we dropped that rule, then addOval(rectangles,oval) would be legal, and it would add an Oval to a list of Rects. This would be bad: Since Oval is not a subclass of Rect, an Oval is not a Rect, and a list of Rects should never be able to contain an Oval. The method call addOval(rectangles,oval) does not make sense and should be illegal.)
Test Yourself
- Draw a UML diagram that correctly describes the relationship between Shape, Rect, and Oval.
- Write the signature for a method that would only allow Shape objects.
- Write the signature for a method that would only allow subclasses of Oval.
- Write the method signature would allow parameters of any type that implements Boxable.
This type of wildcard is an upper bounded wildcard and it is specified writing “? extends T”. When you use upper-bound, the argument can be any type or subclass of type. Consider the addAll method in the Collection class,
addAll(Collection<? Extends T> coll)
This method adds every element in coll to the calling Collection
// Suppose shapes is a Collection of Shape objects, rects is a Collection of Rect objects, and // ovals is a Collection of Oval objects, nums is a Collection of Integer objects shapes.addAll(rects); // OK because Rect is a child of Shape rects.addAll(shapes); // NOT OK because Shape is not a subclass of Rect // Remember, it’s also incorrect to say Rect r = new Shape // You can’t refer to a parent object using a child reference Shapes.addAll(nums); // NOT OK because Integer is not a Shape or subclass of Shape // You can’t add an Integer object to a collection of Shape objects anyway!
In a wildcard type such as “? extends T”, T can be an interface instead of a class. Note that the term “extends” (not “implements”) is used in the wildcard type, even if T is an interface. For example, we have seen that Boxable is an interface that defines the method public void peek(). Here is a method that peeks into all the objects in a collection of Boxables by executing the peek() method from each boxable object:
public static viewAll( Collection<? extends Boxable> boxed) { for ( Boxable box: boxed) { System.out.println(box.peek()); } }
Upper bounded wildcards provides more flexibility by allowing the generic type to be exactly the same generic type or a subclass rather than the same generic type.
There is also a lower bounded wildcard and it is specified writing “? super T” (e.g. <? super Integer>). When you use lower-bound, the argument can be any type that is of that type or a superclass of that type.
Consider the addAll method again. The addAll method adds all the items from a collection to the box. Suppose that we wanted to do the opposite: Add all the items that are currently in the box to a given collection. An instance method defined as
public void addAllTo(Collection<T> collection)
would only work for collections whose base type is exactly the same as T. This is too restrictive. We need some sort of wildcard. However, “? extends T” won’t work. Suppose we try it:
public void addAllTo(Collection<? extends T> collection) { // Remove all items currently on the queue and add them to collection while ( ! isEmpty() ) { T item = remove(); // Remove an item from the queue. collection.add( item ); // Add it to the collection. ILLEGAL!! } }
The problem here is that the elements currently in the CRATE may be of type T or of some subclass of T. If the collection to which we’re adding also holds type T, then there is no problem. You can add Shapes to a collection of Shapes, for instance. However, the CRATE may hold Rects. You cannot add Shape objects to a collection of Rect objects. A Rect IS-A Shape, but a Shape is NOT necessarily a Rect.
Test Yourself
Considering only the classes Shape, Rect, and Oval, answer the following questions:
- Objects of which type can be stored using a Shape reference variable ?
- Objects of which type can be stored using a Rect reference variable?
- Given a Collection of Shapes, what type of objects can be added to this collection?
- Given a Collection of Rects, what type of objects can be added to this collection?
Thus, the relationship between the unknown type (?) and T goes the wrong direction. We don’t want a Collection that holds type T or subclasses of type T. We want a Collection that holds type T or superclasses (parent classes) of type T!
If we have a Crate that holds Shapes, you can only store those Shapes in a Collection that holds Shapes. If we have a Crate that holds Rects, you can only store those Rects in a Collection that holds Rects or Shapes (and Shape is the superclass of Rect!).
To express this type of relationship, we need a new kind of type wildcard: “? super T”. This wildcard means, roughly, “either T itself or any class that is a superclass of T.” For example, Collection<? super Rect> would match the types Collection<Shape>, ArrayList<Object>, and Set<Rect>. This is what we need for our addAllTo method. With this change, our generic Crate class becomes:
public class GenericCrate<E> implements GenericBoxable<E> { private ArrayList<E> contents; public GenericCrate() { contents = new ArrayList<E>(); } /** * Removes all elements currently in the Crate and adds them to collection * * @param collection - where to put items in the crate */ public void addAllTo(Collection<? super E> collection) { // Remove all items currently in the Crate and add them to collection while (!contents.isEmpty()) { E item = contents.remove(0); // Remove an item from the queue. collection.add(item); // Add it to the collection. } } /** * Adds everything in collection to this crate */ public void addAll(Collection<? extends E> collection) { // Add all the items from the collection to the end of the queue for (E item : collection) add(item); } }
Bounded Types
Wildcard types don’t solve all of our problems. They allow us to generalize method definitions so that they can work with collections of objects of various types, rather than just a single type. However, they do not allow us to restrict the types that are allowed as formal type parameters in a generic class or method definition. Bounded types exist for this purpose.
We start with a small, not very realistic example. Suppose that you would like to create groups of GUI components using a generic class named ControlGroup. For example, the parameterized type ControlGroup<Button> would represent a group of Buttons, while ControlGroup<Slider> would represent a group of Sliders. The class will include methods that can be called to apply certain operations to all components in the group at once. For example, there will be an instance method of the form
public void disableAll() { . . // Call c.setDisable(true) for every control, c, in the group. . }
The problem is that the setDisable() method is defined in a Control object, but not for objects of arbitrary type. It wouldn’t make sense to allow types such as ControlGroup<String> or ControlGroup<Integer>, since Strings and Integers don’t have setDisable() methods. We need some way to restrict the type parameter T in ControlGroup<T> so that only Control and subclasses of Control are allowed as actual type parameters. We can do this by using the bounded type “T extends Control” instead of a plain “T” in the definition of the class:
public class ControlGroup<T extends Control> { // bounded type T private ArrayList<T> components; // For storing the components in this group.
public void disableAll( ) { for ( Control c : components ) { if (c != null) c.setDisable(true); } } public void enableAll( ) { for ( Control c : components ) { if (c != null) c.setDisable(false); } } public void add( T c ) { // Add a value c, of type T, to the group. components.add(c); } . . // Additional methods and constructors. . }
The restriction “extends Control” on T makes it illegal to create the parameterized types ControlGroup<String> and ControlGroup<Integer>, since the actual type parameter that replaces “T” is required to be either Control itself or a subclass of Control. With this restriction, we know—and, more important, the compiler knows—that the objects in the group are of type Control, so that the operation c.setDisable() is defined for any c in the group.
In general, a bounded type parameter “T extends SomeType” means roughly “a type, T, that is either equal to SomeType or is a subclass of SomeType”; the upshot is that any object of type T is also of type SomeType, and any operation that is defined for objects of type SomeType is defined for objects of type T. The type SomeType doesn’t have to be the name of a class. It can be any name that represents an actual object type. For example, it can be an interface or even a parameterized type.
Bounded types and wildcard types are clearly related. They are, however, used in very different ways. A bounded type can be used only as a formal type parameter in the definition of a generic method, class, or interface. A wildcard type is used most often to declare the type of a formal parameter in a method and cannot be used as a formal type parameter. One other difference, by the way, is that, in contrast to wildcard types, bounded type parameters can only use “extends”, never “super”.
Bounded type parameters can be used when declaring generic methods. For example, as an alternative to the generic ControlGroup class, one could write a free-standing generic static method that can disable any collection of Controls as follows:
public static void disableAll(Collection comps) { for ( Control c : comps ) if (c != null) c.setDisable(true); }
Using “<T extends Control>” as the formal type parameter means that the method can only be called for collections whose base type is Control or some subclass of Control, such as Button or Slider.
Note that we don’t really need a generic type parameter in this case. We can write an equivalent method using a wildcard type:
public static void disableAll(Collection<? extends Control> comps) { for ( Control c : comps ) if (c != null) c.setDisable(true); }
In this situation, the version that uses the wildcard type is to be preferred, since the implementation is simpler. However, there are some situations where a generic method with a bounded type parameter cannot be rewritten using a wildcard type. Note that a generic type parameter gives a name, such as T, to the unknown type, while a wildcard type does not give a name to the unknown type. The name makes it possible to refer to the unknown type in the body of the method that is being defined. If a generic method definition uses the generic type name more than once or uses it outside the formal parameter list of the method, then the generic type parameter cannot be replaced with a wildcard type.
Let’s look at a generic method in which a bounded type parameter is essential. Here is a code segment for inserting a string into a sorted list of strings, in such a way that the modified list is still in sorted order:
public static void disableAll(Collection<? extends Control> comps) { for ( Control c : comps ) if (c != null) c.setDisable(true); }
This method works fine for lists of strings, but it would be nice to have a generic method that can be applied to lists of other types of objects. The problem, of course, is that the code assumes that the compareTo() method is defined for objects in the list, so the method can only work for lists of objects that implement the Comparable interface. We can’t simply use a wildcard type to enforce this restriction. Suppose we try to do it, by replacing List<String> with List<? extends Comparable>:
static void sortedInsert(List<? extends Comparable> sortedList, ???? newItem) { ListIterator<?????> iter = sortedList.listIterator(); ... }
We immediately run into a problem, because we have no name for the unknown type represented by the wildcard. We need a name for that type because the type of newItem and of iter should be the same as the type of the items in the list. The problem is solved if we write a generic method with a bounded type parameter, since then we have a name for the unknown type, and we can write a valid generic method:
static <T extends Comparable> void sortedInsert(List<T> sortedList, T newItem) { ListIterator<T> iter = sortedList.listIterator(); while (iter.hasNext()) { T item = iter.next(); if (newItem.compareTo(item) <= 0) { iter.previous(); break; } } iter.add(newItem); }
There is still one technicality to cover in this example. Comparable is itself a parameterized type, but I have used it here without a type parameter. This is legal but the compiler might give you a warning about using a “raw type.” In fact, the objects in the list should implement the parameterized interface Comparable<T>, since they are being compared to items of type T. This just means that instead of using Comparable as the type bound, we should use Comparable<T>:
static <T extends Comparable<T>> void sortedInsert(List<T> sortedList, ...