VOL 220 .... No. 48

TUESDAY, SEPTEMBER 15, 1992

Reflecting Generic Types in Java

Categories: Programming

erasure-heavenly-action-music-picture-idea-girl-consulting-word-press

My first experience with generics in Java was when I was working on my Darwin G.P. library.  I was excited to find out about and take advantage of generics, but there was one major problem I had with the way they were implemented–they were completely disregarded at run-time!  I never fully understood what the motivation behind that decision was, and I’ve been informed that C# does things differently (have not verified), but I recently discovered an interesting workaround using the Factory pattern.

For those of you who do not know, generics were introduced in Java 1.5 and offer an extra layer of compile-time type-safety to classes.  They are utilized heavily by the Java standard-library collections (like ArrayList, HashMap, etc).  You can define a class like:

public class ValueHolder< Type > {
 
	private Type value;
 
	public ValueHolder(Type value) {
		this.value = value;
	}
 
	public Type getValue() {
		return value;
	}
 
	public void setValue(Type value) {
		this.value = value;
	}
 
}

This all works very nicely, and gives you some fantastic compile-time checking, but the run-time reflection is incredibly weak.

If you are unfamiliar with reflection, reflection gives you some features found in dynamic programming languages. On any class, you can call .class to get a Class object, which you can then use to perform any logic you want on. It’s very nice, but if you try to call .class on a generic class, you’ll be told “Illegal class literal for the type parameter Type.”

It would be nice to have some way of getting the class object for a parameterized type.

Java tells us on this page that:

4.6 Type Erasure

Type erasure is a mapping from types (possibly including parameterized types and type variables) to types (that are never parameterized types or type variables). We write |T| for the erasure of type T. The erasure mapping is defined as follows.

The erasure of a parameterized type (§4.5) G is |G|.
The erasure of a nested type T.C is |T|.C.
The erasure of an array type T[] is |T|[].
The erasure of a type variable (§4.4) is the erasure of its leftmost bound.
The erasure of every other type is the type itself.
The erasure of a method signature s is a signature consisting of the same name as s, and the erasures of all the formal parameter types given in s.

So Java is well-aware that parameterized types are not reified at run-time. I suppose they have their reasons for doing it this way, but it would still sometimes be nice to have this information at run-time.

The solution I came to in past projects was to explicitly pass the class object to the constructor as follows:

public class ValueHolder< Type > {
 
	private Type value;
	private Class< Type > cls;
 
	public ValueHolder(Type value, Class< Type > cls) {
		this.value = value;
		this.cls = cls;
	}
 
	...
 
}

This does work. And in fact, it actually prevents programmers from accidentally breaking something by passing the wrong Class object, since Class itself is parameterized, but still, it makes programmers have to repeat themselves, as follows:

ValueHolder< String > v = new ValueHolder< String >("hello world", String.class);

I recently discovered a new technique, that accomplishes the reification of generic types without forcing programmers to repeat themselves by means of the Factory Pattern. Simply adding a static method of the following signature to your class accomplishes this:

public static <Type> ValueHolder<Type> create(Type value, Class<Type> cls) {
	return new ValueHolder<Type>(value, cls);
}

Now, programmers can instantiate a ValueHolder as follows:

ValueHolder< String > v = ValueHolder.create("hello world", String.class);

It’s really only a minor reduction of the strain put on programmers, but actually does make a pretty big difference when using it.


related post

Tags: ,
  1. No comments yet.
  1. March 28th, 2010 at 19:10 | #1
Comments are closed.