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

5 thoughts on “Some notes on discovering your type parameter using the Reflection API

  • November 28, 2009 at 8:55 pm
    Permalink

    WordPress is not my forte… Let’s try this one more time:

    With a type hierarchy that has a “concrete” parameter at the top level (such as ExampleSerializable<String>), why not propagate that “concrete” parameter down the type reflection hierarchy? So that instead of reflecting to retrieve supertype Example<T>, reflection would return supertype Example<String>?

    It seems to me being able to discover the parameter at generic levels of the type hierarchy would be very handy — such as in cases where you need to find out the parameter type for generic class Example<T>, but don’t know anything about subclass(es).

    Consider an example:

    public class ExampleIntegerToString extends ExampleMap<Integer,String>
    public class ExampleMap<X,Y> extends Example<Y>
    public class Example<T>

    In this example, if Example<T> wanted to reflect on the “concrete” type of <T> (in this case, <String>), it would not be able to do so safely, even if it did try to reflect on the subclass ExampleMap<Integer,String>.

    Any idea why this is not feasible? I’m sure there was some reason inside the JVM to make it impractical…

    Or perhaps I have completely misunderstood the nature of the problem. 🙂

    Thanks again Ben! Cheers,

    Johann

    • December 1, 2009 at 12:19 am
      Permalink

      Johann,

      Sorry for not responding earlier — got a little distracted.

      The reason for not propagating downward, is that the fundamental nature of generics is that you really do not have fully parameterized types of the superclasses of (for example) ExampleMap<Integer,String>. Creating a fully parameterized subtype is no different at all from “regular” subclassing — it does not suddenly create a whole, parallel class hierarchy. That is, Java does not end up with Example<T>, Example<Integer>, Example<String> and all sorts of other, parallel types just to support subtypes. Java introduces fully parameterized types only when the type hierarchy actually stops being generic.

      Note that this is fundamentally different from genericity in C++ (for example) where the compiler really just performs macro expansion and actually does introduce parallel hierarchies. Java, by contrast, uses “real” inheritance to move from generic to parameterized types. So, in effect, the reason for “no propagation” is the same as the reason that subclasses in Java don’t have copies of their parent’s methods but really do delegate to their parents in case of inheritance: because Java uses real inheritance and not “simulated inheritance.”

  • November 28, 2009 at 8:50 pm
    Permalink

    Whoops, sorry about the HTML formatting Ben! Hopefully this comment doesn’t get so mangled…

    Your clear explanation is what I was clumsily trying to get across in my comment about “sub-typing”. 🙂

    With a type hierarchy that has a “concrete” parameter at the top level (such as ExampleSerializable), why not propagate that “concrete” parameter down the type reflection hierarchy? So that instead of reflecting to retrieve supertype Example, reflection would return supertype Example?

    It seems to me being able to discover the parameter at generic levels of the type hierarchy would be very handy — such as in cases where you need to find out the parameter type for generic class Example, but don’t know anything about subclass(es).

    Consider an example:

    public class ExampleIntegerToString extends ExampleMap
    public class ExampleMap extends Example
    public class Example

    In this example, if Example wanted to reflect on the “concrete” type of (in this case, ), it would not be able to do so safely, even if it did try to reflect on the subclass ExampleMap.

    Any idea why this is not feasible? I’m sure there was some reason inside the JVM to make it impractical…

    Or perhaps I have completely misunderstood the nature of the problem. 🙂

    Thanks again Ben! Cheers,

    Johann

  • November 21, 2009 at 7:19 pm
    Permalink

    Johann,

    First of all, thanks for the compliment.

    Your post was partially obliterated by WordPress’ HTML processor, so I hope I have correctly interpreted your comment to be about the following situation:


    StringExample extends ExampleSerializable<String>
    ExampleSerializable<T> extends Example<T>
    Example<T>

    No, they didn’t limit the depth of subtyping to 1. The problem is more fundamental than that: the JVM never creates a ParameterizedType for Example<String>. And the reason is that in your type hierarchy, the first fully parameterized, non-generic type is ExampleSerializable<String> — up to that point the entire hierarchy is generic and there is no concept of a non-generic form of the Example type.

    You can see this by running the following program:


    import java.io.Serializable;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;

    class Example<T> {
        public void reportType()
        {
            Class thisClass = getClass();
            ParameterizedType pType = (ParameterizedType)thisClass.getGenericSuperclass();
            ParameterizedType pSuperType = (ParameterizedType) ((Class)pType.getRawType()).getGenericSuperclass();
            System.out.println(pType);
            System.out.println(pSuperType);
        }
    }

    class ExampleSerializable<T> extends Example<T> implements Serializable {}

    class StringExample extends ExampleSerializable<String> {
        public static void main(String[] args) {
            new StringExample().reportType();
        }
    }

    which prints the two lines


    ExampleSerializable<java.lang.String>
    Example<T>

    As you can see, the parameterized supertype of ExampleSerializable<java.lang.String> is the generic, non-instantiated type Example<T>. Which brings us to the reason for the error you are getting: when you request the first type parameter of Example<T>, you don’t get a class, but a type variable (which is not a supertype of java.lang.Class).

    Hope that helps (or at least, doesn’t disappoint too badly).

    Best regards,

    Ben.

  • November 21, 2009 at 2:10 am
    Permalink

    Thanks very much for the article Ben!

    Interesting: if we derive from Example a more specific class ExampleSerializable, and then try to determine the parameterized type of Example, it seems to be impossible. (Or “non-reifiable” I guess.)

    To test, take the final example code, and after line 7 add:

    pType = (ParameterizedType) ( (Class) pType.getRawType () ).getGenericSuperclass ();

    That will get the superclass of ExampleSerializable (which is Example) as a parameterized type, so that we can reflect on the parameter .

    Then change StringExample to derive from ExampleSerializable (rather than directly from Example).

    public class StringExample extends ExampleSerializable {}

    Then create a new class “ExampleSerializable”:

    import java.io.Serializable;

    public class ExampleSerializable
    extends Example
    {
    }

    So now we have a hierarchy of 3 derived classes:

    StringExample extends ExampleSerializable
    ExampleSerializable extends Example
    Example

    The reportType() code chokes at runtime trying to cast the first “actual type argument” to a Class:

    Exception in thread “main” java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class
    at Example.reportType(Example.java:14)
    at StringExample.main(StringExample.java:4)

    So far in my explorations I have not found anyway to determine what the type of Example is. The only remedy is to remove the “interim” class ExampleSerializable.

    So I guess rather than create more reflective types at runtime, the powers-that-be decided to limit the depth of sub-typing to 1?

    Seems somewhat arbitrary, considering the Type objects are only really instantiated when the reflection user actually asks for them… They’re not “real” types inside the JVM.

    Or am I all muddled?

    I don’t suppose there are any workarounds for the “2 levels of sub-typing” case?

    Thanks again Ben & cheers,

    Johann Tienhaara

Comments are closed.