美文网首页
反射与泛型

反射与泛型

作者: agui526 | 来源:发表于2019-01-09 14:33 被阅读0次

原文: 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)。

相关文章

  • 泛型与反射

    引入泛型的目的 通过泛型使得在编译阶段完成一些类型转换的工作,避免在运行时强制类型转换而出现ClassCastEx...

  • 反射与泛型

    原文: https://lsy.iteye.com/blog/220264 反射是Java语言中很重要的一个组成部...

  • 泛型 & 注解 & Log4J日志组件

    掌握的知识 : 基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例) 泛型 概述 : 泛型...

  • Java泛型

    本文介绍的知识点 泛型是什么? 泛型的使用在反射中使用泛型在集合类中使用泛型 关于泛型擦除如何理解?如何避免泛型擦...

  • 关于反射的使用

    反射中获取泛型参数信息

  • java注解与反射,泛型与反射

    一、反射与注解 内置注解 java内置了3种注解,用来为编译器提供检查。 自定义注解 元注解 元注解是用来修饰注解...

  • 【Java】【反射】泛型反射

    泛型反射 在运行时,泛型是无效的,所以可以通过反射在运行时将其他类型变量添加到集合,而不需要考虑泛型

  • Java 泛型反射与擦除

    问:下面两个代码片段有问题吗,为什么? 答:上面 Part 1 编译出错,Part 2 编译 OK,运行出错。因为...

  • Java笔记---泛型

    总结了泛型的基本语法、上下级通配符、泛型反射以及使用泛型的一些实践,看完还不会用泛型你顺着网线来打我(狗头)。 我...

  • Java 泛型

    泛型类 例如 泛型接口 例如 泛型通配符 泛型方法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型上下边...

网友评论

      本文标题:反射与泛型

      本文链接:https://www.haomeiwen.com/subject/udtgrqtx.html