反射与泛型

原文: https://lsy.iteye.com/blog/220264

反射是Java语言中很重要的一个组成部分,所以就此话题讨论的资源可谓数之不尽,日常开发也会经常使用到关于反射的 Reflection API 。
Java5.0 Tiger 出现以后,更对反射API有了新的扩展,尽管讨论的话题很多,不过我还是觉得不够全面,尤其是对泛型这一块,所以就我所知,再花力气总结一番

首先反射的入口是从 Class 开始的,所以如何获取 Class 就变得十分关键了。这里总结了几种方式:

  1. 通过 ${name}.class 语法。这里${name}可以是对象,也可以是原始数据类型,不过别忘了 void.classVoid.class
  2. 通过 ${name}.TYPE 语法。这里${name}是八种原始数据的包装类和Void.TYPE
  3. 通过对象的 getClass() 方法。
  4. 通过 Class 对象的 forName() 方法
  5. 通过类 Class 的 getSuperclass() 获取父亲类Class
  6. 通过类 Class 的 getEnclosingClass() 获取外部类Class
  7. 通过类 Class 的 getClasses()getDeclaredClasses() 获取内部类Class

下面是一张表用来说明 getClasses()getDeclaredClasses() 两个方法,稍后还会用该表说明其他 Reflection API


Member Class API Return type Inherited members Private members
Class getDeclaredClasses() Array N Y
getClasses() Array Y N
Field getDeclaredField() Single N Y
getField() Single Y N
getDeclaredFields() Array N Y
getFields() Array Y N
Method getDeclaredMethod() Single N Y
getMethod() Single Y N
getDeclaredMethods() Array N Y
getMethods() Array Y N
Constructor getDeclaredConstructor() Single N/A Y
getConstructor() Single N/A N
getDeclaredConstructors() Array N/A Y
getConstructors() Array N/A N

如上表所示, getClasses() 拥有继承的特点,可以获取父亲级定义的内部类,而不能访问定义为private的内部类;
而 getDeclaredClasses() 刚好相反,可以访问定义为 private 的内部类,却无法获取父亲级定义的内部类

成功获取了Class以后,那么就可以开始访问 Field, Method 和 Constructor 了,他们都继承自 java.lang.reflect.Member 。
从上表已经很容易可以看出各个成员是否拥有继承特性,是否能够访问私有成员,返回类型的数量这些信息。
这里需要注意一点,由于 Constructor 是无法被继承的,所以 Constructor 成员任何方法都没有继承的特性。
另外 Field 和 Method 在赋值或者调用的之前需要留意是否在操作私有成员,如果是那么需要先修改可访问度,执行 setAccessible(true) 。
还有一点就是 Method 成员的 getDeclaredMethod() 和 getMethod() 方法都需要两个参数,一个是方法的名称,另外一个参数 Class 的数组,这里需要感谢 Java5.0 引入不定长参数的特点,使我们可以在某些情况下少传入一个参数,如:

假设Order类有下列方法
public Long getId() { return id; }

5.0以前获取该方法的代码如下
Method getId = Order.class.getMethod("getId", new Class[0]);

而5.0仅需要写
Method getId = Order.class.getMethod("getId");

现在说说5.0泛型出现之后, Java Reflection API 的新特点。
首先增加一个接口 java.lang.reflect.Type ,其下一共有4个接口继承了它, TypeVariable, ParameterizedType, GenericArrayType 和 WildcardType,下面逐个分析。

1.TypeVariable

我们知道泛型信息会在编译时被JVM编译时转换为定义的一个特定的类型,这减少了应用程序逐步向下检查类型的开支,避免了发生 ClassCastException 的危险。
而 TypeVariable 就是用来反映在JVM编译该泛型前的信息。举个例子,假设 BaseOrder 类定义有如下一个方法

    public class BaseOrder<M extends Object & Serializable, N extends Comparable<N>> implements IBaseOrder {  
        public M getManufactory(){  
            return manufactory;  
        }  
    }  

这时候我们可以通过如下代码获取该泛型 Type , 并且经过测试该 Type 就是 TypeVariable

    Field manufactoryField = BaseOrder.class.getDeclaredField("manufactory");     
    type = manufactoryField.getGenericType();  
    assertTrue("The type of field manufactory is an instance of TypeVariable", type instanceof TypeVariable);  
    TypeVariable tType = (TypeVariable)type;  
    assertEquals("The name of this TypeVariable is M", "M", tType.getName());  
    assertEquals("The TypeVariable bounds two type", 2, tType.getBounds().length);   
    assertEquals("One type of these bounds is Object", Object.class, tType.getBounds()[0]);   
    assertEquals("And annother si Serializable", Serializable.class, tType.getBounds()[1]);  

通过 getName() 方法可以获取该泛型定义的名称,而更为重要的是 getBounds() 方法,可以判断该泛型的边界。

2.ParameterizedType

这个接口就比较出名了,在过去讨论最多的问题就是 GenericDao<T> 中,如何获取 T.class 的问题了。这里再翻出来过一遍,加入上述的类B aseOrder 定义不变,新定义一个Order对象,代码如下:

    public class Order extends BaseOrder<Customer, Long> implements IOrder, Serializable {  
    }  

那么如何通过 Order 获取到 Customer 呢?

    Type genericSuperclass = Order.class.getGenericSuperclass();  
    assertTrue("Order's supper class is a type of ParameterizedType.", genericSuperclass instanceof ParameterizedType);  
    ParameterizedType pType = (ParameterizedType)genericSuperclass;  
    assertEquals("Order's supper class is BaseOrder.", BaseOrder.class, pType.getRawType());  
    Type[] arguments = pType.getActualTypeArguments();  
    assertEquals("getActualTypeArguments() method return 2 arguments.", 2, arguments.length);  
    for (Type type : arguments) {  
        Class clazz = (Class)type;  
        if(!(clazz.equals(Customer.class)) && !(clazz.equals(Long.class))){  
            assertTrue(false);  
        }  
    }

可以看出通过Order类的 getGenericSuperclass() 方法将返回一个泛型,并且它就是 ParameterizedType 。这个接口的 getRawType() 方法和 getActualTypeArguments() 都非常重要.
getRawType() 方法返回的是承载该泛型信息的对象,而 getActualTypeArguments() 将会返回一个实际泛型对象的数组。
这里先提及一下Class对象中 getGenericSuperclass() 和 getSuperclass() 两个方法的区别,后文还有详细说明。
getGenericSuperclass() 方法首先会判断是否有泛型信息,有那么返回泛型的 Type ,没有则返回 Class ,方法的返回类型都是 Type ,这是因为 Tiger 中 Class 也实现了 Type 接口。将父亲按照 Type 接口的形式返回,而 getSuperclass() 直接返回父亲的 Class 。

3.GenericArrayType

这个接口比较好理解。如果泛型参数是一个泛型的数组,那么泛型 Type 就是 GenericArrayType ,它的 getGenericComponentType() 将返回被JVM编译后实际的数组对象。这里假设上文中BaseOrder有一个方法如下:

    public String[] getPayments(String[] payments, List<Product> products){  
        return payments;  
    }  

可以看出该方法的参数中有泛型信息,测试一下:

    Method getPayments = BaseOrder.class.getMethod("getPayments", new Class[]{String[].class, List.class});  
    types = getPayments.getGenericParameterTypes();  
    assertTrue("The first parameter of this method is GenericArrayType.", types[0] instanceof GenericArrayType);  
    GenericArrayType gType = (GenericArrayType)types[0];  
    assertEquals("The GenericArrayType's component is String.", String.class, gType.getGenericComponentType());  

发现这个 getPayments() 方法中的一个参数 String[] payments 是一个 GenericArrayType ,通过 getGenericComponentType() 方法返回的是 String.class 。这是怎么回事呢?
这里我们回过头去看 Class 对象的 getGenericSuperclass() 方法和 getSuperclass() 方法,如果把它们说成是一对的话,那么这里的 getGenericParameterTypes() 和 getParameterTypes() 就是另外一对。
也就是说 getGenericParameterTypes() 首先判断该方法的参数中是否有泛型信息,有那么返回泛型Type的数组,没有那么直接按照Class的数组返回;
而getParameterTypes()就直接按照Class的数组返回。非常相似吧,其原因就是这些成对的方法都有一个共同点就是判断是否有泛型信息,可以查看 Tiger 的源代码:

    public Type[] getGenericParameterTypes() {  
        if (getGenericSignature() != null)  
            return getGenericInfo().getParameterTypes();  
        else 
            return getParameterTypes();  
    }  

而这类成对出现的方法还很多,如Method对象定义的 getGenericReturnType() 和 getReturnType() , getGenericExceptionTypes() 和 getExceptionTypes() ,
Field对象定义的 getGenericType() 和 getType() 。

4.WildcardType

这个接口就是获取通配符泛型的信息了。这里假设上述的BaseOrder定义有一个属性

    private Comparable<? extends Customer> comparator;  

现在就来获取泛型?的信息,测试代码如下:

    Field comparatorField = BaseOrder.class.getDeclaredField("comparator");       
    ParameterizedType pType = (ParameterizedType)comparatorField.getGenericType();  
    type = pType.getActualTypeArguments()[0];  
    assertTrue("The type of field comparator is an instance of ParameterizedType, and the actual argument is an instance of WildcardType.", type instanceof WildcardType);  
    WildcardType wType = (WildcardType)type;  
    assertEquals("The upper bound of this WildcardType is Customer.", Customer.class, wType.getUpperBounds()[0]);  

首先我们获取到 comparator 这个属性,通过它的 getGenericType() 方法我们拿到了一个Type,可以看出它是一个 ParameterizedType ,而 ParameterizedType 的 actual argument 就是我们需要的 WildcardType ,
这个接口有两个主要的方法, getLowerBounds() 获取该通配符泛型的下界信息,相反 getUpperBounds() 方法获取该通配符泛型的上界信息。

说完了这四个接口,我们再回过头来看看Method对象,它还定义有一个方法 getTypeParameters() ,这又是干什么的呢?我们知道泛型是不能出现在静态的成员,静态的方法,或者静态的初始化器的逻辑中的。如下列代码都是错误的:

    //error  
    private static T customer;   
    //error  
    public static T getCustomer(){  
        return customer;  
    }  
    //error  
    static {  
        customerHolder = new HashSet<T>();  
    }  

不过静态的方法的参数却是可以被泛化的,如:

    public static <B extends BusinessType, S extends Serializable> Customer getSpcialCustomer(List<B> types, S serial){  
        return new Customer();  
    }  

这个方法我们就可以通过getTypeParameters()方法来获取它的参数化信息,代码如下:

    Method getSpcialCustomer = SalePolicy.class.getMethod("getSpcialCustomer", new Class[]{List.class,Serializable.class});  
    types = getSpcialCustomer.getTypeParameters();  
    assertEquals("The method declared two TypeVariable.", 2, types.length);  
    assertEquals("One of the TypeVariable is B.", "B", ((TypeVariable)types[0]).getName());  
    assertEquals("And another is S.", "S", ((TypeVariable)types[1]).getName());  

最后,说一下 Annotation ,成员可以通过 isAnnotationPresent(annotationClass) 和 getAnnotation(annotationClass) 方法来判断是否被某个 Annotation 标注,
不过需要注意的时该annotation自身必须被标注为 @Retention(RetentionPolicy.RUNTIME)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,165评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,476评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,446评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,157评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,164评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,615评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,969评论 3 430
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,959评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,495评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,529评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,641评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,233评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,218评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,715评论 2 366

推荐阅读更多精彩内容