JAVA反射学习之——深入研究(反射与泛型)
通过上节的学习,了解了反射的基本功能,下面关于反射有如下几个需要注意的地方。在学习JAVA与泛型之前,先说两个小知识点。
一、学会使用反射编写具有通用性功能的代码
设计一个方法,将Object对像中的propertyName属性的值设置为value,方法声明如下:
public void setProperty(Object obj, String propertyName, Object value);
用反射,完成具有通用性的功能:
- class Persion{
- private String name;
- private int age;
-
- @Override
- public String toString()
- {
- return name + " --- " + age;
- }
- }
-
- public class ReflectTest2
- {
- public static void setProperty(Object obj,String propertyName,Object value)
- throws Exception
- {
-
-
- Class clazz = obj.getClass();
-
-
- Field field = clazz.getDeclaredField(propertyName);
-
- field.setAccessible(true);
-
- field.set(obj, value);
- }
-
- public static void main(String[] args) throws Exception
- {
- Persion p = new Persion();
- System.out.println(p);
-
- setProperty(p,"name","刘德华");
- setProperty(p,"age",50);
-
- System.out.println(p);
- }
- }
从上面代码来看,利用反射 不但可以完成具有通用性的功能,而且,还是访问的private型字段,可见反射的好用之处。
通过反射来获得配置文件的内容,如果配置文件中包含有类名,那我们就可以通过类名来获得类的class文件对像,从而调用该对像中的方法,如果应用层编写的是具有通用性的功能,那底层改变只需改一下配置文件就可以了。
二、反射带来发灵活性,但同时也损失了性能
下面通过一个测试来比较一下,对于同一个类,(1)利用成员方法进行普通赋值;(2)利用反射动态调用类方法
赋值;(3)取消访问权限检查后,再利用反射动态调用成员方法赋值。
- class Persion1{
- private String name;
- private int age;
-
- public void setName(String name){
- this.name=name;
- }
- }
-
-
- public class ReflectTest3
- {
- public static void main(String[] args) throws Exception
- {
- Persion1 p = new Persion1();
-
-
- long start = System.currentTimeMillis();
- for(int i = 0; i < 100000000L; i ++){
- p.setName("张三");
- }
- long end = System.currentTimeMillis();
- System.out.println("普通方法赋值1亿次,执行时间: " + (end-start) + "ms");
-
-
- Class clazz = p.getClass();
- Method m = clazz.getDeclaredMethod("setName", String.class);
- long start1 = System.currentTimeMillis();
- for(int i = 0; i < 100000000L; i ++){
- m.invoke(p, "张三");
- }
- long end1 = System.currentTimeMillis();
- System.out.println("反射动态赋值1亿次,执行时间: " + (end1-start1) + "ms");
-
-
- m.setAccessible(true);
- long start2 = System.currentTimeMillis();
- for(int i = 0; i < 100000000L; i ++){
- m.invoke(p, "张三");
- }
- long end2 = System.currentTimeMillis();
- System.out.println("反射取消访问检查,动态赋值1亿次,执行时间: " + (end2-start2) + "ms");
- }
-
- }
下面是执行结果:
普通方法赋值1亿次,执行时间: 645ms
反射动态赋值1亿次,执行时间: 48561ms
反射取消访问检查,动态赋值1亿次,执行时间: 3043ms
从上面执行结果来看,这几种方法之间的性能差异是具大的,所以在程序中对性能有要求的地方,比如循环处,关键代码段,能不使用反射就不要使用,即使用了,也可以关掉访问权限检查以提高性能。
三、反射与泛型
1. 使用反射越过泛型检查
首先,这个知识点的背景是这样的,如果你创建了一个集合,限制传入Integer类型,但是后面使用时,你想传入String类型,怎么办呢?先看几行代码的例子:
- public static void main(String[] args) throws Exception
- {
-
- ArrayList<Integer> list = new ArrayList<Integer>();
-
- list.add(100);
-
-
-
-
- Class clazz = list.getClass();
-
- Method add = clazz.getDeclaredMethod("add", Object.class);
-
- add.invoke(list, "hello");
- add.invoke(list, "world");
- add.invoke(list, "java se");
-
- System.out.println(list);
- }
对于泛型,在编写程序时都是给编译器看的,可以查看class文件的反编译结果,发现是没有泛型的,所以从理论上来说,对于上面的list对像,可以存储任意类型,在反射内部处理中,都是处理的Object类型,所以就有了以上代码。
2. 使用反射来获取泛型信息
java采用泛型擦除的机制来引入泛型,也就是说,泛型仅仅是给编译器javac看的,来确保数据的安全性和免去数据类型转换,但是,一旦编译完成,所有和泛型相关的东西都被擦除,这一点也可以从类编译的class文件反编译看到。
在实际应用中,为了获得和泛型有关的信息,Java就新增了几种类型来代表不能被归一到Class类中的类型,但又和基本数据类型齐名的类型,通常使用的是如下两个:
GenericType: 表示一种元素类型是参数化的类型或者类型变量的数组类型。
ParameterizedType: 表示一种参数化的类型。
为什么要引入这两种呢,实际上,在通过反射获得成员变量时,Field类有一个方法是getType,可以获得该字段的属性,但是这种属性如果是泛型就获取不到了,所以才引入了上面两种类型。
看下面一个例子:
- class student
- {
- private Map<String,Integer> score ;
-
- }
-
- public class ReflectTest5
- {
- public static void main(String[] args) throws Exception
- {
- Class<student> clazz = student.class;
-
- Field f = clazz.getDeclaredField("score");
-
-
- System.out.println("score的类型是:" + f.getType());
-
-
- Type gType = f.getGenericType();
-
-
- if(gType instanceof ParameterizedType)
- {
- ParameterizedType pType = (ParameterizedType)gType;
-
- Type rType = pType.getRawType();
- System.out.println("原始类型是: " + rType);
-
-
- Type[] gArgs = pType.getActualTypeArguments();
-
- for(int i=0; i < gArgs.length; i ++)
- {
- System.out.println("第"+ i +"个泛型类型是:" + gArgs[i]);
- }
- }
- else{
- System.out.println("获取泛型信息失败");
- }
- }
- }
Type是java.lang.reflect包下的一个接口,该接口代表所有类型的公共接口,Class是Type接口的实现类。