How to get the actual type arguments to an indirectly implemented generic interface?

后端 未结 3 1466
时光说笑
时光说笑 2021-02-09 08:40

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

相关标签:
3条回答
  • 2021-02-09 08:49

    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;
        }
    }
    
    0 讨论(0)
  • 2021-02-09 08:56

    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
    
    0 讨论(0)
  • 2021-02-09 09:01

    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.

    0 讨论(0)
提交回复
热议问题