反射
反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
使用反射,能在设计或者在添加新类的时候,能够快速应用开发工具动态的查询新添加类的能力。
能够分析类能力的程序称为 反射 反射功能极其强大可以用来
- 在运行时分析类的能力
- 在运行时查看对象
- 实现通用的数组来操作代码
- 利用Method对象,此对象很类似C++中的函数指针
1,前提介绍: Class 类
在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象专属的类。
Class类就是用来专门保存这些类的信息,通过Object类中的getClass()方法得到每个类的Class类实例
Person p;
Class cl = p.getClass();
一个Class类可以用来保存一个类的属性,如:
p.setName("ZhangSan");
System.out.print(cl.getName()+" "+p.getName());
此时程序会输出
Person ZhangSan
Class的getName()方法可以得到这个类的类名。
获得类的Class实例的第2种方法:Class.forName(类名)
String className = "com.henu.Person";
Class cl = Class.forName(className);
第三种方法,直接 类名.class
Class class1 = Person.class;
Class class2 = Random.class;
通过获得的Class实例再创建类的实例
Class cl = p.getClass();
Perons p = cl.newInstance();
以上代码在使用时要放在try catch代码块里,来处理捕获的异常
2,利用反射分析类的能力,检查类的结构
java.lang.reflect包中有三个类
- Field 描述类的域 getType()方法可以返回描述域所属类型的Class对象
- Method 描述类的方法
- Constructor 描述类的构造器
这三个类都有一个getModifiers的方法,描述各自的访问修饰符,public private final...等,
再通过Modifier类中的isPublic,isPrivate,isXxx方法对其进行判断.
getFields
getMethods
getConstructors
这三个方法分别返回类提供的public域,方法,构造器数组,其中包括超类的共有成员.getDeclareFields
getDeclareMethods
getDeclaredConstructors
这三个方法将返回类中声明的全部域,方法和构造器,包括private,protect,但不包括超类的共有成员
在运行时使用反射分析对象
接下来进一步查看数据域的实际内容给
Person p = new Person("张三",20);
Class cl = p.getClass();
Field name = cl.getDeclaredField("name");//获得Person 类的name域;
Object value = name.get(p); // 此时value = "张三"
值的注意的一点是,若name域在Person类中是私有的,代码会抛出异常,我们需要对获得的数据域进行设置
Person p = new Person("张三",20);
Class cl = p.getClass();
Field name = cl.getDeclaredField("name");//获得Person 类的name域;
name.setAccessible(true);
Object value = name.get(p); // 此时value = "张三"
setAccessible方法是AccessibleObject类中的一个方法,它是Field,Method,Constructor类的共有超类,是为了调试,持久存储和相似调试提供的.
若数据域是基本数据类型,可以使用域.getDouble(类的实例),getInt(类的实例),也可以继续使用get( )方法,反射机制会自动的将这个域值装箱,变成Double,Integer。
.set(obj,value)方法可以将obj对象的指定域进行赋值
name.set(p,"李四")//此时p对象的name为李四
3,使用反射编写泛型数组代码
java.lang.reflect包中的Array类允许动态的创建数组
以前我们学过Arrays类中有个copyOf()方法,可以用来拓展已经满了的数组.
Person[ ] team = new Person[10];
team = Arrays.copyOf(team,2*team.length);
我们可以用反射写一个通用的拓展数组长度的方法,能拓展任意类型的数组。
首先我们写一个错误的例子
public static Object[] badCopyOf(Object[] a,int newLength) {
Object[] newArray = new Object[newLength];
System.arrayCopy(a,0,newArray,0,Math.min(a.length,newLength));//第四个参数是要从原数组复制元素的个数
return newArray;
}
为什么这个代码是错的呢,因为这个函数返回的数组类型是对象数组类型(Object [])
一个对象数组不能转化成特定的类的数组(Object[]->person[]),这样做会产生异常,其实,当我们把Person[]转化成 Object[] 再转化成Person[]是可以的,但是一个一开始就是Object[]的数组是不能转的,我们可以通过下面这个方法
Object newArray = Array.newInstance(componentType,int newLength);
java.lang.reflect.Array中的静态方法newInstance方法能够构造新的数组,第一个参数是元素的类型,第二个是数组长度。所以现在我们需要获得新数组的元素类型和数组长度。
- 获得数组的长度可以通过.getLength()或者Array.getLength(数组)获得
- 获得数组的元素类型可以通过Class类的getComponentType()获得
所以我们现在要实现一个通用的拓展任意类型的方法要经过3步
- 首先获得数组的Class对象
- 确认它是一个数组类型(通过Class类的isArray()方法)
- 使用Class类的getComponentType()方法确定数组的类型
public static Object goodCopyOf(Object a,int newLength) {
Class cl = p.getClass();
if (cl.isArray()) return null;//如果不是数组类型就结束
Class componentType = cl.getComponentType();//获得数组的元素类型
int length = a.getLength();
Object newArray = Array.newInstance(componentType,length);
System.arrayCopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;
}
这样这个方法就可以用了来拓展任意类型的数组了
int[] a = {1,2,3,4,5};
a = (int [])goodCopyOf(a,10);
通过Method的invoke方法调用类的任意方法
两个方法
Method method = Person.getClass().getMethod(方法名,方法的参数类型.class)
method.invoke(方法隶属的类的实例,方法的参数值)
//动态构造InvokeTest类的实例
Class> classType = InvokeTest.class;
Object invokeTest = classType.newInstance();
//动态构造InvokeTest类的add(int num1, int num2)方法,标记为addMethod的Method对象
Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class});
//动态构造的Method对象invoke委托动态构造的InvokeTest对象,执行对应形参的add方法
Object result = addMethod.invoke(invokeTest, new Object[]{1, 2});