Java generics explained in short form with examples

Please see this FAQ for more Information:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1

Here in short form some importent points from this FAQ.

Example (of a generic type):

class Pair<X,Y>  { 
  private X first;
  private Y second;   public Pair(X a1, Y a2) {
    first  = a1;
    second = a2;
  }
  public X getFirst()  { return first; }
  public Y getSecond() { return second; }
  public void setFirst(X arg)  { first = arg; }
  public void setSecond(Y arg) { second = arg; }
}

The class Pair has two type parameters X and Y .  They are replaced by type arguments when the generic type Pair is instantiated. For instance, in the declaration Pair<String, Date> the type parameter X is replaced by the type argument String and Y is replaced by  Date .
The scope of the identifiers X and Y is the entire definition of the class.  In this scope the two type parameters X and Y are used like they were types (with some restrictions). In the example above, the type parameters are used as the argument and return type of instance methods and the types of instance fields.
Type parameters can be declared with bounds. Bounds give access to methods of the unknown type that the type parameter stands for. In our example, we do not invoke any methods of the unknown types X and Y . For this reason, the two type parameters are unbounded.

Example (of a concrete parameterized type):

public void printPair( Pair<String,Long> pair) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Pair<String,Long> limit = new Pair<String,Long> ("maximum",1024L);
printPair(limit);

Example (of a wildcard parameterized type):

public void printPair( Pair<?,?> pair) {
  System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Pair<?,?> limit = new Pair<String,Long> ("maximum",1024L);
printPair(limit);

All instantiations of a generic type share the same runtime type representation, namely the representation of the raw type. For instance, the instantiations of a generic type List ,  such as List<Date> , List<String> , List<Long> , etc. have different static types at compile time, but the same dynamic type List at runtime.

A cast consists of two parts:

  • a static type check performed by the compiler at compile time and
  • a dynamic type check performed by the virtual machine at runtime.

The static part sorts out nonsensical casts, that cannot succeed, such as the cast from String to Date or from List<String> to List<Date> .

The dynamic part uses the runtime type information and performs a type check at runtime.  It raises a ClassCastException if the dynamic type of the object is not the target type (or a subtype of the target type) of the cast. Examples of casts with a dynamic part are the cast from Object to String or from Object to List<String> .  These are the so-called downcasts, from a supertype down to a subtype.

Examples of concrete parameterized types are List<String> , Map<String,Date> , but not List<? extends Number> or Map<String,?> .

It is sometimes expected that a List<Object> would be a supertype of a List<String> , because Object is a supertype of String .  This expectation stems from the fact that such a type relationship exists for arrays:  Object[] is a supertype of String[] , because Object is a supertype of String . (This type relationship is known as covariance .)  The super-subtype-relationship of the component types extends into the corresponding array types. No such a type relationship exists for instantiations of generic types. (Parameterized types are not covariant.)

The lack of a super-subtype-relationship among instantiations of the same generic type has various consequences.  Here is an example.

Example:

void printAll(ArrayList<Object> c) {
  for (Object o : c) 
    System.out.println(o);
} ArrayList<String> list = new ArrayList<String>();
... fill list ...
printAll(list);   // error

A ArrayList<String> object cannot be passed as argument to a method that asks for a ArrayList<Object> because the two types are instantiations of the same generic type, but for different type arguments, and for this reason they are not compatible with each other.

Concrete parameterized types are concrete instantiations of a generic type.  They are almost like types; there are only a few restrictions.  They can NOT be used for the following purposes:

  • for creation of arrays
  • in exception handling
  • in a class literal
  • in an instanceof expression

Arrays holding elements whose type is a concrete parameterized type are illegal.

Example (of illegal array type):

static void test() {
  Pair<Integer,Integer>[] intPairArr = new Pair<Integer,Integer>[10] ; // error
  addElements(intPairArr); 
  Pair<Integer,Integer> pair = intPairArr[1];
  Integer i = pair.getFirst();
  pair.setSecond(i);
}
static void addElements( Object[] objArr) {
  objArr[0] = new Pair<Integer,Integer>(0,0);
  objArr[1] = new Pair<String,String>("","");      // should fail with ArrayStoreException
}

The compiler prohibits creation of arrays whose component type is a concrete parameterized type, like Pair<Integer,Integer> in our example.  We discussed in the preceding entry why is it reasonable that the compiler qualifies a Pair<Integer,Integer>[] as illegal.  The key problem is that compiler and runtime system must ensure that an array is a homogenous sequence of elements of the same type.  One of the type checks, namely the array-store-check performed by the virtual machine at runtime, fails to detect the offending insertion of an alien element. In the example the second insertion in the addElements method should fail, because were are adding a pair of strings to an array of integral values, but it does not fail as expected  The reasons were discussed in the preceding entry.

If we cannot use arrays holding elements whose type is a concrete parameterized type, what do we use as a workaround?

Let us consider 3 conceivable workarounds:

  • array of raw type
  • array of unbounded wildcard parameterized type
  • collection instead of array

While arrays of concrete parameterized types are illegal, collections of concrete parameterized types are permitted.

A wildcard parameterized type is an instantiation of a generic type where at least one type argument is a wildcard.  Examples of wildcard parameterized types are Collection<?> , List<? extends Number> , Comparator<? super String> and Pair<String,?> . A wildcard parameterized type denotes a family of types comprising concrete instantiations of a generic type.  The kind of the wildcard being used determines which concrete parameterized types belong to the family.  For instance, the wildcard parameterized type Collection<?> denotes the family of all instantiations of the Collection interface regardless of the type argument.  The wildcard parameterized type List<? extends Number> denotes the family of all list types where the element type is a subtype of  Number .  The wildcard parameterized type Comparator<? super String> is the family of all instantiations of the Comparator interface for type argument types that are supertypes of String.

A wildcard parameterized type is not a type in the regular sense (different from a non-parameterized class/interface or a raw type).

Wildcard parameterized types can be used for typing (like non-parameterized classes and interfaces):

  • as argument and return types of methods
  • as type of a field or local reference variable
  • as component type of an arrayas type argument of other parameterized types
  • as target type in casts

Not only types can be generic, but methods can be generic, too. Static and non-static methods as well as constructors can have type parameters. The syntax for declaration of the formal type parameters is similar to the syntax for generic types. The type parameter section is delimited by angle brackets and appears before the method’s return type. Its syntax and meaning is identical to the type parameter list of a generic type.

Here is the example of a generic max method that computes the greatest value in a collection of elements of an unknown type A .

Example (of a generic method):

class Collections {
  public static <A extends Comparable<A>> A max(Collection< A > xs) {
    Iterator< A > xi = xs.iterator();
    A w = xi.next();
    while (xi.hasNext()) {
      A x = xi.next();
      if (w.compareTo(x) < 0) w = x;
    }
    return w;
  }

The max method has one type parameter, named A .  It is a place holder for the element type of the collection that the method works on. The type parameter has a bound; it must be a type that is a subtype of Comparable<A>

Example (of the same generic type, this time with bounds):

public interface Comparable<T> {
  public int compareTo(T arg);
}
public class TreeMap<Key extends Comparable<Key> ,Data>{
  private static class Entry<K,V> {
     K key;
     V value;
     Entry<K,V> left = null;
     Entry<K,V> right = null;
     Entry<K,V> parent;
  }
  private transient Entry<Key,Data> root = null;
  ...
  private Entry<Key,Data> getEntry(Key key) {
     Entry<Key,Data> p = root;
     Key k = key;
     while (p != null) {
       int cmp = k. compareTo(p.key)
       if (cmp == 0)
         return p;
       else if (cmp < 0)
         p = p.left;
       else 
         p = p.right; 
     }
     return null;
  }
  public boolean containsKey(Key key) {
     return getEntry(key) != null;
  }
  ...
}

In the example above, the type parameter Key has the bound Comparable<Key> .  Specification of a bound has two effects:

  • It gives access to the methods that the bound specifies .  In the example, the bound Comparable<Key> gives access to the compareTo method that we want to invoke in the implementation of our TreeMap class.
  • Only types “within bounds” can be used for instantiation of the generic type.   In the example, a parameterized type such as TreeMap<Number,String> would be rejected, because the type Number is not a subtype of Comparable<Number> .  A parameterized type like TreeMap<String,String> would be accepted, because the type String is within bounds, i.e. is a subtype of Comparable<String> .

All classes, interfaces, and enum types can be used as type parameter bound, including nested and inner types.  Neither primitive types nor array types be used as type parameter bound.

Examples (of type parameter bounds):

class X0 <T extends int > { ... }      // error
class X1 <T extends Object[] > { ... } // error
class X2 <T extends Number > { ... }
class X3 <T extends String > { ... }
class X4 <T extends Runnable > { ... }
class X5 <T extends Thread.State > { ... }
class X6 <T extends List > { ... }
class X7 <T extends List<String> > { ... }
class X8 <T extends List<? extends Number> > { ... }
class X9 <T extends Comparable<? super Number> > { ... }
class X10<T extends Map.Entry<?,?> > { ... }

The code sample shows that primitive types such as int and array types such as Object[] are not permitted as type parameter bound.

Class types, such as Number or String , and interface types, such as Runnable , are permitted as type parameter bound.

Enum types, such as Thread.State are also permitted as type parameter  bound. Thread.State is an example of a nested type used as type parameter bound.  Non-static inner types are also permitted.

Raw types are permitted as type parameter bound; List is an example.

Parameterized types are permitted as type parameter bound, including concrete parameterized types such as List<String> ,  bounded wildcard parameterized types such as List<? extends Number> and Comparable<? super Long> , and unbounded wildcard parameterized types such as Map.Entry<?,?> .  A bound that is a wildcard parameterized type allows as type argument all types that belong to the type family that the wildcard denotes.  The wildcard parameterized type bound gives only restricted access to fields and methods; the restrictions depend on the kind of wildcard.

Example (of wildcard parameterized type as type parameter bound):

class X< T extends List<? extends Number> > {
  public void someMethod(T t) {
    t.add(new Long(0L));    // error
    Number n = t.remove(0);
  }
}
class Test {
  public static void main(String[] args) {
     X<ArrayList< Long >>   x1 = new X<ArrayList<Long>>(); 
     X<ArrayList< String >> x2 = new X<ArrayList<String>>(); // error
  }
}

Reference variables of type T (the type parameter) are treated like reference variables of a wildcard type (the type parameter  bound).  In our example the consequence is that the compiler rejects invocation of methods that take an argument of the “unknown” type that the type parameter stands for, such as List.add , because the bound is a wildcard parameterized type with an upper bound.
At the same time the bound List<? extends Number> determines the types that can be used as type arguments. The compiler accepts all type arguments that belong to the type family List<? extends Number> , that is, all subtypes of List with a type argument that is a subtype of Number .

A type parameter can be used as the bound of another type parameter.

Example (of a type parameter used as a type parameter bound):

class Triple <T> {
  private T fst, snd, trd;
  public < U extends T , V extends T , W extends T > Triple(U arg1, V arg2, W arg3) {
    fst = arg1;
    snd = arg2;
    trd = arg3;
  }
}

In this example the type parameter T of the parameterized class is used as bound of the type parameters U V and W of a parameterized instance method of that class.

Further opportunities for using type parameters as bounds of other type parameters include situations where a nested type is defined inside a generic type or a local class is defined inside a generic method.  It is even permitted to use a type parameter as bound of another type parameter in the same type parameter section.

A bound that is a class gives access to all its public members, that is, public fields, methods, and nested type. Only constructors are not made accessible, because there is no guarantee that a subclass of the bound has the same constructors as the bound.

Example (of a class used as bound of a type parameter):

public class SuperClass {     // static members
    public enum EnumType {THIS, THAT}
    public static Object staticField;
    public static void staticMethod() { ... }

    // non-static members
    public class InnerClass { ... }
    public Object nonStaticField;
    public void nonStaticMethod() { ... }

    // constructors
    public SuperClass() { ... }

    // private members
    private Object privateField; 

    ...
}

public final class SomeClass<T extends SuperClass > {
    private T object;
    public SomeClass(T t) { object = t; } 

    public String toString() {
        return
         "static nested type    : "+T.EnumType.class+"\n" 
        +"static field          : "+T.staticField+"\n"
        +"static method         : "+T.staticMethod()+"\n"
        +"non-static nested type: "+T.InnerClass.class+"\n"
        +"non-static field      : "+object.nonStaticField+"\n"
        +"non-static method     : "+object.nonStaticMethod()+"\n"
        +"constructor           : "+(new T())+"\n"                    // error
        +"private member        : "+object.privateField+"\n"          // error 
        ;
    }
}

The bound SuperClass gives access to its nested types, static fields and methods and non-static fields and methods.  Only the constructor is not accessible.  This is because constructors are not inherited. Every subclass defines its own constructors and need not support its superclass’s constructors.  Hence there is no guarantee that a subclass of SuperClass will have the same constructor as its superclass.

The context in which “ Enum<E extends Enum<E>> ” appears is the declaration of the Enum class in package java.lang :

public abstract class Enum<E extends Enum<E>> {
  ...
}

The type Enum is the common base class of all enumeration types.  In Java an enumeration type such as Color is translated into a class Color that extends Enum<Color> . The purpose of the superclass Enum is to provide functionality that is common to all enumeration types.

Here is a sketch of class Enum :

public abstract class Enum< E extends Enum<E>> implements Comparable< E >, Serializable {
  private final String name;
  public  final String name() { ... }   private final int ordinal;
  public  final int ordinal() { ... }

  protected Enum(String name, int ordinal) { ... }

  public String           toString() { ... }
  public final boolean    equals(Object other) { ... }
  public final int        hashCode() { ... }
  protected final Object  clone() throws CloneNotSupportedException { ... }
  public final int        compareTo( E o) { ... }

  public final Class< E > getDeclaringClass() { ... }
  public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}

The surprising feature in the declaration  “ Enum<E extends Enum<E>> ” is the fact that the newly defined class Enum and its newly defined type parameter E appear in the bound of that same type parameter.  It means that the Enum type must be instantiated for one of its subtypes.  In order to understand why this makes sense, consider that every enum type is translated into a subtype of Enum .

Here is the contrived enum type Color :

enum Color {RED, BLUE, GREEN}

The compiler translates it into the following class:

public final class Color extends Enum<Color> {
  public static final Color[] values() { return (Color[])$VALUES.clone(); }
  public static Color valueOf(String name) { ... }   private Color(String s, int i) { super(s, i); }

  public static final Color RED;
  public static final Color BLUE;
  public static final Color GREEN;

  private static final Color $VALUES[];

  static {
    RED = new Color("RED", 0);
    BLUE = new Color("BLUE", 1);
    GREEN = new Color("GREEN", 2);
    $VALUES = (new Color[] { RED, BLUE, GREEN });
  }
}

The inheritance has the effect that the Color type inherits all the methods implemented in Enum<Color> .  Among them is the compareTo method.  The Color.compareTo method should probably take a Color as an argument.  In order to make this happen class Enum is generic and the Enum.compareTo method takes Enum ‘s type parameter E as an argument.  As a result, type Color derived from Enum<Color> inherits a compareTo method that takes a Color as and argument, exactly as it should.
If we dissect the declaration “ Enum<E extends Enum<E>> ” we can see that this pattern has several aspects.

First, there is the fact that the type parameter bound is the type itself: “ Enum <E extends Enum <E>> “. It makes sure that only subtypes of type Enum are permitted as type arguments. (Theoretically, type Enum could be instantiated on itself, like in Enum<Enum> , but this is certainly not intended and it is hard to imagine a situation in which such an instantiation would be useful.)

Second, there is the fact that the type parameter bound is the parameterized type Enum <E> ,  which uses the type parameter E as the type argument of the bound. This declaration makes sure that the inheritance relationship between a subtype and an instantiation of Enum is of the form “ X extends Enum<X> “. A subtype such as “ X extends Enum<Y> ” cannot be declared because the type argument Y would not be within bounds; only subtypes of Enum<X> are within bounds.

Third, there is the fact that Enum is generic in the first place.  It means that some of the methods of class Enum take an argument or return a value of an unknown type (or otherwise depend on an unknown type). As we already know, this unknown type will later be a subtype X of Enum<X> .  Hence, in the parameterized type Enum<X> , these methods involve the subtype X , and they are inherited into the subtype X .  The compareTo method is an example of such a method; it is inherited from the superclass into each subclass and has a subclass specific signature in each case.

To sum it up, the declaration “ Enum<E extends Enum<E>> ” can be decyphered as: Enum is a generic type that can only be instantiated for its subtypes, and those subtypes will inherit some useful methods, some of which take subtype specific arguments (or otherwise depend on the subtype).

Lower Bound Type Parameters of Classes

Type parameters can have several bounds, like in class Box<T extends Number> {...} . But a type parameter can have no lower bound, that is, a construct such as class Box<T super Number> {...} is not permitted.  Why not?  The answer is: it is pointless because it would not buy you anything, were it allowed.  Let us see why lower bound type parameters of classes are confusing by exploring what a upper bound on a type parameter means.

The upper bound on a type parameter has three effects:

    1. Restricted Instantiation. The upper bound restricts the set of types that can be used for instantiation of the generic type.  If we declare a class Box<T extends Number> {...} then the compiler would ensure that only subtypes of Number can be used as type argument.  That is, a Box<Number> or a Box<Long> is permitted, but a Box<Object> or Box<String> would be rejecte

Example (of restricted instantiation due to an upper bound on a type parameter):
class Box<T extends Number> { 
   private T value; 
   public Box(T t) { value = t; } 
   ...
class Test { 
   public static void main(String[] args) { 
       Box<Long>    boxOfLong   = new Box<Long>(0L);   // fine
       Box<String>  boxOfString = new Box<String>(""); // error: String is not within bounds
   }
}

  1. Access To Non-Static Members. The upper bound gives access to all public non-static methods and fields of the upper bound.  In the implementation of our class Box<T extends Number> {...} we can invoke all public non-static methods defined in class Number , such as intValue() for instance.  Without the upper bound the compiler would reject any such invocation.Example (of access to non-static members due to an upper bound on a type parameter):

class Box<T extends Number> { 
   private T value; 
   public Box(T t) { value = t; } 
   public int increment() { return value.intValue()+1; } // <= would be an error without the Number bound
   ...

    1. Type Erasure. The leftmost upper bound is used for type erasure and replaces the type parameter in the byte code.  In our class Box<T extends Number> {...} all occurrences of T would be replaced by the upper bound Number .  For instance, if class Box has a private field of type T and a method void set(T content) for setting this private field, then the field would be of type Number after type erasure and the method would be translated to a method void set(Number content) .

Example (of use of upper bound on a type parameter in type erasure – before type erasure):
class Box< T extends Number > { 
   private T value; 
   public Box( T t) { value = t; } 
   ...

      Example (of use of upper bound on a type parameter in type erasure –

after

      type erasure):

 

class Box { 
   private Number value; 
   public Box( Number t) { value = t; } 
   ...
}

    In addition, the leftmost upper bound appears in further locations, such as automatically inserted casts and bridge methods.

If lower bounds were permitted on type parameters, which side effects would or should they have? If a construct such as class Box<T super Number> {...} were permitted, what would it mean?  What would the 3 side effects of an upper type parameter bound  – restricted instantiation, access to non-static member, type erasure – mean for a lower bound?

Restricted Instantiations. The compiler could restrict the set of types that can be used for instantiation of the generic type with a lower bound type parameter.  For instance, the compiler could permit instantiations such as Box<Number> and Box<Object> from a Box<T super Number> and reject instantiations such as Box<Long> or Box<Short> .  This would be an effect in line with the restrictive side-effect described for upper type parameter bounds.

Access To Non-Static Members. A lower type parameter bound does not give access to any particular methods beyond those inherited from class Object . In the example of Box<T super Number> the supertypes of Number have nothing in common, except that they are reference types and therefore subtypes of Object .  The compiler cannot assume that the field of type T is of type Number or a subtype thereof.  Instead, the field of type T can be of any supertype of Number , such as Serializable or Object .  The invocation of a method such as intValue() is no longer type-safe and the compiler would have to reject it. As a consequence, the lower type parameter bound would not give access to an non-static members beyond those defined in class Object and thus has the same effect as “no bound”.

Type Erasure .  Following this line of logic, it does not make sense to replace the occurences of the type parameter by its leftmost lower bound.  Declaring a method like the constructor Box( T t) as a constructor Box( Number t) does not make sense, considering that T is replaces by a supertype of Number .  An Object might be rightly passed to the constructor in an instantiation Box<Object> and the constructor would reject it.  This would be dead wrong.   So, type erasure would replace all occurences of the type variable T by type Object , and not by its lower bound.  Again, the lower bound would have the same effect as “no bound”.

Do you want to figure out what it would mean if both lower _and_ upper bounds were permitted?   Personally, I do not even want to think about it and  would prefer to file it under “not manageable”, if you permit.

The bottom line is:  all that a “ super ” bound would buy you is the restriction that only supertypes of Number can be used as type arguments.  And even that is frequently misunderstood.  It would NOT mean, that class Box<T super Number> {...} contains only instances of supertypes of Number .  Quite the converse – as the example below demonstrates!

Example (of use of upper bound on a type parameter in type erasure – before type erasure):

class Box< T super Number > {
   private T value;
   public Box( T t) { value = t; }
   ...
}

Example (of use of upper bound on a type parameter in type erasure – after type erasure):

class Box {
   private Object value;
   public Box( Object t) { value = t; }
   ...
}

A class Box<T super Number> {...} would be translated by type erasure to a Box containing an Object field and it’s constructor would be translated to Box(Object t) . That’s fundamentally different from a class Box<T extends Number> {...} , which would be translated to a Box containing a Number field and it’s constructor would be translated to Box(Number t) . Consequently, a Box<Number> instantiated from a class Box<T extends Number> {...} would be different from a Box<Number> instantiation from a class Box<T super Number> {...} , which is likely to cause confusion.  For this reason lower bounds do not make sense on type parameters of classes.

 


 

Lower Bound Type Parameters of Methods

In conjunction with methods and their argument types, a type parameter with a lower bound can occasionally be useful.

Example (of a method that would profit from a type parameter with a lower bound):

class Pair<X,Y> {
   private X first;
   private Y second;
   ...
   public < A super X , B super Y > B addToMap(Map<A,B> map) {  // error: type parameter cannot have lower bound
      return map.put(first, second);
   }
}
class Test { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = pair.addToMap(map);
   }
}

The addToMap() method adds the content of the pair to a map.  Any map that can hold supertypes of X and Y would do.  The map’s put() method returns the value found in the map for the given key, if there already is a key-value entry for the key in the map.  The return value of the map’s put() method shall be returned from the addToMap() method.  Under these circumstances one would like to declare the method as shown above: The map is parameterized with supertypes of the pair’s type parameters and the addToMap() method’S return type is the map’s value type.

Since the compiler does not permit lower bounds on type parameters we need a work-around.

One work-around that comes to mind is use of a wildcard, because wildcards can have a lower bound.  Here is a work-around using a wildcard.

Example (of a work-around for the previous example using wildcards):

class Pair<X,Y> {
   private X first;
   private Y second;
   ...
   public Object addToMap(Map< ? super X , ? super Y > map) { 
      return map.put(first, second);
   }
}
class Test { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = (Number) pair.addToMap(map);
   }
}

It works, except that there is no way to declare the return type as desired.  It would be the supertype of Y that the compiler captures from the map type, but there is no syntax for specifying it.  We must not declare the return type a “ ? super Y “, because “ ? super Y ” is a wildcard and  not a type and therefore not permitted as a return type. We have no choice and must use Object instead as our method’s return type.  This rather unspecific return type in turn forces callers of the addToMap() method into casting the return value down from Object to its actual type.  This is not exactly what we had in mind.

Another work-around is use of static methods.  Here is a work-around with a static instead of a non-static method.

Example (of a work-around for the previous example using a static method):

class Pair<X,Y> {
   private X first;
   private Y second;
   ...
   public static < A , B ,X extends A ,Y extends B > B addToMap(Pair<X,Y> pair, Map< A , B > map) {
      return map.put(pair.first,pair.second);
   }
}
class Test { 
   public static void main(String[] args) { 
     Pair<String,Long> pair = new Pair<>("ABC",42L);
     Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
     Number number = Pair .addToMap( pair ,map);
   }
}

The generic addToMap() method has four type parameters: two placeholders X and Y for the pair’s type and two placeholders A and B for the map’s type. A and B are supertypes of X and Y , because X and Y   are declared with A and B as their upper bounds. (Note, that the generic method’s type parameters X and Y have nothing to do with the Pair class’s X and Y parameters.  The names X and Y are reused for the generic method to make them easily recognizably as the pair’s type parameters.)  Using four type parameters we can declare the precise return type as desired: it is the same type as the value type of the map.

Example (of illegal use of type parameter in static context of a generic class):

class SomeClass <T> {
  // static initializer, static field, static method
  static {
    SomeClass< T > test = new SomeClass< T >(); // error
  }
  private static T globalInfo;          // error
  public  static T getGlobalInfo() {        // error
    return globalInfo;
  }
  // non-static initializer, non-static field, non-static method
  { 
    SomeClass< T > test = new SomeClass< T >();
  }
  private        T localInfo;
  public         T getLocalInfo() { 
    return localInfo;
  }
  // static nested types
  public static class Failure extends Exception {
    private final T info;           // error
    public Failure( T t) { info = t; }      // error
    public T getInfo() { return info; }    // error
   }
  private interface Copyable {
    T copy();                       // error
  }
  private enum State {
    VALID, INVALID;
    private T info; // error
    public void setInfo( T t) { info = t; } // error
    public T getInfo() { return info; }    // error
  }
  // non-static nested types
  public class Accessor {
    public T getInfo() { return localInfo; }
  }
}

The example illustrates that the type parameter cannot be used in the static context of a generic class.  It also shows that nested interfaces and enum types are considered static type members of the class.  Only inner classes, that is, non-static nested classes, can use the type parameter of the enclosing generic class.

The scope of an interface’s type parameter is the entire definition of the interface, except any fields or nested types.  This is because fields and nested types defined in an interface are implicitly static.

Example (of illegal use of type parameter in a generic interface):

interface SomeInterface <T> {
  // field
  SomeClass< T > value = new SomeClass< T >();  // error
  // nested type
  class Accessor {
    public T getInfo() {            // error
      return value.getGlobalInfo();
    }
  }
  // methods
  T getValue();
}

The example shows that fields of an interface are implicitly static, so that the type parameter cannot be used anywhere in the declaration of a field of a generic interface.  Similarly, the nested class is considered a static nested class, not an inner class, and for this reason use of the type parameter anywhere in the nested class is illegal.

Workaround for nested static classes and interfaces:

In case of static nested classes and interfaces this is not a major calamity.  As a workaround we can generify the static class or interface itself.

Example (workaround – generify the nested static type):

class Wrapper <T> {
  private final T theObject;

  public Wrapper( T t) { theObject = t; }
  public T getWrapper() { return theObject; }   public Immutable <T> makeImmutable() {
        return new Immutable <T> (theObject);
  }
  public Mutable <T> makeMutable() {
        return new Mutable <T> (theObject);
  }
  private static <A> A makeClone(A theObject) { ... }

  public static final class Immutable <A> { // is a generic class now
        private final A theObject;

        public Immutable( A arg) {
            theObject = makeClone(arg);
        }
        public A getWrappedItem() {
            return makeClone(theObject);
        }
  }
  public static class Mutable <A> {
        ... similar ...
  }
}

There is no such workaround for nested enum type because they cannot be generic.

Workaround for nested static classes:

If the nested static type is a class, an alternative workaround would be turning the static class into an non-static inner class.

Example (workaround – use an inner class):

class Wrapper <T> {
  private final T theObject;

  public Wrapper( T t) { theObject = t; }
  public T getWrapper() { return theObject; }   public Mutable makeMutable() {
      return this. new Mutable(theObject);
  }
  public Immutable makeImmutable() {
      return this. new Immutable(theObject);
  }
  private static <A> A makeClone(A theObject) { ... }

  public final class Immutable {  // is no longer a static class
      private final T theObject;

      public Immutable( T arg) {
          theObject = makeClone(arg);
      }
      public T getWrappedItem() {
          return makeClone(theObject);
      }
  }
  public class Mutable 
        ... similar ...
  }
}

This workaround comes with a certain amount of overhead because all inner classes have a hidden reference to an instance of the outer type, which in this example they neither need nor take advantage of; the hidden reference is just baggage.  Often, inner classes are combined with interfaces in order to keep the inner class a private implementation detail of the enclosing class.  We can do the same here.

Example (the previous workaround refined):

class Wrapper <T> {
  private final T theObject;

  public Wrapper( T t) { theObject = t; }
  public T getWrapper() { return theObject; }   public Mutable <T> makeMutable() {
      return this.new Mutable(theObject);
  }
  public Immutable <T> makeImmutable() {
      return this.new Immutable(theObject);
  }
  private static <A> A makeClone(A theObject) { ... }

  public interface Immutable<S> {
      S getWrappedItem();
  }
  public interface Mutable<S> {
      S getWrappedItem();
      void setWrappedItem(S arg);
  }
  private final class ImmutableImplementation implements Immutable<T>

      private final T theObject;

      public ImmutableImplementation(T arg) {
          theObject = makeClone(arg);
      }
      public T getWrappedItem() {
          return makeClone(theObject);
      }
  }
  private class MutableImplementation implements Mutable<T>
        ... similar ...
  }
}

As you can see, the nested public interfaces need to be generic whereas the inner private classes can use enclosing class’s the type parameter.

 

 

 

 

 

Leave a Reply Cancel reply