ZeroBlue76
ZeroBlue76

Reputation: 57

java generics runtime type

A post I found by Ian Robertson has a great utility method for determining the runtime class for a Java class type parameter. It goes a few steps beyond the quick version:

ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
Class<?> c = (Class<?>) pt.getActualTypeArguments()[0]

Either method works like a charm for parameterized children inheriting from an abstract parameterized parent (class Foo<T> extends Bar<T>), or an anonymous instance of a parameterized abstract class (new Foo<Bar>() {}), and instantiated with a concrete type.


Where I fail is in trying to do the same for some other object instantiated via type parameter. Relevant class objects are:

public class Foo {/* code omitted for brevity */}

public class MyFoo extends Foo {/* code omitted for brevity */}

public abstract class AbstractLoader<T extends Foo> {/* code omitted for brevity */}

public abstract class AbstractReader<T extends Foo> {/* code omitted for brevity */}

public class MyReader<T extends Foo> extends AbstractReader<T> {/* code omitted for brevity */}

public class Loader<T extends Foo> extends AbstractLoader<T> {
    /* code omitted for brevity */

    public MyReader<T> getReader() {
        // Parameterized type "T" doesn't seem to carry through here
        return new MyReader<T>();
    }
}

Sample Code:

static void main(String... s) {
    Loader<MyFoo> loader = new Loader<>(); // new Loader<MyFoo>() for Java < 1.7
    MyReader<MyFoo> = loader.getReader();

    ParameterizedType pt = (ParameterizedType)loader.getClass().getGenericSuperclass();
    System.out.println("LoaderType = " + pt.getActualTypeArguments()[0]);
    // Prints: LoaderType = MyFoo

    pt = (ParameterizedType)reader.getClass().getGenericSuperclass();
    System.out.println("ReaderType = " + pt.getActualTypeArguments()[0]);
    // Prints: ReaderType = T
}

Intuition tells me this "should" be possible somehow, but I can't seem to discover the right answer. OTOH, this may be another example of "can't do that" due to type erasure.

Upvotes: 4

Views: 434

Answers (2)

newacct
newacct

Reputation: 122429

You are completely misunderstanding something. There is NO DIFFERENCE between new Loader();, new Loader<Integer>();, and new Loader<Object>();, etc. at runtime. It is impossible to tell them apart. Get that notion out of your head right now.

If we create a class, then the types (including generics) in the declarations about that class, including superclass, method types, field types, etc. are stored in the class file. This information can be retrieved at runtime.

So when you have new Foo<Bar>() {}, that creates an instance of a certain class (an anonymous class) which extends a generic type with a specific type parameter. It's similar to:

class SomeAnonymousClass extends Foo<Bar> {
}
new SomeAnonymousClass()

The fact that Foo<Bar> is hard-coded at compile-time as the superclass of this class, this is retrievable at runtime.

But your code is doing nothing of this sort. You did not make any subclasses of Loader.

Upvotes: 1

Alexei Kaigorodov
Alexei Kaigorodov

Reputation: 13525

You cannot find type parameter for an object - it is erased at run time.

If you could, then Loader loader0, Loader<MyFoo> loader1, and Loader<MyBar> loader2 would give different results. This difference has to be represented in runtime somehow: either in objects directly, or by referencing different classes. The first variant require additional memory for each instance and was considered unappropriate. The second requires creating classes at runtime, as class Loader itself cannot contain all possible variants of parameterized class Loader - they are unknown at compile time.

Upvotes: 0

Related Questions