Just as
classes can benefit from generification, so can methods. Static utility methods
are particularly good candidates for generification. All of the “algorithm”
methods in Collections (such as binarySearch and sort) have been
generified.
Writing
generic methods is similar to writing generic types. Consider this method,
which returns the union of two sets:
// Uses raw types - unacceptable! (Item 23)
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
This method
compiles, but with two warnings:
Union.java:5: warning: [unchecked] unchecked call to
HashSet(Collection<? extends E>) as a member of
raw type HashSet
Set result = new HashSet(s1);
^
Union.java:6: warning: [unchecked] unchecked call to
addAll(Collection<? extends E>) as a member of raw
type Set
result.addAll(s2);
^
To fix these
warnings and make the method typesafe, modify the method declaration to declare
a type parameter representing the element type for the three sets (two
arguments and the return value) and use the type parameter in the method. The type
parameter list, which declares the type parameter, goes between the method’s
modifiers and its return type. In this example, the type parameter list is <E> and the return type is Set<E>. The naming conventions for type parameters
are the same for generic methods as for generic types (Items 26, 44):
// Generic method
public static <E>
Set<E> union(Set<E> s1,
Set<E> s2) {
Set<E>
result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
Here’s a
simple program to exercise our method. The program contains no casts and compiles
without errors or warnings:
// Simple program to exercise generic method
public static void main(String[] args) {
Set<String> guys = new HashSet<String>(
Arrays.asList("Tom", "Dick",
"Harry"));
Set<String> stooges = new HashSet<String>(
Arrays.asList("Larry", "Moe",
"Curly"));
Set<String> aflCio = union(guys, stooges);
System.out.println(aflCio);
}
A limitation
of the union method is that
the types of all three sets (both input parameters and the return value) have
to be the same. You can make the method more flexible by using bounded wildcard types (Item 28).
In the case of
the program above, the compiler sees that both arguments to union are of type Set<String>, so it knows
that the type parameter E must be String. This process is called type inference.
// Parameterized type instance creation with
constructor
Map<String,
List<String>> anagrams =
new HashMap<String,
List<String>>();
To eliminate
this redundancy, write a generic static
factory method corresponding to each constructor that you want to use. For
example, here is a generic static factory method corresponding to the
parameterless HashMap constructor:
// Generic static factory method
public static <K,V> HashMap<K,V>
newHashMap() {
return new HashMap<K,V>();
}
With this
generic static factory method, you can replace the repetitious declaration
above with this concise one:
// Parameterized type instance creation with static
factory
Map<String, List<String>> anagrams = newHashMap();
It would be
nice if the language did the same kind of type inference when invoking
constructors on generic types as it does when invoking generic methods. Someday
it might, but as of release 1.6, it does not.
A related
pattern is the generic singleton factory. On occasion,
you will need to create an object that is immutable but applicable to many
different types. Because generics are implemented by erasure (Item 25), you can
use a single object for all required type parameterizations, but you need to
write a static factory method to repeatedly dole out the object for each
requested type parameterization. This pattern is most frequently used for
function objects (Item 21) such as Collections.reverseOrder, but it is
also used for collections such as Collections.emptySet.
Suppose you
have an interface that describes a function that accepts and returns a value of
some type T:
public interface UnaryFunction<T> {
T apply(T arg);
}
Now suppose
that you want to provide an identity function. It would be wasteful to create a
new one each time it’s required, as it’s stateless. If generics were reified,
you would need one identity function per type, but since they’re erased you
need only a generic singleton. Here’s how it looks:
// Generic singleton factory pattern
private static UnaryFunction<Object>
IDENTITY_FUNCTION =
new UnaryFunction<Object>() {
public Object apply(Object arg) { return arg; }
};
// IDENTITY_FUNCTION is stateless and its type
parameter is
// unbounded so it's safe to share one instance
across all types.
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T>
identityFunction() {
return (UnaryFunction<T>)
IDENTITY_FUNCTION;
}
The cast of IDENTITY_FUNCTION to (UnaryFunction<T>)
generates
an unchecked cast warning, as UnaryFunction<Object>
is
not a UnaryFunction<T> for every T.
Here is a
sample program that uses our generic singleton as a UnaryFunction< String> and a UnaryFunction<Number>. As usual, it
contains no casts and compiles without errors or warnings:
// Sample program to exercise generic singleton
public static void main(String[] args) {
String[] strings = { "jute", "hemp",
"nylon" };
UnaryFunction<String> sameString =
identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryFunction<Number> sameNumber =
identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
It is
permissible, though relatively rare, for a type parameter to be bounded by some
expression involving that type parameter itself. This is what’s known as a recursive type bound. The most common use of recursive type
bounds is in connection with the Comparable
interface,
which defines a type’s natural ordering:
public interface Comparable<T> {
int compareTo(T o);
}
The type
parameter T defines the type to which
elements of the type implementing Comparable<T>
can
be compared.
There are many
methods that take a list of elements that implement Comparable, in order to sort the list, search within
it, calculate its minimum or maximum, and the like. To do any of these things,
it is required that every element in the list be comparable to every other
element in the list, in other words, that the elements of the list be mutually comparable. Here is how to express that constraint:
// Using a recursive type bound to express mutual
comparability
public static <T extends Comparable<T>> T
max(List<T> list) {...}
The type bound
<T extends
Comparable<T>> may be read as “for every type T
that
can be compared to itself,” which corresponds more or less exactly to the notion
of mutual comparability.
Here is a
method to go with the declaration above. It calculates the maximum value of a
list according to its elements’ natural order, and it compiles without errors
or warnings:
// Returns the maximum value in a list - uses
recursive type bound
public static <T extends Comparable<T>> T
max(List<T> list) {
Iterator<T> i = list.iterator();
T result = i.next();
while (i.hasNext()) {
T t = i.next();
if (t.compareTo(result) > 0)
result = t;
}
return result;
}
In summary,
generic methods, like generic types, are safer and easier to use than methods
that require their clients to cast input parameters and return values. Like
types, you should make sure that your new methods can be used without casts,
which will often mean making them generic. And like types, you should generify
your existing methods to make life easier for new users without breaking
existing clients (Item 23).
Reference: Effective Java 2nd Edition by Joshua Bloch