Determining the type parameter of a generic base class

April 15, 2009

The following code is provided for educational/illustrative purposes only.

This is probably just a clever hack. This is not kosher. To all students of the Java language, please do not start using this in your day-to-day code. Let me be the first to admit that I haven’t used this extensively and I wouldn’t recommend using it for production.

Disclaimers aside, I won’t give any more background into Java generics. For those in need of an introduction, Angelika Langer’s Java Generics FAQ explains a lot of things with more clarity and depth than I ever could.

The basic problem is, given a generic class, we would like to determine the type parameter to that class at run-time..

I’ve found myself needing to do this more than once. The simplest example I can contrive is one when we want the Base class to perform run-time type-checking of some input parameter. For example:

public class Base<T> {

  public boolean accepts(Object obj) {
    // how to ensure obj is assignable to
    // our type parameter T?
  }
}

The easy (and safe) way

Probably the safest way to do this would be to require that subclasses of Base pass in their type parameter to Base via a constructor call. To illustrate:

public class Base<T> {

  private final Class<T> klazz;

  public Base(Class<T> klazz) {
    this.klazz = klazz;
  }

  public boolean accepts(Object obj) {
    return this.klazz.isInstance(obj);
  }

}

Which we would extend like such:

public class ExtendsBase extends Base<String> {

  public ExtendsBase() {
    super(String.class);
  }

}

At this point, calling ExtendsBase#accepts("Foo") should return true, and all is well.

Laziness is the mother of invention

However, this approach imposes upon subclasses that particular constraint. What if we didn’t want to be so imposing? What if we wanted to be clever, and somehow let Base figure things out on its own without subclasses lifting a finger?

A generic Base class with type-parameter auto-discovery

Well, it turns out that even with erasure, there is a way to discover the type parameter of a parameterized class! This is by exploiting the information we can get from ParameterizedType.

Let us rewrite the constructor of Base to show you how this is possible:

  @SuppressWarnings("unchecked")
  public Base() {
    Class<? extends Base> actualClassOfSubclass = this.getClass();
    ParameterizedType parameterizedType = (ParameterizedType) actualClassOfSubclass.getGenericSuperclass();
    Type firstTypeParameter = parameterizedType.getActualTypeArguments()[0];
    this.klazz = (Class) firstTypeParameter;
  }

The @SuppressWarnings annotation is there so the compiler won’t complain about our (obviously) unsafe casts.

In the interest of brevity, I’ve also omitted sanity checks such as making sure that actualClassOfSubclass.getGenericSuperclass() is actually aParameterizedType, and firstTypeParameter is actually a Class.

With the above in hand, we can now rewrite our subclass removing the super(String.class) constructor.

Here’s more or less complete code (minus import statements) with a JUnit 4 test to boot:

public class Base<T> {

  private final Class<T> klazz;

  @SuppressWarnings("unchecked")
  public Base() {
    Class<? extends Base> actualClassOfSubclass = this.getClass();
    ParameterizedType parameterizedType = (ParameterizedType) actualClassOfSubclass.getGenericSuperclass();
    Type firstTypeParameter = parameterizedType.getActualTypeArguments()[0];
    this.klazz = (Class) firstTypeParameter;
  }

  public boolean accepts(Object obj) {
    return this.klazz.isInstance(obj);
  }

}

class ExtendsBase extends Base<String> {

  // nothing else to do!

}

public class ExtendsBaseTest {

  @Test
  public void testTypeDiscovery() {
    ExtendsBase eb = new ExtendsBase();
    assertTrue(eb.accepts("Foo"));
    assertFalse(eb.accepts(123));
  }
}

Limitations

Please note that the above trick won’t work if we extend ExtendsBase further. I’ll leave it as an exercise for reader to figure out why, and how to make it work no matter how deep our class hierarchy gets.

Final disclaimer: I’ve only tested this on Java 1.5.0_16 running on an Intel Mac with OS X 10.5.6. YMMV. In fact, I’d love to hear if this works or doesn’t work for you.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: