Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java 101: The next generation: The essential Java language features tour, Part 1

Java programming with assertions and generics

  • Print
  • Feedback

Page 3 of 9

Generics in the Java class library

Although generics are widely used in the Java Collections Framework, they are not exclusive to it. Generics are also used in other parts of Java's standard class library, including java.lang.Class, java.lang.Comparable, java.lang.ThreadLocal, and java.lang.ref.WeakReference.

Consider the following code fragment, which demonstrates the lack of type safety that was common in Java code before generics were introduced:

List doubleList = new LinkedList();
doubleList.add(new Double(3.5));
Double d = (Double) doubleList.iterator().next();

Although the goal of the above program is to store only java.lang.Double objects in the list, nothing prevents other kinds of objects from being stored. For example, you could specify doubleList.add("Hello"); to add a java.lang.String object. However, when storing another kind of object, the final line's (Double) cast operator causes ClassCastException to be thrown when confronted with a non-Double object.

Because this lack of type safety isn't detected until runtime, a developer might not be aware of the problem, leaving it to the client to discover. Clearly it would be better to have the compiler detect the problem. Generics aid the compiler by letting the developer mark the list as containing a specific type of object, as follows:

List<Double> doubleList = new LinkedList<Double>();
doubleList.add(new Double(3.5));
Double d = doubleList.iterator().next();

List<Double> now reads "List of Double". List is a generic interface, expressed as List<E>, that takes a Double type argument, which is also specified when creating the actual object. The compiler can now enforce type correctness when adding an object to the list -- for instance, the list could store Doubles only. This enforcement removes the need for the (Double) cast.

Discovering generic types

A generic type is a class or interface that introduces a set of parameterized types via a formal type parameter list, which is a comma-separated list of type parameter names between a pair of angle brackets. Generic types adhere to the following syntax:

class identifier<formalTypeParameterList>
{
   // class body
}

interface identifier<formalTypeParameterList>
{
   // interface body
}

The Java Collections Framework offers many examples of generic types and their parameter lists. For example, java.util.Set<E> is a generic type with <E> as its formal type parameter list and E as this list's solitary type parameter. java.util.Map<K, V> is another example.

Naming type parameters

Java programming convention dictates that type parameter names be single uppercase letters, such as E for element, K for key, V for value, and T for type. If possible, avoid using a meaningless name like "P" -- java.util.List<E> means a list of elements, but what could you possibly mean by List<P>?

A parameterized type is a generic type instance where the generic type's type parameters are replaced with actual type arguments (type names). For example, Set<String> is a parameterized type where String is the actual type argument replacing type parameter E.

The Java language supports the following kinds of actual type arguments:

  • Concrete type: A class or other reference type name is passed to the type parameter. For example, in List<Animal>, Animal is passed to E.
  • Concrete parameterized type: A parameterized type name is passed to the type parameter. For example, in Set<List<Shape>>, List<Shape> is passed to E.
  • Array type: An array is passed to the type parameter. For example, in Map<String, String[]>, String is passed to K and String[] is passed to V.
  • Type parameter: A type parameter is passed to the type parameter. For example, in class Container<E> { Set<E> elements; }, E is passed to E.
  • Wildcard: The question mark (?) is passed to the type parameter. For example, in Class<?>, ? is passed to T.

Each generic type implies the existence of a raw type, which is a generic type without a formal type parameter list. For example, Class is the raw type for Class<T>. Unlike generic types, raw types can be used with any kind of object.

Declaring and using generic types

Declaring a generic type involves specifying a formal type parameter list and using these type parameters throughout its implementation. Using the generic type involves passing actual type arguments to its type parameters when instantiating the generic type. See Listing 5.

Listing 5. GenDemo.java (version 1)

class Container<E>
{
   private E[] elements;
   private int index;

   Container(int size)
   {
      elements = (E[]) new Object[size];
      index = 0;
   }

   void add(E element)
   {
      elements[index++] = element;
   }

   E get(int index)
   {
      return elements[index];
   }

   int size()
   {
      return index;
   }
}

public class GenDemo
{
   public static void main(String[] args)
   {
      Container<String> con = new Container<String>(5);
      con.add("North");
      con.add("South");
      con.add("East");
      con.add("West");
      for (int i = 0; i < con.size(); i++)
         System.out.println(con.get(i));
   }
}

Listing 5 demonstrates generic type declaration and usage in the context of a simple container type that stores objects of the appropriate argument type. To keep the code simple, I've omitted error checking.

The Container class declares itself to be a generic type by specifying the <E> formal type parameter list. Type parameter E is used to identify the type of stored elements, the element to be added to the internal array, and the return type when retrieving an element.

The Container(int size) constructor creates the array via elements = (E[]) new Object[size];. If you're wondering why I didn't specify elements = new E[size];, the reason is that it isn't possible: doing so could lead to a ClassCastException.

Compile Listing 5 (javac GenDemo.java). The (E[]) cast causes the compiler to output a warning about the cast being unchecked. It flags the possibility that downcasting from Object[] to E[] might violate type safety because Object[] can store any type of object.

Note, however, that there's no way to violate type safety in this example. It's simply not possible to store a non-E object in the internal array. I'll show you how to suppress this warning message in a future article.

  • Print
  • Feedback

Resources
  • Download the source code for this article.
  • Read Angelika Langer's Java Generics FAQs for a wealth of information and perspective about generics in the Java language.
  • For students of the Java language and its controversies, Langer's "Und erstanding the closures debate" (JavaWorld, June 2008) compares the three initial proposals for adding closures, or lambda expressions, to the Java language in Java 7.
  • See "Java Reflection: Generics" (Jakob Jenkov, Jenkov.com) for further discussion about reflection with generics and special cases where it is possible to access generics information at runtime.
  • More from Java 101: The next generation:
  • More about the Java Collections Framework on JavaWorld: