Tuesday, April 6, 2010

Working around type erasure in Java

Due to how generics are implemented in Java, there is no way to determine at runtime, e.g. the type of objects contained in a collection. This can be a bit of an issue if you want to use that type information to look up a specific converter or repository for that type.

What I didn't know was that while the type information is lost on the actual instance, there is still the possibility to get it from the surrounding class declaration. This does require that the instance is declared in a Field and that that Field contains the generic type declaration. Similarly, for classes extending/implementing generic classes or interfaces you can also access type parameters via reflection.

Here's a messy example that hopefully should illustrate both points:

public class LongToStringList extends AbstractList<String> implements List<String> {

private List<Long> someList = new ArrayList();

@Override
public String get(int index) {
return Long.toString(someList.get(index));
}

@Override
public int size() {
return someList.size();
}

@Test
public void test() throws Exception {
// alternatively this.getClass().getGenericInterfaces()[0]
ParameterizedType superclass = (ParameterizedType) this.getClass().getGenericSuperclass();
assertArrayEquals(new Type[]{String.class}, superclass.getActualTypeArguments());

Field field = this.getClass().getDeclaredField("someList");
ParameterizedType fieldType = (ParameterizedType) field.getGenericType();
assertArrayEquals(new Type[]{Long.class}, fieldType.getActualTypeArguments());
}

}
I'm always a bit scared of reflection and so I only learned about it browsing through my colleague Markus' code and then again while stumbling through some of the code in Spring-Binding (which lead here). The latter can use this to determine which converter to use to map form values from an array into a collection on a bound model. And that is pretty nifty if not without its problems.