Is it possible to obtain the generic type of an interface?

Viewed 96

Given the interface:

interface GenericInterface<T> {
  Class<T> getGenericType();
}

and the following implementation "skeletons":

class GenericInterfaceImpl<T> implements GenericInterface<T> { /* impl */ }

class TypedInterfaceImpl implements GenericInterface<String> { /* impl */ }

define an implementation for getGenericType in a manner that it would pass the following assertions:

assertEquals(new GenericInterfaceImpl<String>().getGenericType(), String.class, "Type should resolve to String.class");

assertEquals(new TypedInterfaceImpl().getGenericType(), String.class, "Type should resolve to String.class");

I'm perfectly aware of a good implementation that solves this problem on the TypedInterfaceImpl "scenario". The following code passes the assertion for the second case:

@Override
@SuppressWarnings("unchecked")
Class<T> getGenericType() {
    Type t = this.getClass().getGenericInterfaces()[0];
    ParameterizedType pt = (ParameterizedType) t;

    return (Class<T>) pt.getActualTypeArguments()[0];
}

But I can't find a way to get the same result if I'm working with the GenericInterfaceImpl version of this problem. When using the above implementation with GenericInterfaceImpl, the "type argument" resolves to a sun.reflect.generics.reflectiveObjects.TypeVariableImpl instead of java.lang.String like it does when handling TypedInterfaceImpl.

The Javadocs for getGenericInterfaces explicitly states that it will not resolve type parameters if it's not defined in the source code (for reference: https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getGenericInterfaces-- I'm using Java 11 in my particular case but it's the same nonetheless), so by definition, I understand that this particular method can't solve this problem.

Is there a way to solve this problem at all or is this a "core limitation" of the Java language?

1 Answers

assertEquals(new GenericInterfaceImpl<String>().getGenericType(), String.class, "Type should resolve to String.class");

impossible.

assertEquals(new TypedInterfaceImpl().getGenericType(), String.class, "Type should resolve to String.class");

Possible, but a bad idea; it would require you to document your interface with 'when implementing this interface, you must pick a concrete, non-type-parameterized type for T or nothing works right.', which is a weird caveat.

Class<T> getGenericType() {

Bad idea; the thing in the <> does not have to be a Class - for example, it could be List<String>, which is not representable as a class object (only List is, there is one object that represents all lists, there's no way to have an instance of j.l.Class that represents List<String>).

The Javadocs for getGenericInterfaces

getGenericInterfaces is a reflective tool, but it reflects on the definition of the type only. Given class Foo<T> implements Thingie<T>, getGenericInterfaces can only be executed on the entire Foo class, and you get back an object that presents Thingie<T>. Given an instance such as new Foo<String>() it is not possible to derive String.

Is there a way to solve this problem at all or is this a "core limitation" of the Java language?

It's a core limitation, so, no. Not directly.

One trick is to add a method:

public interface Foo<T> {
   public Class<T> getType();
}

and let implementers worry about the problem. This is still a bad idea, for example, a Class object cannot represent List<String>, so you've effectively made it impossible for Foo to properly represent anything where you want the T to be anything that has type params.

Take a step back instead. What are you actually trying to do? Nobody wakes up and goes: I know! I'll build an interface that can report its own type parameter but reified! What did you want to build? A chat app? A plugin system for photoshop?

java.lang.Class, with the intent to invoke .newInstance() on it, is a piss poor factory. Thus, if that is what you intend to do, use factories instead.

Anotehr solution, but you probably don't want this, and before you got all excited, it's got plenty of limitations, are a concept called Super Type Tokens; you can search the web if you must know.

Related