在运行时获取泛型的类型

来源:互联网 发布:65是什么意思网络用语 编辑:程序博客网 时间:2024/05/16 17:02

转自:http://www.dongliu.net/post/5896921377931264


都Java 1.5在加入泛型支持时,为了保持兼容,采用的是擦除法实现,泛型的定义只在编译的时候有效,编译之后是没有保留泛型的类型信息的,运行时所有的instance都当作限界类型来使用,没有指定限界类型是则是当作Object类型。所以,通常是无法在运行时获得泛型的类型活实例化泛型类的,如下面的代码都无法通过编译:

public class Test<T> {
    public Test(){
        T t = new T();    // error
        Class<T> clazz = T.class;   //error
        T[] ts = new T[10];   //error
    }
}
然而,擦除法的实现存在一些特列,在这些特例情况下,Java会记录泛型的类型信息,并且可以通过Api来获取的。比如在各种持久化框架中广泛使用的BaseDao泛型类的写法,就利用一个泛型继承的特例:

public class BaseDAO<U> {
    private Class<U> entityClass;

    protected BaseDAO() {
        // 获取泛型类型
        Type type = getClass().getGenericSuperclass();  
        Type[] trueType = ((ParameterizedType) type).getActualTypeArguments();
        this.entityClass = (Class<U>) trueType[0];
    }
       
    public U getById(Serializable id){
        PersistenceManager pm = getPersistenceManager();
        pm.setDetachAllOnCommit(true);
        U object;
        try{
            object = pm.getObjectById(entityClass, id);
        }catch (JDOObjectNotFoundException e) {
            object = null;
        }finally{
            pm.close();
        }
        return object;
    }
}

public class TagDAO extends BaseDAO<Tag>{
}
代码的关键是getGenericSuperclass方法,这个方法返回的类型为ParameterizedType,里面可以通过getActualTypeArguments来获取泛型的真正类型。这样的特例是违背了擦除法的实现思想的,这也是Java为保证类型系统完整而不得不做的妥协。

Jackson json中反序列化时,可以通过TypeReference传递容器中的泛型类型,也是利用了泛型继承的特例:
Map<Integer, ApkBean> map = mapper.readValue(jsonData, new TypeReference<Map<Integer, ApkBean>>(){});
TypeReference是一个泛型抽象类,在readValue的第二个方法中,传入了TypeReference的一个匿名子类实例,由此带入了Map的泛型信息。由于是两级泛型的嵌套,具体的情况其实更复杂一些,有兴趣的可以看看Jackson代码中TypeReference和TypeFactory的实现。

除了泛型继承这种情况之外,还有另外两个特例,也可以获取ParameterizedType类型,继而获取到泛型的类型。一个是类的field可以通过getGenericType来获取:
public class Test {

    private Map<String, Number> map;

    public static void main(String[] args) throws NoSuchFieldException {
        Class<?> clazz = Test.class;
        Field field = clazz.getDeclaredField("map");
        //取得泛型类型
        Type type = field.getGenericType();
        ParameterizedType ptype = (ParameterizedType)type;
        System.out.println(ptype.getActualTypeArguments()[0]);
        System.out.println(ptype.getActualTypeArguments()[1]);
    }

}
另外就是方法的泛型参数可以通过getGenericParameterTypes来获取:
public class Test {

    public static void main(String[] args) throws NoSuchMethodException {
        Class<?> clazz = Test.class;
        Method method = clazz.getDeclaredMethod("getGenericSample", Collection.class);
        //取得泛型类型参数集
        Type[] type = method.getGenericParameterTypes();
        ParameterizedType ptype = (ParameterizedType)type[0];
        type = ptype.getActualTypeArguments();
        System.out.println(type[0]);
    }

    public void getGenericSample(Collection<Number> collection){

    }
}

这两种特例的实用价值比较小,实际的代码hack中也比较少见。


0 0
原创粉丝点击