Generics1)

  • A view to a regular collection that performs a runtime type check each time an element is inserted is called a checked collection:
    List<String> stringList = Collections.checkedList(new ArrayList<String>(), String.class);
  • Is a list of String also a list of Object? The answer is no, consider the following code:
     
    List<String> ls = new ArrayList<String>();
    List<Object> lo = ls; // not really allowed
    lo.add(new Object());
    String s = ls.get(0); // run-time error: ClassCastException

    Type-safety is a primary design goal of generics. In particular, the language is designed to guarantee that if your entire application has been compiled without unchecked warnings, it is type safe (does not cause exceptions like above).

  • The component type of an array may not be a type variable or a parametrized type, unless it is an (unbounded) wildcard type.
    List<String>[] lArr = new List<String>[10]; // not really allowed, the only possibility is: List<?>[] lArr = new List<?>[10];
    Object oA = lArr;
    Object[] oArr = (Object[]) o;
    List<Integer> l = new ArrayList<Integer>();
    l.add(new Integer(3));
    oAr[1] = l;
    String s = lArr[1].get(0); // run-time error: ClassCastException
  • Collection<Object> is a heterogeneous collection, while Collection<?> is a homogeneous collection of elements of the same unknown type. You cannot add anything into the last collection, except null (which can be cast to anything) and you can get only Object values from this collection. If you know in runtime the type of collection elements you can cast and add objects to collection using “getTypeArgument” trick described below.
  • When collection is declared with wildcard parametrized collection type, cannot be added elements to:
    Collection<? extends Pair<String, String>> c = new ArrayList<Pair<String, String>>();
    c.add(new Pair<String, String>("a", "b")); // compiler error

    The collection with wildcard parametrization of the type itself (Collection<Pair<String, ?>>) works fine.

  • void someMethod(Pair<?, ?> pair) { ... }

    is almost the same as

    <X, Y> void someMethod(Pair<X, Y> pair) { ... }

    except that you can use X and Y types in 2nd method implementation, while in 1st the only possible type is Object.

    <T> void someMethod(Pair<T, T> pair) { ... }

    makes sure that only pairs of elements of the same type are passed to the method.

  • Because there is only one byte code representation of each generic type or method, Java compiler chooses the most general method signature (that matches any parameter T) to delegate the call (Object or Object[] or Collection<?>):
    static void overloadedMethod(Collection<?> l) {
        System.out.println("overloadedMethod(Collection<?>)");
    }
    static void overloadedMethod(List<? extends Number> l) {
        System.out.println("overloadedMethod(List<? extends Number>)");
    }
     
    static <T> void genericMethod(List<T> l) {
        overloadedMethod(l) ;  // which method is called?
    }
     
    ...
     
    genericMethod(new ArrayList<Integer>()); // expected overloadedMethod(List<? extends Number> l) to be called
  • You cannot override the generics methods, if they do not have a 100% signature compatibility on compile time. You can always check this by putting @Override before a method call:
    class Box<T extends Serializable> {
        public void reset(T t) { ... }
    }
     
    class NumBox<S extends Number> extends Box<Long> {
        @Override
        public void reset(S t) { // compiler error here: the method must override a superclass method
            super.reset(t);
        }
    }
     
    (new NumBox<Long>()).reset(Long.valueOf(1)); // seems fine?
     
    // NumBox<S>.reset(S) might not be compatible with Box<Long>.reset(Long).
    // Compiler cannot guarantee this when generating a binary class for NumBox.
    (new NumBox<Integer>()).reset(Long.valueOf(1));    // Oops, is it Box.reset(Long) or NumBox.reset(Integer)? The method is not overriding a super class method anymore

    Correct examples follow:

    class NumBox<S extends Number> extends Box<S> {
        @Override
        public void reset(S t) { // fine here
            super.reset(t);
        }
    }
    class NumBox extends Box<Long> {
        @Override
        public void reset(Long t) { // fine here
            super.reset(t);
        }
    }
  • A wildcard parametrized type with a lower bound usage example:
    class Person implements Comparable<Person> {
         ...
    }
     
    class Student extends Person {
        ...
    }
     
    class Utilities {
        public static <T extends Comparable<T>> void sort(List<T> list) {
            ...
        }
    }
     
    List<Student> list = new ArrayList<Student>();
    Utilities.sort(list); // error

    The reason for the error message is that the compiler infers the type parameter of the sort method as T = Student and that class Student is not Comparable<Student>. It is Comparable<Person>, but that does not meet the requirements imposed by the bound of the type parameter of method sort. The solution is to use the following signature:

    class Utilities {
        public static <T extends Comparable<? super T >> void sort(List<T> list) {
            ...
        }
    }
  • “getThis” trick is a way to recover the type of this object in a class hierarchy. Example:
    public abstract class Node<N extends Node<N>> {
        private final List<N>    children = new ArrayList<N>();
        private final N        parent;
     
        protected Node(N parent) {
            this.parent = parent;
            parent.children.add(this); // error: incompatible types
        }
     
        public N getParent() {
            return parent;
        }
     
        public List<N> getChildren() {
            return children;
        }
    }
     
    public class SpecialNode extends Node<SpecialNode> {
        public SpecialNode(SpecialNode parent) {
            super(parent);
        }
    }

    The list has been declared as List<N>, where N is a subtype of Node, which cannot be casted to List<Node>. And this is Node, which causes the error. In order to overcome the problem, we need to recover the this object's actual type:

    public abstract class Node<N extends Node<N>> {
        private final List<N>    children = new ArrayList<N>();
        private final N        parent;
     
        protected abstract N getThis();
     
        protected Node(N parent) {
            this.parent = parent;
            parent.children.add(getThis()); // fine
        }
     
        public N getParent() {
            return parent;
        }
     
        public List<N> getChildren() {
            return children;
        }
    }
     
    public class SpecialNode extends Node<SpecialNode> {
        public SpecialNode(SpecialNode parent) {
            super(parent);
        }
     
        @Override
        protected SpecialNode getThis() {
            return this;
        }
    }
  • “getTypeArgument” trick is a technique for recovering the type argument from a wildcard parametrized type at run-time.
    interface GenericType<T> {
        void method(T arg);
    }
     
    class ConcreteType implements GenericType<TypeArgument> {
        public void method(TypeArgument arg) {...}
    }
     
    /* Some interface */
    class GenericUsage {
        // Using a wildcard parametrization of the generic interface:
        private GenericType<?> reference;
     
        public void method(Object arg) {
            reference.method(arg); // error
        }
    }

    In order to solve the problem, you add a method to the implementation of the generic interface that returns the type argument of the parametrisation of the generic interface that the class implements. This way you can later retrieve the type argument dynamically at run-time:

    interface GenericType<T> {
        void method(T arg);
     
        Class<T> getTypeArgument();
    }
     
    class ConcreteType implements GenericType<TypeArgument> {
        public void method(TypeArgument arg) {...}
     
        public Class<TypeArgument> getTypeArgument() {
            return TypeArgument.class;
        }
    }
     
    class GenericUsage {
        private GenericType<?> reference;
     
        public void method(Object arg) {
            _helper(reference, arg);
        }
     
        private static <T> void _helper(GenericType<T> reference, Object arg) {
            reference.method(reference.getTypeArgument().cast(arg));
        }
    }

    The work-around relies on the fact that the compiler performs type argument inference when a generic method is invoked. It means that the type of the reference argument in the helper method is not a wildcard parametrization, but a concrete parametrization for an unknown type that the compiler infers when the method is invoked.

  • There are cases when the generic version and the wildcard version of a method mean different things:
    public static class Box<S> {
        public Box(S s) {
        }
    }
     
    public static <T> void print1(List<Box<T>> list) {
        for (Box<T> box : list) {
            System.out.println(box);
        }
    }
     
    public static void print2(List<Box<?>> list) {
        for (Box<?> box : list) {
            System.out.println(box);
        }
    }
     
    public static void print3(List<? extends Box<?>> list) {
        for (Box<?> box : list) {
            System.out.println(box);
        }
    }
     
    List<Box<?>> list1 = new ArrayList<Box<?>>();
    list1.add(new Box<String>("abc"));
    list1.add(new Box<Integer>(Integer.valueOf(100)));
     
    print1(list1); // error: this generic method expects a list of boxes, each having the same type
    print2(list1); // fine
    print3(list1); // fine
     
    List<Box<Object>> list2 = new ArrayList<Box<Object>>();
    list2.add(new Box<Object>("abc"));
    list2.add(new Box<Object>(Integer.valueOf(100)));
     
    print1(list2); // fine
    print2(list2); // error: this wildcard method expects a list of boxes, where there is no restriction regarding the type of the boxes
    print3(list2); // fine
  • Using a generic helper method and wildcard capture.
    public static void reverse(List<?> list) {
        reverseCapture(list);
    }
     
    private static <T> void reverseCapture(List<T> list) {
        List<T> tmp = new ArrayList<T>(list);
        for (int i = 0; i < list.size(); i++) {
            tmp.set(i, list.get(list.size() - i - 1));
        }
        list = tmp;
    }

    More complicated case:

    class Pair<E> {
        private E fst, snd;
     
        public E getFirst() {
            return fst;
        }
     
        public void setFirst(E s) {
            fst = s;
        }
     
        public E getSecond() {
            return snd;
        }
     
        public void setSecond(E s) {
            snd = s;
        }
    }
     
    public static List<? extends Pair<?>> swapAndReverse(List<? extends Pair<?>> l) {
        return captureType(l);
    }
     
    public static <E, T extends Pair<E>> List<T> captureType(List<T> l) {
        final List<T> list = new ArrayList<T>(l);
        for (int i = 0; i < l.size(); i++) {
            list.set(i, l.get(l.size() - i - 1));
        }
        for (T pair : list) {
            E e = pair.getFirst();
            pair.setFirst(pair.getSecond());
            pair.setSecond(e);
        }
        return list;
    }

    The generic method has the advantage that it returns a concrete instantiation of List, that does not suffer from the limitations that come with the wildcard instantiation that is returned from the wildcard version of the method.

  • If the two method have the same erasure then the class is illegal and rejected with a compile-time error message.
    interface Equivalent<T> {
        boolean equalTo(T other);
    }
     
    interface EqualityComparable<T> {
        boolean equalTo(T other);
    }
     
    class SomeClass implements Equivalent<Double>, EqualityComparable<SomeClass> { // error, as interface methods have the same erasures, see below
        public boolean equalTo(Double other) { ... }
        public boolean equalTo(SomeClass other) { ... }
    }

    During type erasure the compiler does not only create the type erased versions of the two colliding interfaces, it would also try to create the necessary bridge methods. Bridge methods are synthetic methods generated by the compiler when a class has a parametrized supertype:

    interface Equivalent {
        boolean equalTo(Object other);
    }
     
    interface EqualityComparable {
        boolean equalTo(Object other);
    }
     
    class SomeClass implements Equivalent, EqualityComparable {
        public boolean equalTo(Double other)    { ... }
        public boolean equalTo(Object other)    { return equalTo((Double) other); }
        public boolean equalTo(SomeClass other) { ... }
        public boolean equalTo(Object other)    { return equalTo((SomeClass) other); }
    }

    The bridge methods would have the same signature. Instead of resolving the conflict the compiler reports an error. There is no problem if the two interfaces are generic and the conflicting methods have different type erasures:

    interface Equivalent<T extends Number> {
        boolean equalTo(T other);
    }
     
    interface EqualityComparable<T> {
        boolean equalTo(T other);
    }
     
    class SomeClass implements Equivalent<Double>, EqualityComparable<SomeClass> {
        public boolean equalTo(Double other)    { ... }
        public boolean equalTo(SomeClass other) { ... }
    }

    Example (after a conceivable translation by type erasure):

    interface Equivalent {
        boolean equalTo(Number other);
    }
     
    interface EqualityComparable {
        boolean equalTo(Object other);
    }
     
    class SomeClass implements Equivalent, EqualityComparable {
        public boolean equalTo(Double other)    { ... }
        public boolean equalTo(Number other)    { return equalTo((Double) other); }
        public boolean equalTo(SomeClass other) { ... }
        public boolean equalTo(Object other)    { return equalTo((SomeClass) other); }
    }

    As long as the colliding methods are instantiated for the same type argument there is no problem at all:

    class SomeClass implements Equivalent<SomeClass>, EqualityComparable<SomeClass> {
        public boolean equalTo(SomeClass other) { ... }
    }

    The class provide exactly one method, namely the matching one from both interfaces and the compiler generates one synthetic bridge method:

    class SomeClass implements Equivalent, EqualityComparable {
        public boolean equalTo(SomeClass other) { ... }
        public boolean equalTo(Object other)    { return equalTo((SomeClass) other); }
    }
1) From Java Generics FAQs by Angelika Langer, also read Generics in the Java Programming Language by Gilad Bracha
programming/java/java_generics.txt · Last modified: 2011/01/05 18:35 by dmitry
 
 
Recent changes RSS feed Driven by DokuWiki