如何获取间接实现的泛型接口的实际类型参数?

7

我有一个带参数的接口,它有很多不同的实现方式。在运行时,我需要找出给定实现该接口的任意对象的接口实际类型参数是什么。

这里有一段代码片段来说明问题,并试图解决它(也可以在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?
    }
}

看起来在运行时有足够的通用类型信息可以推断出:

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

因此我们可以将所有碎片组合在一起得出结论:

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

所以看起来问题是可以解决的,但这并不简单,因为我们必须处理任意类/接口层次结构。这是唯一的方法吗?是否有更简单的方法?有人已经写了一个库来做这件事吗?


我不知道是否有一个库实现了这个功能。我自己做了 - 这并不是很愉快的经历。至少我能找到一个超级类型标记的实现: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
3个回答

4

编辑:您可能只想研究使用以下内容:http://code.google.com/p/gentyref/

如果您可以保证所有的Awesome<?>实现都不会有类型参数,那么以下代码可以让您开始[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);
}

然而,如果要创建 Awesome<?>Base<E> 的实例,则由于类型擦除,该类型信息将丢失。可以采用类似以下约定的方法解决此问题:

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

注意 {},它创建了一个实现(或者在这里是继承)Base<E> 的匿名类。这个类将会为反射提供其类型参数。
如果你担心强制这种约定可能会有问题,你可以隐藏构造函数,只暴露工厂方法。
class Base<E> implements Awesome<Set<E>> {

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

    protected Base() {}
}

作为上述代码尚未完全测试,您可能希望这样做。重点在于,您可以找到实际的类型参数,只要您的要求足够严格。是否适用于您的情况取决于您自己来确定。
[1] 它将打印出类实现的所有接口,而不仅仅是Awesome的类型参数。这可以更改,但我想我会选择更通用的方式,让您解决具体问题。例如,您需要测试这些内容以了解我的意思:
investigate(new ArrayList<Integer>());
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class
investigate("");
investigate(new Awesome<Comparable<?>> () {}); // new anonymous implementation of Awesome

1

在 @oconnor0 的建议下,以下是使用 gentyref 的方法:

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

如果somethingAwesome.getClass()也可能是泛型的,最好先通过GenericTypeReflector.addWildcardParameters进行处理。

Spring还有一个GenericTypeResolver.resolveTypeArgument(Class,Class)可以实现相同的结果。


-1
短暂的回答是不行的。我同意这很遗憾... :( 原因是Java在编译阶段会丢弃类型参数。它们在字节码中不存在,只由编译器使用。
为了解决您的问题,您需要添加另一个“常规”类型的参数,并在创建Base实例时将其传递给构造函数:
class Base<E> implements Awesome<Set<E>> { 
    private E type;
    public Base(E type) {
        this.type = type;
    }
}

2
类型信息在字节码中。所以你是不正确的。你可以在这个位置获取类型信息。谷歌搜索超类型标记以获取示例。 - Thomas Jung
1
@Thomas - 静态类型信息被存储,动态信息则不会。 - OrangeDog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接