Generics in Java

From Wikipedia, the free encyclopedia

Generics were added to the Java programming language in 2004 as part of J2SE 5.0. Unlike C++ templates, generic Java code generates only one compiled version of a generic class. Generic Java classes can only use object types as type parameters — primitive types are not allowed. Thus a List<Integer> (using a primitive wrapper class) is legal, while a List<int> is not.

In Java, generics are checked at compile time for type correctness. The generic type information is then removed via a process called type erasure, generic type information is retained only for superclass. For example, List<Integer> will be converted to the raw type (non-generic type) List, which can contain arbitrary objects. However, due to the compile-time check, the resulting code is guaranteed to be type correct, as long the code generated no unchecked compiler warnings.

One side-effect of this process is that the generic type information is typically not known at runtime. Thus, at runtime, a List<Integer> and a List<String> refer to the same class, List. One way to mitigate this side effect is through the use of the various methods in the class java.util.Collections that return dynamically checked versions of various collection types, including checkedCollection() and checkedList(). These methods decorate the declared Collection and check for improper use (e.g. insertion of an inappropriate type) of a typed Collection at runtime. This can be useful in situations when legacy code is interoperating with code that makes use of generics, as the program can verify that the legacy code doesn't insert an illegal value in the Collection.

Like C++ and C#, Java allows nested generic types. Thus List<Map<Integer, String>> is a valid type.

Contents

[edit] Wildcards

Generic type parameters in Java are not limited to specific classes. Java allows the use of wildcards to specify bounds on the type of parameters a given generic object may have. Wildcards are type parameters of the form "?", possibly annotated with a bound. Given that the exact element type of an object with a wildcard is unknown, restrictions are placed on the type of methods that may be called on the object.

As an example of an unbounded wildcard, List<?> indicates a list which has an unknown object type. Methods which take such a list as an argument can take any type of list, regardless of parameter type. Reading from the list will return objects of type Object, and writing non-null elements to the list is not allowed, since the parameter type is not known.

To specify the upper bound of a generic element, the extends keyword is used, which indicates that the generic type is a subtype of the bounding class. Thus it must either extend the class, or implement the interface of the bounding class. So List<? extends Number> means that the given list contains objects which extend the Number class. For example, the list could be List<Float> or List<Number>. Reading an element from the list will return a Number, while writing non-null elements is once again not allowed.

To specify the lower bound of a generic element, the super keyword is used, which indicates that the generic type is a supertype of the bounding class. So List<? super Number> could be List<Number> or List<Object>. Reading from the list returns objects of type Object. Any element of type Number can be added to the list, since it is guaranteed to be a valid type to store in the list.

[edit] Limitations

A limitation of the Java implementation of generics makes it impossible for a method with a generic argument to instantiate an object of that type:

T method(List<T> arg) {
    // this doesn't work, because the runtime system doesn't know
    // what type T is
    return new T();
}

Another limitation of the Java implementation of generics is that it is impossible to create an array of a generic class with a type parameter type other than <?>. This is because of the type erasure process that converts

List<Integer> l = new ArrayList<Integer>();
...
Integer i = l.get(0);

to

List l = new ArrayList();
...
Integer i = (Integer) l.get(0);

Note that the type parameter of the list is lost, and replaced by a cast. The compile time type checking guarantees that this cast will never produce a ClassCastException, because only Integers can be put into the List l. Now consider the situation where an array of the generic type had been created:

List<Integer>[] l = new ArrayList<Integer>[4];
...
Integer i = l[0].get(0);

would compile into

List[] l = new ArrayList[4];
...
Integer i = (Integer) l[0].get(0);

The runtime system knows that l must contain Lists, but does not know what kind of List. It therefore can't prevent the algorithm represented by ... from casting l to a Object[] array and putting a List<String> into it for example. In order to maintain the guarantee that the inserted cast will not fail, the compiler simply prevents you from having arrays of generic types. [1]

[edit] See also

[edit] References

In other languages