Articles
Enterprise Architecture
In addition to generic types, Java 5 introduced generic methods. In this example from
java.util.Collections, a singleton list is constructed. The element type of the new
List is inferred based on the type of the object passed into the method:
static <T> List<T> Collections.singletonList(T o)
Example usage:
public List<Integer> getListOfOne() {
return Collections.singletonList(1);
}
In the example usage, we pass in an
int. The return type of the method is then
List<Integer>. The compiler infers
Integer for
T. This is different from generic types because you do not generally need to explicitly specify the type argument.
This also shows the interaction of autoboxing with generics. Type arguments must be reference types; that's why we get
List<Integer> and not
List<int>.
The
emptyList() method was introduced with generics as a type safe replacement for the
EMPTY_LIST field in
java.util.Collections:
static <T> List<T> Collections.emptyList()
Example usage:
public List<Integer> getNoIntegers() {
return Collections.emptyList();
}
Unlike the previous example, this one has no parameters, so how does the compiler infer the type for
T? Basically, it will try once using the parameters. If that does nothing, it tries again using the return or assignment type. In this case, we are returning
List<Integer>, so
T is inferred to be
Integer.
What if you are invoking a generic method in a place other than in a return statement or assignment statement? Then the compiler is unable to do the second pass of type inferencing. In this example,
emptyList() is invoked from within the conditional operator:
public List<Integer> getNoIntegers() {
return x ? Collections.emptyList() : null;
}
The compiler cannot see the return context and cannot infer
T, so it gives up and assumes
Object. You would see an error message like "
cannot convert List<Object> to List<Integer>."
To fix this, you explicitly pass the type argument to the method invocation. This way, the compiler won't try to infer the type arguments for you, and you get the right result:
return x ? Collections.<Integer>emptyList() : null;
The other place where this happens frequently is in method invocation. If a method takes a
List<String> and you try to call this passing
emptyList() for that param, you will also need to use this syntax.
Here are three examples of Generic types that are not Collections that use generics in a novel way. All of these come from the standard Java libraries:
Class<T>
Class is parameterized on the type of the class. This makes it possible to construct a
newInstance without casting.
Comparable<T>
Comparable is parameterized by the actual comparison type. This provides stronger typing on
compareTo() invocations. For example,
String implements
Comparable<String>. Invoking
compareTo() on anything other than a
String will fail at compile time.
Enum<E extends Enum<E>>
Enum is parameterized by the enum type. An enum called
Color would extend
Enum<Color>. The
getDeclaringClass() method returns the class object for the enum type, which in this case would be a
Color. It's different from
getClass(), which may return an anonymous class.
The most complex part of generics is understanding wildcards. We'll cover the three types of wildcards and why you may want to use them.
First let's look at how arrays work. You can assign a
Number[] from an
Integer[]. If you attempt to write a
Float into the
Number[], it will compile but fail at runtime with an
ArrayStoreException:
Integer[] ia = new Integer[5]; Number[] na = ia; na[0] = 0.5; // compiles, but fails at runtime
If we try to translate that example directly into generics, it fails at compile time because the assignment isn't allowed:
List<Integer> iList = new ArrayList<Integer>(); List<Number> nList = iList; // not allowed nList.add(0.5);
With Generics, you will never get a runtime
ClassCastException as long as you have code that compiles without warnings.
What we want is a list whose exact element type is unknown, unlike the array case.
A
List<Number> is a list whose element type is the concrete type
Number, exactly.
A
List<? extends Number> is a list whose exact element type is unknown. It is
Number or a subtype.
If we update our original example and assign to a
List<? extends Number>, the assignment now succeeds:
List<Integer> iList = new ArrayList<Integer>(); List<? extends Number> nList = iList; Number n = nList.get(0); nList.add(0.5); // Not allowed
We can get
Numbers out of the list because no matter what the exact element type of the list is (
Float,
Integer, or
Number), we can still assign it to
Number.
We still can't insert floats into the list. This fails at compile time because we can't prove this is safe. If we were to add a float into the list, it would violate the original type safety of
iList—that it stores only
Integers.
Wildcards give us more expressive power than is possible with arrays.
In this example, a wildcard is used to hide type information from the user of the API. Internally, the
Set is stored as
CustomerImpl. To users of the API, all they know is that they are getting a
Set from which they can read
Customers.
Wildcards are necessary here because you can't assign from a
Set<CustomerImpl> to a
Set<Customer>:
public class CustomerFactory {
private Set<CustomerImpl> _customers;
public Set<? extends Customer> getCustomers() {
return _customers;
}
}