Starting with release 5.0, the Java language comes equipped with parametric polymorphism. Also known as generics, this language feature allows for a type (a class, interface, enum or other) to be parameterized with a type variable: a variable that represents some other type. This allows for the creation of Java code that is generic with respect to the type system (like when using java.lang.Object as the type of a method parameter) yet type safe at runtime (once you’ve chosen a specific type to use, the compiler can enforce that choice).

Even though parameterized polymorphism allows you to write code that is generic with respect to the type system, sometimes you want to write code that knows which actual type(s) it has been parameterized with. This article will examine how to discover that information, as well as the discoverability limits of the Java generic type system.

Java 5.0: Generics

When Java 1.0 was introduced in 1995, it didn’t include any concept of parametric polymorphism; it was a difficult concept and considered too complicated for inclusion in the original language. So for years we as Java developers were forced to write code that relied on the supertype of the entire language (java.lang.Object) whenever we didn’t want to be specific about which type(s) our code would work with at runtime. Of course there was a major downside to this: when you formally declare that your code needs a java.lang.Object, you immediately lose the ability of the compiler to check for correct usage if you are actually going to use a more specific type. I.e. if you want to use a java.util.List to include Strings, the compiler cannot check that everybody only puts Strings into that list and not objects of a different type.

So with the advent of Java 5.0, finally parametric polymorphism was added to the language: Java was outfitted with so-called type parameters, the type equivalent of method parameters. Like a method declares formal parameters which a program can reason over in a generic way and which are only given an actual value at runtime, a class, method or interface can now take a type parameter that makes the types the code works with just as indefinite as the values of method parameters. That is, the exact type a piece of generic code will act over may not be known at programming time, but it becomes known and verifiable at compile time and therefore a safe situation at runtime. For example, consider this code:

public class Example<T>
{
     public void report(T parameter)
     {
         /* Really interesting, generic code here */
     }
}

...

Example<String> stringExample = new Example<String>();
stringExample.report("Hello World!");

Within the declaration of class Example, you as a programmer don’t know the exact type that Example will use (although you can set limits). But once a specific Example instance has been created, the compiler can check that that instance is always used with the correct type.

But now suppose that you want to know within the generic code what runtime type you’re dealing with? For example, if we modify the example above a bit like so:

public class Example<T>
{
     public void reportType()
     {
         System.out.println("This example is using type " + [what goes here?] + " for parameter T");
     }
}

Is it possible to implement the example above?

Java 5.0 and the new, generic type system

In order to answer that question, we have to delve a bit into Java 5.0’s new, generic type system — and into the representation of the type system in the Reflection API.

In previous versions of Java, the type system was rather simple: you had classes and interfaces and everything was represented in the Reflection API by instances of the java.lang.Class class. Which was arranged very conveniently, since you got all the reflective functionality for free with your regular programming work; whenever you declared a class, the Java Virtual Machine would magically arrange at runtime for a java.lang.Class instance to appear to represent the new class you had just written. And everything (even primitive types) was represented by a java.lang.Class instance, whether it was really a class, or an interface or anything else. Simple.

With the introduction of generics, the simplicity is unfortunately gone. Parametric polymorphism forces a language to have a far more extensive type system, capable of making distinctions between all sorts of different kinds of types. Before we could get away with calling everything a class; now, we have classes (like before), but we also have parameterized classes and type variables (and annotations and varargs, but let’s not care about those for now). This is because we have to be able to distinguish between a regular old class (or interface), a class that has type parameters and the type parameters themselves (which, after all, are also a kind of type in the language).

To deal with all this, Java changed its type system and the way it is represented in the Reflection API. For starters, every type in Java is now a form of type — which is represented by the new interface java.lang.reflect.Type. Since classes and interfaces are types, java.lang.Class is now a subtype of java.lang.reflect.Type. But there are more subtypes of this new interface, including java.lang.reflect.TypeVariable (which represents a type variable in a generic declaration) and java.lang.reflect.WildcardType (which represents generic wildcards used when you cannot be definite about a type). And in addition to the new java.lang.reflect.Type type, there is also java.lang.reflect.GenericDeclaration interface, which represents the declaration of a generic type (in our example above, the declaration of the Example class is a generic declaration). Since generic declarations usually declare classes and interfaces, the main implementation of the GenericDeclaration interface is also java.lang.Class.

Confused already? Good, because now it gets worse. How many types do you think you introduce to the Java language every type you declare a generic class? The correct answer is infinitely many. Of course there’s the obvious one, the generically declared class you just typed in by hand. But also every potential use with a specific type is a new type in Java. In the case of our Example class, for instance, we’ve introduced Example<T> but also (potentially) Example<String>, Example<Integer>, Example<Number> and so on. Every time you come up with an Example in which the generic parameter T has been substituted for a real type, that’s a new type in the Java language. And the JVM represents these by automagically introducing an instance of a special subtype of java.lang.reflect.Type called java.lang.reflect.ParameterizedType (which is not the parameterized type declaration that it sounds like, but rather an instance of the generic declaration with all the type varables filled in with a real value).

Type erasures and reifiable types

If the discussion above already made your mind boggle, you’re going to love what comes next. You see, when the powers that be were working on Java generics, they decided that the most important thing would be to remain backwards compatible. That is, the Example class introduced earlier should still work with Java 1.4 code once compiled. That means that all the generics stuff in Java is purely administrative data to be used by the compiler, but at runtime it all gets thrown out (you can still discover it using the Reflection API in an administrative sense, but it doesn’t affect the interaction with other classes). So, in effect, at runtime the Example class is still a class that declares a method using java.lang.Object as a formal parameter. It’s just that the compiler has done extra verification so we do not need runtime type checking.

This choice by the language designers has some consequences. For starters, it means that at compile time all generic code goes through a process called type erasure, which really means that all generic data is removed. So we forget that it is Example<T> (this becomes just Example, like in Java 1.4) and we forget that stringExample is an Example<String> (at runtime it will just be an Example).

This same choice also means that we have a distinction between what is called reifiable types and non-reifiable types. Essentially this means that in the case of some types we can completely discover at runtime what their full generic declaration was (this is called reifiable) and in other cases this is not possible.

Discovering type parameter values at runtime

So the question of discovering the type parameter values at runtime is going to center around whether we are using a reifiable type or not. And let me get right down to it: very often this is going to mean that the answer is “no, you cannot discover at runtime”. In the case of our Example class for instance, the compiler throws away all the generic information during compilation so stringExample will never know that it is working with Strings.

So when can you discover type parameter values at runtime? Well, if you have explicitly introduced a ParameterizedType (i.e. a fully defined, fully resolved form of a generic class with all the type parameters filled in). ParameterizedType information is definite, you see, and not generic. So it doesn’t get erased by the compiler. An example to clarify:

  • The generic class Example<T> gets erased into Example by the compiler
  • The parameterized type Example<String> stays, because it is definite at compile time

So how do you explicitly introduce a ParameterizedType? Through subtyping. If you create a definite subclass of a generic type, that means the type hierarchy at runtime must include the ParameterizedType (otherwise there is a hole in the type hierarchy). And a ParameterizedType (being a fully resolved, reifiable type) allows you to discover every detail of its being. Let’s look at this in the case of our Example. First, we introduce a subclass of Example for Strings:

public class Example<T>
{
     public void reportType()
     {
         System.out.println("This example is using type " + [what goes here?] + " for parameter T");
     }
}

public class StringExample extends Example&lt;String&gt; {}

This code will introduce three types to the language at runtime: Example (the erased version of Example<T>), StringExample and Example<String> (the String-specific, non-generic, fully reifiable supertype of StringExample). Why is Example<String> not lost as a type? Because otherwise StringExample wouldn’t have a supertype anymore. After all, Example cannot be the supertype of StringExample — it doesn’t know about Strings, only about Objects. And StringExample cannot inherit directly from Object because it has to inherit the report method from somewhere. So there has to be an intermediate type that introduced a String-specific version of the report method (and any other inherited methods). So there is this mysterious Example<String> supertype. By the way, don’t expect this type to show up as a java.lang.Class instance: it’s not a class that you can instantiate or an interface that you can cast to. But it’s there nevertheless.

So now how do we do discovery and implement the report method? As with all uses of the Reflection API, we start out by getting the java.lang.Class instance that represents StringExample. Starting in Java 5.0, Class has a new method called getGenericSuperclass(), which returns the java.lang.reflect.Type that represents the (possibly generic) supertype of the class. In the case of StringExample that is a java.lang.reflect.ParameterizedType representing Example<String>. ParameterizedTypes have an array of actual type arguments, Types representing the actual types the ParameterizedType was instantiated with. And if any of those types is a Class, that is the definite, reifiable type we are looking for. So the report method will look like this:

public class Example<T>
{
     public void reportType()
     {
         Class thisClass = getClass();
         // Since we KNOW this must be a ParameterizedType, we can cast
         ParameterizedType pType = (ParameterizedType)thisClass.getGenericSuperclass();
         Type firstType = pType.getActualTypeArguments()[0];
         Class whatWeWant = (Class)firstType;
         System.out.println("This example is using type " + whatWeWant.getName() + " for parameter T");
     }
}

public class StringExample extends Example<String> {}

Of course, normally you would have to check whether firstType is really a java.lang.Class.

Conclusion

In this article we have discussed the changes to the Java 5.0 type system with respect to genericity and with an eye towards runtime discovery of type parameter values. This has lead us to review the new concept of Type in the type system and the many subtypes thereof. It has also lead us to examine the differences and causes of differences between reifiable and non-reifiable types. Finally, we have distinguished when type variable values are discoverable and when they are not, as well as how their values can be discovered.

Some notes on discovering your type parameter using the Reflection API