Home > Articles > Programming > Java

  • Print
  • + Share This
Like this article? We recommend

Generic Types

A generic type is a class or interface with a type-generalized implementation. The class or interface name is followed by a formal type parameter section, which is surrounded with angle brackets (<>). Although space characters can appear between the type name and the open angle bracket (<), it is conventional to avoid placing spaces between these syntax elements:

class classname<formal-type-parameter-section>
{
}

interface interfacename<formal-type-parameter-section>
{
}

The formal type parameter section declares one or more formal type parameters, in which each parameter describes a range of types. The E in public class ArrayList<E> and T extends Number in class Vertex<T extends Number> are examples of formal type parameters.

If multiple formal type parameters are present, a comma separates each parameter from its predecessor: <type-parm1, type-parm2, type-parm3, ...>. For example, public interface Map<K,V>’s formal type parameter section declares K and V formal type parameters.

Each formal type parameter identifies a type variable. Examples of type variables include E in public class ArrayList<E>, T in class Vertex<T extends Number>, and K and V in public interface Map<K,V>. Formal type parameters and type variables are often identical.

By convention, type variables are represented by single uppercase letters to distinguish them from the names of their enclosing classes or interfaces. It is common to use T and surrounding letters such as S to name type variables; the Collections API also uses E to denote an element type, K to denote a key type, and V to denote a value type.

A generic class can specify type variables as the types of its non-static fields, non-static method parameters, and local variables; and as non-static method return types. For example, ArrayList specifies type variable E as the type of its private transient E[] elementData field and as the return type of its public E get(int index) method. Listing 3 presents another example.

Listing 3 GenericClass.java

// GenericClass.java

public class GenericClass<T>
{
  T field;

  public T getField ()
  {
   return field;
  }

  public void setField (T field)
  {
   this.field = field;
  }

  public void someOtherMethod ()
  {
   T local = field;

   // Use local in some way.
  }
}

A generic type is instantiated by replacing its type variables with actual type arguments. This instantiation is also known as a parameterized type. For example, GenericClass<T> is a generic type, GenericClass<String> is a parameterized type, and String is an actual type argument. Think of this instantiation as turning Listing 3’s generic class into this class:

public class GenericClass
{
  String field;

  public String getField ()
  {
   return field;
  }

  public void setField (String field)
  {
   this.field = field;
  }

  public void someOtherMethod ()
  {
   String local = field;

   // Use local in some way.
  }
}

Type Variable Bounds

A type variable can be specified as being unbounded or upper-bounded in the formal type parameter section. For unbounded type variables (such as E in ArrayList<E>), you can pass any actual type argument ranging from Object down to the lowest subclass or interface to the type variable.

However, if you need to restrict the range of types that a type variable can take on (such as being passed only Number and its subclasses), you must specify an upper bound. You can specify single or multiple upper bounds. These upper bounds let you set an upper limit on what types can be chosen as actual type arguments.

A type variable can be assigned a single upper bound via extends. For example, in class Foo<T extends Number>, T is assigned Number as its single upper bound. This type variable can receive only Number and subtypes as actual type arguments—Foo<Float> is legal; Foo<String> is illegal.

More than one upper bound can be assigned to a type variable. The first (leftmost) bound is a class or an interface; all remaining bounds must be interfaces. An ampersand (&) is used to separate each bound from its predecessor. The following generic class accepts only types that subclass Number and implement Comparable (which is redundant since Number implements this interface):

class CMP<T extends Number & Comparable<T>>
{
  CMP (T first, T second)
  {
    System.out.println (first.compareTo (second));
  }
}

Type Variable Scope

As revealed in Listing 3, a type variable’s scope (visibility) is the entire class or interface in which its formal type parameter section is present; static members are exceptions. This scope also extends to the formal type parameter section, where a type variable can be declared in terms of itself (as shown below) or a previously declared type variable:

class CMP<T extends Comparable<T>>
{
  CMP(T first, T second)
  {
   System.out.println (first.compareTo (second));
  }
}

The example’s formal type parameter section employs T extends Comparable<T> to require actual type arguments passed to T to implement the Comparable interface. Because String and Integer implement this interface, new CMP<String> ("ABC", "DEF"); and new CMP<Integer> (new Integer (1), new Integer (1)); are legal.

A type variable’s scope can be overridden by declaring a same-named type variable in the formal type parameter section of a nested class. In other words, the nested class’s type variable hides the outer class’s type variable. Because this scenario can lead to confusion, it is best to choose different names for these type variables:

// Outer’s T and Inner’s T are two different type variables.
// Outer’s T can be any type; Inner’s T is restricted to 
// Number and subtypes of Number (such as Float or Integer).

class Outer<T>
{
  class Inner<T extends Number>
  {
  }
}

// It is less confusing to specify a different name for the 
// nested class’s type variable. Here, S has been chosen to 
// make the distinction.

class Outer<T>
{
  class Inner<S extends Number>
  {
  }
}

// The following Outer and Inner class instantiations prove 
// that there are two different type variables.

Outer<String> o = new Outer<String> ();
Outer<String>.Inner<Float> i = o.new Inner<Float> ();

// T is assigned type argument String; S is assigned Float.

Five Kinds of Actual Type Arguments

When instantiating a generic type, which results in a parameterized type, an actual type argument is supplied for each formal type parameter. These arguments, which are specified as a comma-separated list between a pair of angle brackets, can be concrete types, concrete parameterized types, array types, type variables, or wildcards (depending on context and the generic type):

  • concrete type: The actual type argument is the name of a class or an interface that is passed to the type variable. Example: List<Integer> list = new ArrayList<Integer> ();. Each List element is an Integer.
  • concrete parameterized type: The actual type argument is the name of a parameterized type that is passed to the type variable. Example: List<List<Integer>> list = new ArrayList<List<Integer>> ();. Each List element is a List of Integers.
  • array type: The actual type argument is an array. Example: List<int []> list = new ArrayList<int []> ();. Each List element is an array of ints.
  • type variable: The actual type argument is a type variable. Example: List<T> list = new ArrayList<T> ();. Each list element is the type specified by T when the enclosing type is instantiated.
  • wildcard: The actual type argument is a ?. I’ll discuss this argument when I explore the wildcard type.

In addition to this list of actual type arguments, it is also possible to specify no arguments. The resulting type, which is known as a raw type, exists to allow legacy Java code (that is, code written prior to generics) to be compiled with the J2SE 5.0 compiler (albeit with warning messages). Example: List l = new ArrayList ();.

Wildcard Type

Having studied Java’s object-oriented capabilities, you understand that a subtype is a kind of supertype—an ArrayList is a kind of List, String and Integer are kinds of Objects, and so on. However, are List<String> and List<Integer> kinds of List<Object>s? Examine the following code:

List<Integer> li = new ArrayList<Integer> ();
List<Object> lo = li;
lo.add (new Object ());
Integer i = li.get (0);

The first line is correct; the second line is not correct. If it were correct, the list of integers would also be a list of objects, the third line would succeed, and the fourth line would throw a ClassCastException—you cannot cast Object to Integer. Because type safety would be violated, List<Integer> is not a kind of List<Object>.

This example can be generalized: For a given subtype x of type y, and given G as a generic type declaration, G<x> is not a subtype of G<y>. This fact might be the hardest thing to learn about generics because we are used to thinking about subtypes as being kinds of supertypes. Also, it can trip you up when writing code. Consider a first attempt at writing a method that outputs any collection:

public static void outputCollection (Collection<Object> c)
{
  for (Object o: c)
    System.out.println (o);
}

This method can be used to output any collection whose actual type argument is Object –- Set<Object> set = new TreeSet<Object> (); outputCollection (set); and List<Object> list = new ArrayList<Object> (); outputCollection (list); are legal, but List<String> list = new ArrayList<String> (); outputCollection (list); is illegal.

The problem is due to the fact that Collection<Object> is only the supertype of all other collection types whose actual type arguments are Object. If you need to pass other actual type arguments, you need to replace Object in the previous outputCollection() method with a ?. This character identifies the wildcard type, which accepts any actual type argument:

public static void outputCollection (Collection<?> c)
{
  for (Object o: c)
    System.out.println (o);
}

By simply changing to the wildcard type, List<String> list = new ArrayList<String> (); outputCollection (list); is now legal. Because Collection<?> is the supertype of all kinds of collections, you can pass any actual type argument to the method, assign each collection element to Object (which is safe), and access the element. But you cannot add elements to the collection:

public static void copyCollection (Collection<?> c1,
                  Collection<?> c2)
{
  for (Object o: c1)
    c2.add (o);
}

The method above, which attempts to copy c1’s elements to a presumably empty c2, will not compile. Although c1’s elements can be assigned to Object, c2’s element type is unknown. If this type is anything other than Object (such as String), type safety is violated. You need a generic method to copy c1’s elements to c2.

  • + Share This
  • 🔖 Save To Your Account