Once you get past simple usage of Java Generics and start implementing generic classes yourself it may seem quite intimidating. It is tricky, so it is important to remember a few rules.
Subtyping
The Liskov Substitution Principle, the rule that says that subclasses should be substitutable for their base classes, does not apply to generic elements!
-
Integeris a subtype ofNumber. -
Integeris a subtype ofComparable. -
Listis a subtype ofCollection. -
List<Integer>is a subtype ofCollection<Integer>. -
List<Integer>is not a subtype of List<Number>. -
List<Integer>is not a subtype ofList<Comparable>.
// Example why generic elements are not proper subtypes: List<Integer> integers = new LinkedList<Integer>; List<Number> numbers = integers; // Won’t compile! numbers.add(3.14); // Integers cannot contain 3.14
The fact that the Liskovs Substitution Principle does not apply to generics restricts
their usefulness; we need wildcards to loosen some of these restrictions.
Wildcards
To loosen the constraint above, wildcards may be used. Wildcards are used with the
keywords extends and super.
-
<? extends Number>means all types that are subclasses ofNumberare allowed. -
<? super Integer>means all types that are superclasses ofIntegerare allowed.
The Get and Put Principle: use extends only when you intend to get values out of a
structure. Use super only when you intend to put values into a structure.
The container that you get something out of is guaranteed to contain elements that
are instances of the expected class or of a subclass and may be used properly by the
recipient of the get.
The container that you put something into is guaranteed to contain at least in-
stances of the expected class or a superclass ensuring that the put is valid.
This also implies: don’t use any wildcards when you intend to both get and put
values into and out of a structure.
// Extends wildcard violation
List<Integer> integers = new LinkedList<Integer>();
List<? extends Number> numbers = integers;
numbers.get(i); // Works fi ne!
numbers.add(3); // Won’t compile!
// Super wildcard violation
List<Number> numbers = new LinkedList<Number>();
List<? super Integer> integers = numbers;
integers.add(3); // Works fi ne!
int i = integers.get(0); // Won’t’ compile!
Object o = integers.get(0); // Works fine, object is upper bound!
// Copy all elements, subclasses of T, from source to dest which con-
tains elements that are superclasses of T.
public static <T> void copy(List<? super T> dest, List<? extends T>
source) {
for (int i = 0; i < source.size(); i++) {
dest.set(i, source.get(i));
}
}
In addition to the above principle there are also a few restrictions on wildcards:
Don’t use wildcards when creating instances, specifying generic method calls or ex-
tending superclasses:
List<?> integers = new LinkedList<?>(); // Won’t compile, instance
creation!
List<?> integers = Lists.<?>factory(); // Won’t compile, generic
method!
class AnyList implements List<?> {} // Won’t compile, extend su-
perclass!
The syntax above is valid if any of the question marks is replaced by a proper class.
The generic method is shown here in case the syntax looks unfamiliar.
class Lists {
static <T> List<T> factory() {
return new LinkedList<T>();
}
}
Bounds
Bounds are used to make sure that generic parameters are of a specified subtype.
// The generic parameter of Query must extend (or implement) Entity and
Entity must have a getId method!
public class Dao<T extends Entity> {
T createOrUpdate(T entity) {
if (entity.getId() != null) {
return update(entity);
} else {
return create(entity);
}
}
}
// Using Dao
// Works fi ne since Person extends Entity!
Dao<Person> personDao = new Dao<Person>();
// Wont compile since String does not extend Entity!
Dao<String> stringDao = new Dao<String>();
Bounds may also be used in more advanced ways. The example below is a simplifi ed
version from java.util.Collections and show a recursive bound. The generic
parameter T is also used inside the bound Comparable<T> to make sure that the
objects contained in the collection are comparable amongst themselves.
// The method max takes a parameter which must contain elements of a
subclass of Comparable.
// In addition the Comparable class must be comparable with the declared
type
public static <T extends Comparable<T>> T max(Collection<T> collection)
{
T currentMax = collection.iterator().next();
for (T element: collection) {
if (currentMax.compareTo(element) < 0) currentMax = element;
}
return currentMax;
}
By far the most diffi cult generic declaration comes from java.lang.Enum and looks
like this Class Enum<E extends Enum<E>> implements Comparable<E>. Like the
above declaration this is a recursive bound but it is even more constrained than the
above. The key to understanding this is to know how enums are implemented in Java.
// Declaring an enum
enum Tapir {MALAYAN, BRAZILIAN, BAIRD, MOUNTAIN}
// creates a class similar to this!
public final class Tapir extends Enum<Tapir> implements Comparable<Tapir>
{
private Tapir(String name, int ordinal) {super(name, ordinal)}
public static final Tapir MALAYAN = new Tapir(”MALAYAN”, 0);
public static final Tapir BRAZILIAN = new Tapir(”BRAZILIAN”, 1);
public static final Tapir BAIRD = new Tapir(”BAIRD”, 2);
public static final Tapir MOUNTAIN = new Tapir(”MOUNTAIN”, 3);
private static final Tapir[] VALUES = {MALAYAN, BRAZILIAN, BAIRD,
MOUNTAIN};
public static Tapir[] values() {return VALUES.clone()}
public static Tapir valueOf(String name) {
for (Tapir t: VALUES) if t.getName().equals(name) return t;
throw new IllegalArumentException();
}
}
As can be seen in the code above E extends Enum<E> maps to Tapir extends and
Enum<Tapir>Comparable<E> maps to Comparable<Tapir>. This makes sure
that enums of one type can only be compared with enums of the same type. The
innermost generic parameter, Enum<E extends Enum<E>>, makes the subclass’ type
available to the superclass, allowing it to defi ne methods whose parameters and
return values are that of the subclass’.
Generic parameters may also have multiple bounds. The signature of the simplifi ed
example above actually looks like this:
// Actual signature of max from Java Collections public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> collection);
When multiple bounds appear the fi rst bound is used for erasure and the reason for
the Object in the signature above is that it makes the signature backwardly compat-
ible. The reason for the super and the extends are the same as above to make the
method more generic.
The method takes a collection of Ts or a collection of Ts subclasses (Collection<? ex-
tends T>) where T or one of Ts superclasses implements Comparable with erasure
Object (<T extends Object & Comparable<? super T>>).
Erasure
Java Generics is implemented by erasure. This means that the generic information
is removed when the class is compiled:
- The erasure of
List<Integer>, List<String>, List<List<Integer>>isList. - The erasure of
Comparable<? super T>isComparable. - The erasure of Object & Comparable is the leftmost, Object.
Another thing to be aware of is that the implementation of generics with erasure
forces the compiler to insert additional methods into the class files.
// Additional methods are compiled into generic classes
public interface Foo<T> {
void foo(T param);
}
public class Bar implements Foo<Bar> {
// This method will appear twice once with Object as parameter and
once with Bar.
public void foo(Bar param) {}
public static void main(String[] args) {
for (Method m : Bar.class.getMethods())
if (m.getName().startsWith(”foo”))
System.out.println(m.toGenericString());
}
}
$ java Bar
public void Bar.foo(Bar)
public volatile void Bar.foo(java.lang.Object)
This can trip you up if you try to overload a method to accept Object as a parameter
too. It has never been a good idea to overload with Object as well as a subclass of
Object but now it will not even compile:
Error:line (6)name clash: foo(java.lang.Object) in Bar and foo(T) in Foo<Bar> have the same erasure, yet neither overrides the other
Compatibility
All in all, the implementation of generics in Java is a wonderful example of crafts-
manship. The solution is binary compatible both backwardly and forwardly, allowing
new code to use old libraries as well as allowing old code to use new libraries. I do,
however, wish that they had skipped the compatibility and made generic classes
aware of what they are at runtime.
If you want to know more about generics I highly recommend the book Java Gener-
ics by Philip Wadler and Maurice Naftalin.
Originally published in JayView.
Anders Janmyr
Consultant at Jayway

0 comments ↓
There are no comments yet...Kick things off by filling out the form below.
Leave a Comment