My favorites | Sign in
Project Home Wiki Issues Source
READ-ONLY: This project has been archived. For more information see this post.
Search
for
GenericClass  
An explanation of GenericClass
Updated Dec 13, 2010 by darren.pearce

Introduction

Although Java provides for generic classes, it is only a compile-time facility; run-time objects do not 'remember' their generic information. For example, an ArrayList<Integer> used in the code is indistinguishable from an ArrayList at run-time. This is due to a process known as 'erasure' whereby all generic information about an object is erased by the time the code is executed.

This is a problem when using Class<?> instances as a way of storing the 'type' of something within the software. For example, if in the source code, there is Expression<IntegerValue> then using Expression.class does not capture that this is for an Expression that works with values of type IntegerValue.class. This is because getting the .class attribute is a run-time facility and, by the time a program runs, all generic type information for any given instance is erased.

As a concrete example of why this is a problem, imagine a simplified version of the XML conversion layer. Let's define an (abstract) XMLConverter class as follows:

public abstract class XMLConverter<O> {
  
  private Class<O> objectClass;

  public XMLConverter(Class<O> objectClass) {
    this.objectClass = objectClass;
  }

  public abstract Element convertToXML(O object);

  public abstract O convertToObject(Element element);

}

This looks nice in that it uses generics to 'enforce' that the convertToXML and convertToObject methods work with the correct type, O. Having the objectClass stored means that we can provide a lookup table that maps Class instances to their corresponding XMLConverter.

Further developing the example, if we had a class Person, we could extend XMLConverter as follows:

public class PersonXMLConverter extends XMLConverter<Person> {

  public PersonXMLConverter() {
    super(Person.class);
  }

  public Element convertToXML(Person object) {
     ...
  }

  public Person convertToObject(Element element) {
    ...
  }

}

This all works fine assuming our conversion methods behave correctly (in terms of their assumptions about the XML format). But, certainly, we can guarantee that we put a Person in and we get a Person back since we are using generics and generics are a useful way of doing extra checking. Note that this checking happens at compile-time which means that if it fails, the class doesn't compile. This is far better than happening at run-time at which time a failed check makes the software fall over or behave in more insidious unexpected ways.

Now let's imagine that we want to write a converter for a list of integers, List<Integer>. We could start off writing:

public class IntegerListXMLConverter extends XMLConverter<List<Integer>> {

  public IntegerListXMLConverter() {

but what comes next? Doing:

super(List.class)

will lead to a compile-time error since the type of List.class is Class<List> not Class<List<Integer>> which is what is required by the base-class (since O=List<Integer>).

So, one possibility is to forget about the generic parameter of Integer and do:

public class IntegerListXMLConverter extends XMLConverter<List> {

  public ListXMLConverter() {
    super(List.class);
  }

  public Element convertToXML(List object) {
     ...
  }

  public List convertToObject(Element element) {
    ...
  }

}

This will compile fine except for warnings. Warnings will be generated because the compiler knows that List is a generic type so it is advising you to specify a generic parameter for better compile-time checking. One fix that could be tried is to do:

public class IntegerListXMLConverter extends XMLConverter<List> {

  public ListXMLConverter() {
    super(List.class);
  }

  public Element convertToXML(List<Integer> object) {
     ...
  }

  public List<Integer> convertToObject(Element element) {
    ...
  }

}

which uses List<Integer> in the convert methods rather than List but this will lead to compile errors since the generic parameter of the base-class (O) is List not List<Integer>.

So, what we are forced to do is to use:

public class IntegerListXMLConverter extends XMLConverter<List> {

  public IntegerListXMLConverter() {
    super(List.class);
  }

  @SuppressWarnings
  public Element convertToXML(List object) {
     ...
  }

  @SuppressWarnings
  public List convertToObject(Element element) {
    ...
  }

}

so that we feel we are doing the right thing. Moreover, the methods will have to assume that the passed-in List is in fact a List of Integers. There is no guarantee that this will be the case and, if it does happen, the code will fall over at run-time (since the conversion routines will cast to Integer manually at some point).

So, what to do?

Using GenericClass

AFAIK, there is no easy way around this in Java. Generics are great but they have their limitations and erasure does lead to problems as demonstrated above.

The class GenericClass provides a way around this issue.

The most important point to understand about GenericClass is that in the same way that doing .class on a class name such as List.class provides a unique identifier for the List class, GenericClass provides a unique instance for any given generic specification such as List<Double> or List<Integer>.

It is important to remember once again that all generic information associated with an instance is erased by the time the code is executed. However, this does not mean that it is impossible to access any generic information at run-time. Using reflection, it is possible, for example, to obtain the generic types of a method's parameters or return type.

You can see a generic type as a 'class tree' in that the class HashMap<Integer, HashMap<String, Double>> can be seen as:

  • HashMap.class
    • Integer.class
    • HashMap.class
      • String.class
      • Double.class

In order to gain access to this information and create a GenericClass instance for it, a subclass is required. For the above example, this is:

public ComplexHashMapType extends GenericClass<HashMap<Integer, HashMap<String, Double>>> {

}

This is a necessary evil so that, at run-time, there is somewhere that GenericClass can use to 'dig out' (using reflection) the class tree from the class' declaration. Returning to the XMLConverter example, we can now specify the 'type' of the converter using a GenericClass instance:

public abstract class XMLConverter<O> {
  
  private GenericClass<O> objectClass;

  public XMLConverter(GenericClass<O> objectClass) {
    this.objectClass = objectClass;
  }

  public abstract Element convertToXML(O object);

  public abstract O convertToObject(Element element);

}

When subclassing this to encode List<Integer> (not just List), we need access to an instance of the GenericClass which corresponds to the type of List<Integer>. This requires specification of a subclass which we can call whatever we like but let's call it IntegerListType:

public IntegerListType extends GenericClass<List<Integer>> {

}

The static method GenericClass.getGeneric() takes a Class<?> argument. This argument must be a subclass of GenericClass just like our IntegerListType above. The method instantiates the class (and saves the instance for later use). So, the IntegerListConverter now looks like:

public class IntegerListXMLConverter extends XMLConverter<List<Integer>> {

  public IntegerListXMLConverter() {
    super(GenericClass.getGeneric(IntegerListType.class));
  }

  public Element convertToXML(List<Integer> object) {
     ...
  }

  public List<Integer> convertToObject(Element element) {
    ...
  }

}

which applies our generic compile-time constraints as we want.

In the case of the simpler PersonXMLConverter, there is no need to create a GenericClass subclass since (the assumption here is that) the Person class is not generic. In view of this, we use the method GenericClass.get() which just builds a class tree that consists of the passed-in class as the root node:

public class PersonXMLConverter extends XMLConverter<Person> {

  public PersonXMLConverter() {
    super(GenericClass.get(Person.class));
  }

  public Element convertToXML(Person object) {
     ...
  }

  public Person convertToObject(Element element) {
    ...
  }

}

Postscripts

Some points to note:

  • It may not seem necessary to derive the class tree internally in GenericClass but it is imperative that there is only one GenericClass instance for any given class tree otherwise the uniqueness of using GenericClass as a handle is lost and things can get very confusing.
  • It is possible, however, to create a non-generic handle for a generic class. For example, we could call GenericClass.get(List.class). This can be useful when we really don't want the generic information to be remembered.
  • All subclasses of GenericClass are required to consist at most of a default constructor and nothing more.

Powered by Google Project Hosting