《转》ParameterizedType getGenericSuperclass 获取泛型参数;class getClass区别

2,070 阅读6分钟

反射的基础知识

请前往上一篇随笔 java 反射基础

那么,Base.class 与 getClass(),这两个方法来获取类的字节码的时候,有什么不一样的地方呢?当然有不一样的地方了,Base.class 是写死了的,它得到的永远是 Base 类的字节码,

而 getClass() 方法则不同,在上面代码注释中的第一、二行注释我用了“实际运行的类”6个字,这几个字很重要,一定要理解,如果无法理解,下面的你可能就看不懂了。

为了方便大家的理解,下面插加一个小例子来加以说明 类.class 与 getClass() 两种方法来获取类的字节码有什么区别:

package test; /**


  • @描述 超类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Father {

public Father (){
   
    System.out.println("Father 类的构造子:");
    System.out.println("Father.class :" + Father.class);
    System.out.println("getClass()      :" + getClass());
}

}

package test;

/**


  • @描述 超类的子类(超类的实现类)
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Children extends Father{

}

package test; /**


  • @描述 测试类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    new Children(); //实际运行的类是Children(Father类的子类或者说是实现类)
}

}

后台打印输出的结果:

Father 类的构造子: Father.class :class test.Father getClass() :class test.Children

从打印出的结果看来,类.class 与 getClass() 的区别很明了了,getClass() 获取的是实际运行的类的字节码,它不一定是当前类的 Class,有可能是当前类的子类的 Class,具体是哪

个类的 Class,需要根据实际运行的类来确定,new 哪个类,getClass() 获取的就是哪个类的 Class,而 类.class 获取得到的 Class 永远只能是该类的 Class,这点是有很大的区别的。

这下“实际运行的类”能理解了吧,那么上面的那段被拆分的代码也就不难理解了,getClass() 理解了那 clazz.getGenericSuperclass() 也就没什么问题了吧,千万不要以为

clazz.getGenericSuperclass() 获取得到的是 Object 类那就成了,实际上假如当前运行的类是 Base 类的子类,那么 clazz.getGenericSuperclass() 获取得到的就是 Base 类。

再者就是最后一句,(Class) parameterizedType[0],怎么就知道第一个参数(parameterizedType[0])就是该泛型的实际类型呢?很简单,因为 Base 的泛型的类型

参数列表中只有一个参数,所以,第一个元素就是泛型 T 的实际参数类型。

其余的已经加了注释,看一下就明白了,这里不多解释,下面 Base 这个类是不是就直接能使用了呢?来看一下就知道了:

package test; /**


  • @描述 测试类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    Base<String> base = new Base<String>();
    System.out.println(base.getEntityClass());                        //打印输出 null
//    System.out.println(base.getEntityName());                //抛出 NullPointerException 异常
//    System.out.println(base.getEntitySimpleName()); //抛出 NullPointerException 异常
}

}

从打印的结果来看,Base 类并不能直接来使用,为什么会这样?原因很简单,由于 Base 类中的 clazz.getGenericSuperclass() 方法,如果随随便便的就确定 Base 类的泛型的类型

参数,则很可能无法通过 Base 类中的 if 判断,导致 entityClass 的值为 null,像这里的 Base,String 的 超类是 Object,而 Object 并不能通过 if 的判断语句。

Base 类不能够直接来使用,而是应该通过其子类来使用,Base 应该用来作为一个基类,我们要用的是它的具体的子类,下面来看下代码,它的子类也不是随便写的:

package test; /**


  • @描述 Base类的实现类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Child extends Base{

}

从上面代码来看,Base 的泛型类型参数就是 Base 的子类本身,这样一来,当使用 Base 类的子类 Child 类时,Base 类就能准确的获取到当前实际运行的类的 Class,来看下怎么使用

package test; /**


  • @描述 测试类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    Child child = new Child();
    System.out.println(child.getEntityClass());
    System.out.println(child.getEntityName());
    System.out.println(child.getEntitySimpleName());
}

}

后台打印输出的结果:

class test.Child test.Child Child

好了,文章接近尾声了,如果你能理解透这个例子,你可以将这个思想运用到 DAO 层面上来,以 Base 类作为所有 DAO 实现类的基类,在 Base 类里面实现数据库的 CURD 等基本

操作,然后再使所有具体的 DAO 类来实现这个基类,那么,实现这个基类的所有的具体的 DAO 都不必再实现数据库的 CURD 等基本操作了,这无疑是一个很棒的做法。

(通过反射获得泛型的实际类型参数)补充:

泛型反射的关键是获取 ParameterizedType 接口,再调用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可获得实际绑定的类型。

由于去参数化(擦拭法),也只有在 超类(调用 getGenericSuperclass 方法) 或者成员变量(调用 getGenericType 方法)或者方法(调用 getGenericParameterTypes 方法)

像这些有方法返回 ParameterizedType 类型的时候才能反射成功。

上面只谈到超类如何反射,下面将变量和方法的两种反射补上:

通过方法,反射获得泛型的实际类型参数:

package test;

import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection;

/**


  • @描述 测试类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-26


*/ public class Test {

public static void main(String[] args){
    /**
     * 泛型编译后会去参数化(擦拭法),因此无法直接用反射获取泛型的参数类型
     * 可以把泛型用做一个方法的参数类型,方法可以保留参数的相关信息,这样就可以用反射先获取方法的信息
     * 然后再进一步获取泛型参数的相关信息,这样就得到了泛型的实际参数类型
     */
    try {
        Class<?> clazz = Test.class; //取得 Class
        Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
        Type[] type = method.getGenericParameterTypes(); //取得泛型类型参数集
        ParameterizedType ptype = (ParameterizedType)type[0];//将其转成参数化类型,因为在方法中泛型是参数,且Number是第一个类型参数
        type = ptype.getActualTypeArguments(); //取得参数的实际类型
        System.out.println(type[0]); //取出第一个元素
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//声明一个空的方法,并将泛型用做为方法的参数类型
public void applyCollection(Collection<Number> collection){
   
}

}

后台打印输出的结果:

class java.lang.Number

通过字段变量,反射获得泛型的实际类型参数:

package test;www.2cto.com

import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.Map;

/**


  • @描述 测试类
  • @作者 fancy
  • @邮箱 fancydeepin@yeah.net
  • @日期 2012-8-26


*/ public class Test {

private Map<String, Number> collection;

public static void main(String[] args){
   
    try {
       
        Class<?> clazz = Test.class; //取得 Class
        Field field = clazz.getDeclaredField("collection"); //取得字段变量
        Type type = field.getGenericType(); //取得泛型的类型
        ParameterizedType ptype = (ParameterizedType)type; //转成参数化类型
        System.out.println(ptype.getActualTypeArguments()[0]); //取出第一个参数的实际类型
        System.out.println(ptype.getActualTypeArguments()[1]); //取出第二个参数的实际类型
       
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

后台打印输出的结果:

class java.lang.String class java.lang.Number