7

I have a parameterized interface that is implemented in many different ways. At run time I need to figure out, given an arbitrary object that implements that interface, what the actual type parameters to the interface is.

Here's a snippet to illustrate the problem, and a halfway attempt to solve it (also on ideone.com):

import java.util.*;
import java.lang.reflect.*;

interface Awesome<X> { }
class Base<E> implements Awesome<Set<E>> { }
class Child extends Base<List<Integer>> { }

class AwesomeExample {      
    public static void main(String[] args) {
        Awesome<Set<List<Integer>>> x = new Child();

        System.out.println(
            ((ParameterizedType)
                Child.class.getGenericSuperclass()
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.List<java.lang.Integer>"

        System.out.println(
            ((ParameterizedType)
                Base.class.getGenericInterfaces()[0]
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.Set<E>"        

        investigate(x);
        // we want this to print "Set<List<Integer>>"
    }

    static void investigate(Awesome<?> somethingAwesome) {
        // how to do this?
    }
}

It looks like there's enough generic type information at runtime to deduce that:

  • Child extends Base<List<Integer>>
  • Base<E> implements Awesome<Set<E>>

And therefore we can put all the bits and pieces together to conclude that:

  • Child implements Awesome<Set<List<Integer>>>

So it looks like the problem is solvable, but it's not that simple, since we'd have to work with an arbitrary class/interface hierarchy. Is this the only way to do this? Is there a simpler way? Has someone written a library to do this already?

polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • I'm not aware of a library that implements this. I did it myself - and it was not very pleasant. I could at least find a super type token implementation: http://code.google.com/p/google-gson/source/browse/trunk/src/main/java/com/google/gson/reflect/TypeToken.java?r=60&spec=svn89 – Thomas Jung Dec 23 '10 at 09:17

3 Answers3

4

Edit: You may just want to look into using: http://code.google.com/p/gentyref/

If you can guarantee that all implementations of Awesome<?> will not have type arguments, the following code should get you started [1]:

static void investigate(Object o) {
    final Class<?> c = o.getClass();
    System.out.println("\n" + c.getName() + " implements: ");
    investigate(c, (Type[])null);
}

static void investigate(Type t, Type...typeArgs) {
    if(t == null) return;

    if(t instanceof Class<?>) {
        investigate((Class<?>)t, typeArgs);
    } else if(t instanceof ParameterizedType) {
        investigate((ParameterizedType)t, typeArgs);
    }
}

static void investigate(Class<?> c, Type...typeArgs) {
    investigate(c.getGenericSuperclass(), typeArgs);

    for(Type i : c.getGenericInterfaces()) {
        investigate(i, typeArgs);
    }
}

static void investigate(ParameterizedType p, Type...typeArgs) {
    final Class<?> c = (Class<?>)p.getRawType();
    final StringBuilder b = new StringBuilder(c.getName());
    b.append('<');
    Type[] localArgs = p.getActualTypeArguments();
    if(typeArgs != null && typeArgs.length > 0) {
        int i = 0, nextTypeArg = 0;
        for(Type local : localArgs) {
            if(local instanceof ParameterizedType) {
                ParameterizedType localP = (ParameterizedType) local;
                b.append(localP.getRawType()).append('<');
                b.append(typeArgs[nextTypeArg++]);
                b.append('>');
            } else if(local instanceof TypeVariable) {
                // reify local type arg to instantiated one.
                localArgs[nextTypeArg] = typeArgs[nextTypeArg];
                b.append(localArgs[nextTypeArg]);
                nextTypeArg++;
            } else {
                b.append(local.toString());
            }
            b.append(", ");
            i++;
        }
        if(typeArgs.length > 0) {
            b.delete(b.length() - 2, b.length());
        }
        b.append('>');
    } else {
        String args = Arrays.toString(localArgs);
        b.append(args.substring(1, args.length()-1)).append('>');
    }
    System.out.println(b);
    investigate(c, localArgs);
}

If, however, instantiations of Awesome<?> or Base<E> will be made, that type information will be lost due to erasure. This can be worked around, as a convention, with something like this:

Awesome<?> awesome = new Base<Double>() {};

Notice the {}, this creates a new anonymous class that implements (or here extends) Base<E>. This class will have its type parameters available for reflection.

If you're afraid enforcing this convention will be an issue, you can hide the constructors & only expose factory methods:

class Base<E> implements Awesome<Set<E>> {

    public static Base<Number> newNumberInstance() {
        return new Base<Number> () {};
    }

    protected Base() {}
}

As the above code has not been completely tested, you may want to do that. The point here is that you can find the actual type parameters given your requirements are stringent enough. Whether or not that applies to your situation is up to you to determine.

[1] It will print out all of the interfaces a class implements & not just the type parameters of Awesome. This can be changed, but I figured I'd go for more general & let you work out the specifics. For example, you'll want to test these to see what I mean:

investigate(new ArrayList<Integer>());
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class
investigate("");
investigate(new Awesome<Comparable<?>> () {}); // new anonymous implementation of Awesome
oconnor0
  • 3,154
  • 6
  • 34
  • 52
1

Following proposal of @oconnor0, here is how to do it with gentyref:

static Type investigate(Awesome<?> somethingAwesome) {
    return GenericTypeReflector.getTypeParameter(somethingAwesome.getClass(), Awesome.class.getTypeParameters()[0]);
}

In case somethingAwesome.getClass() could be also generic, it might be useful to pass it through GenericTypeReflector.addWildcardParameters first.

Spring has also a GenericTypeResolver.resolveTypeArgument(Class,Class) which can achieve the same result.

Didier L
  • 18,905
  • 10
  • 61
  • 103
-1

The short answer is NO. I agree that it is a pity... :( The reason is that Java drops type parameters at the compile stage. They do not exist in byte code. They are used by compiler only.

To solve your problem you have to add yet another "regular" parameter of type Class and pass it to the constructor when you are creating an instance of Base:

class Base<E> implements Awesome<Set<E>> { 
    private E type;
    public Base(E type) {
        this.type = type;
    }
}
ChrisH
  • 4,788
  • 26
  • 35
AlexR
  • 114,158
  • 16
  • 130
  • 208
  • 2
    The type information is in the byte code. So you're not correct. You can get the type information in this position. Google for super type tokens to get an example. – Thomas Jung Dec 23 '10 at 09:03
  • 1
    @Thomas - Static type information is stored, dynamic info is not. – OrangeDog Dec 23 '10 at 12:00