Experiences with the New Java 5 Language Features
by Jess Garms and Tim Hanson
Originally published on BEA Dev2Dev December 2005
Generic Methods
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>
.
Generic methods without parameters
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.
Beyond collections
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 anewInstance
without casting. -
Comparable<T>
Comparable
is parameterized by the actual comparison type. This provides stronger typing oncompareTo()
invocations. For example,String
implementsComparable<String>
. InvokingcompareTo()
on anything other than aString
will fail at compile time. -
Enum<E extends Enum<E>>
Enum
is parameterized by the enum type. An enum calledColor
would extendEnum<Color>
. ThegetDeclaringClass()
method returns the class object for the enum type, which in this case would be aColor
. It's different fromgetClass()
, which may return an anonymous class.
Wildcards
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.
Upper bounded wildcards
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.
Upper bounds
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
Number
s 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
Integer
s.
Wildcards give us more expressive power than is possible with arrays.
Why use wildcards
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;
}
}
Wildcards and covariant returns
Another common use for wildcards is with covariant returns. The same rules apply to covariant returns as assignments. If you want to return a more specific generic type in an overridden method, the declaring method must use wildcards:
public interface NumberGenerator {
public List<? extends Number> generate();
}
public class FibonacciGenerator extends NumberGenerator {
public List<Integer> generate() {
...
}
}
If this were to use arrays, the interface could return Number[]
and the implementation could return Integer[]
.
Lower bounds
We've talked mostly about upper bounded wildcards. There is also a lower bounded wildcard. A List<? super Number>
is a list whose exact "element type" is unknown, but it is M Number
or a super type of Number
. So it could be a List<Number>
or a List<Object>
.
Lower bounded wildcards are not nearly as common as upper bounded wildcards. But when you need them, they are essential.
Lower vs. upper bounds
List<? extends Number> readList = new ArrayList<Integer>();
Number n = readList.get(0);
List<? super Number> writeList = new ArrayList<Object>();
writeList.add(new Integer(5));
The first list is a list that you can read numbers from.
The second list is a list that you can write numbers to.
Unbounded wildcard
Finally, the List<?>
is a list of anything and is almost the same as List<? extends Object>
. You can always read Object
s, but you cannot write to the list.
Wildcards in public APIs
To summarize, wildcards are great for hiding implementation details from callers as we saw a few sections back, but even though lower bounded wildcards appear to provide read-only access, they do not, due to non-generic methods such as remove(int position)
. If you want a truly immutable collection, use the methods on java.util.Collections
, like unmodifiableList()
.
Be aware of wildcards when writing APIs. In general, you should try to use wildcards when passing generic types. It makes the API accessible to a wider range of callers.
In this example, by accepting a List<? extends Number>
instead of List<Number>
, the method below can be called with many different types of List
s:
void removeNegatives(List<? extends Number> list);
Constructing Generic Types
Now we'll cover constructing your own generic types. We'll show example idioms where type safety can be improved by using generics, as well as common problems that occur when trying to implement generic types.
Collection-like functions
This first example of a generic class is a collection-like example. Pair
has two type parameters, and the fields are instances of the types:
public final class Pair<A,B> {
public final A first;
public final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
}
This makes it possible to return two items from a method without having to write special-purpose classes for each two-type combo. The other thing you could have done is return Object[]
, which isn't type-safe or pretty.
In the usage below, we return a File
and a Boolean
from a method. The client of the method can use the fields directly without casting:
public Pair<File,Boolean> getFileAndWriteStatus(String path){
// create file and status
return new Pair<File,Boolean>(file, status);
}
Pair<File,Boolean> result = getFileAndWriteStatus("...");
File f = result.first;
boolean writeable = result.second;
Beyond collections
In this example generics are used for additional compile-time safety. By parameterizing the DBFactory
class by the type of Peer
it creates, you are forcing Factory
subclasses to return a specific subtype of Peer
:
public abstract class DBFactory<T extends DBPeer> {
protected abstract T createEmptyPeer();
public List<T> get(String constraint) {
List<T> peers = new ArrayList<T>();
// database magic
return peers;
}
}
By implementing DBFactory<Customer>
the CustomerFactory
is forced to return a Customer
from createEmptyPeer()
:
public class CustomerFactory extends DBFactory<Customer>{
public Customer createEmptyPeer() {
return new Customer();
}
}
Generic methods
Whenever you want to place constraints on a generic type between parameters or a parameter and a return type, you probably want to use a generic method.
For example, if you write a reverse function that reverses in place, you don't need a generic method. However, if you want reverse to return a new List
, you'd like the element type of the new List
to be the same as the List
that was passed in. In that case, you need a generic method:
<T> List<T> reverse(List<T> list)
Reification
When implementing a generic class, you may want to construct an array, T[]
. Because generics is implemented by erasure, this is not allowed.
You may try to cast an Object[]
to T[]
. This is not safe.
Reification solution
The solution, courtesy of the generics tutorial, is to use a "Type Token." By adding a Class<T>
parameter to the constructor, you force clients to supply the correct class object for the type parameter of the class:
public class ArrayExample<T> {
private Class<T> clazz;
public ArrayExample(Class<T> clazz) {
this.clazz = clazz;
}
public T[] getArray(int size) {
return (T[])Array.newInstance(clazz, size);
}
}
To construct an ArrayExample<String>
, the client would have to pass String.class
to the constructor because the type of String.class
is Class<String>
.
Having the class objects makes it possible then to construct an array with exactly the right element type.
Conclusion
In summary, the new language features make for a substantial change to Java. By understanding when and how to use them, you'll write better code.
Jess Garms is the Javelin compiler team lead at BEA Systems. Prior to that, Jess worked on BEA's Java IDE, WebLogic Workshop. Additionally, he has a great deal of experience with cryptography, and co-authored Professional Java Security, published by Wrox Press.
Tim Hanson is the Javelin compiler architect at BEA Systems. Tim developed much of BEA's Java compiler - one of the earliest 1.5-compliant implementations. He has written numerous other compilers, including a CORBA/IDL compiler while at IBM, and an XQuery compiler.