Design Generic Types
The earlier tutorials covered generic subtypes, wildcards and type bounds focusing mainly on usage. In this concluding post, we discuss guidelines to follow while designing a new generic type or method.
Use wildcards on input parameters
For flexibility, use wildcard types on input parameters to make them either producers or consumers.
The union method, for example, takes two sets and returns a new set which is union of two.
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {...}
As both input sets are used to produce elements, we can rewrite the method as below. The PECS (producer extends and consumer super) comes handy to choose the wildcard bounds on input parameters.
public static <E> Set<E> union(Set<? extends E> s1,
Set<? extends E> s2) {...}
It also makes input sets s1 and s2 unmodifiable as we can’t invoke add() or set() methods on them. The next example, copy(), copies src to dest list.
public static <T> void copy(List<T> dest, List<T> src) {...}
Here src is producer and dest is consumer, and the preferred way to code it is as follows:
// src is producer, dest is consumer
public static <T> void copy(List<? super T> dest,
List<? extends T> src) {...}
Again, we use PECS to choose proper bounds on wildcards.
PECS and Recursive Type Bound
The previous post discussed recursive type bound using max() method as example which is as below:
public static <T extends Comparable<T>> T max(List<T> list) {...}
It can be revised, applying PECS, as follows:
public static <T extends Comparable<? super T>>
T max(List<? extends T> list) {...}
As we are interested in finding max value, the list is used as producer, so input parameter is modified as List<? extends T> list
. The Comparable of T consumes instances of T and produces integer. Comparable is always consumer, so it becomes Comparable<? super T>
.
Don’t use wildcard types as return type
In the union() method, which we saw above, the input parameters uses wildcard but return type is plain E.
public static <E> Set<E> union(Set<? extends E> s1,
Set<? extends E> s2) {...}
Had we used wildcard in return type then it forces user to use wildcard in client code also. Hence, don’t use wildcards types as return type.
Single type parameter methods
The static swap() method of java.util.Collections class can be declared in following ways:
public static <T> void swap(List<T> list, int i, int j) {...}
public static void swap(List<?> list, int i, int j) {...}
Out of these, the second one is preferred and used in Collections class. When type parameter appears in single formal parameter, then replace it with wildcard ?
as it allows to pass list of any type. Rules to replace is as follows:
- replace unbounded type parameter with unbounded wildcard. Example:
List<T> list
withList<?> list
- replace bounded type parameter with bounded wildcard. Example:
List<T extends Food> list
withList<? extends Food> list
Helper Methods
The above rules creates another problem. We have already learned that, put methods are not allowed on wildcard list. To overcome it, the swap() uses raw type. The source code of the method is here:
@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
// instead of using a raw type here, it's possible to capture
// the wildcard but it will require a call to a supplementary
// private method
final List l = list;
l.set(i, l.set(j, l.get(i)));
}
The developers of the method are sure that nothing new is added to the list in the method; so, they assign the generic list to raw type List variable l. However, it is clearly documented that it is possible to use a supplementary private method instead of raw type. The private method is also known as helper method. To avoid usage of raw type, we can rewrite the method as below:
public static void swap(List<?> list, int i, int j) {
swapHelper(list,i,j);
}
private static <E> void swapHelper(List<?> list, int i, int j) {
final List l = list;
l.set(i, l.set(j, l.get(i)));
}
Summary
- for flexible API, use wildcard types on input parameters to make them either producers or consumers.
- don’t use wildcard type as return type.
- in single type parameter methods:
- replace unbounded type parameter with unbounded wildcard. Example:
List<T> list
withList<?> list
. - replace bounded type parameter with bounded wildcard. Example:
List<T extends Food> list
withList<? extends Food> list
.
- replace unbounded type parameter with unbounded wildcard. Example:
- use private helper method to overcome wildcard capture error.
- go through source code of classes in java.util package to get good insight on generic types and methods design.
Summary of Generic Terminology
- generic type
List<T>
- type parameter/type variables
<T>
- type argument
<String>
- parameterized type
List<String> list;
- formal parameter
foo(String x)
- formal type parameter
foo(T x) or Bar<T>
- unbounded wildcard
<?>
- wildcard with upper bound
<? extends Type>
- wildcard with lower bound
<? super Type>
- type parameter bounds
<T extends Comparable<T>>