反射
概述
我们知道,Java不是一种动态语言,它在运行中产生的一些新的东西是没办法控制的,如果某些类型或者接口是在我们编写程序时不存在的,我们对这种类型的内容一无所知,甚至是名字,所以我们并没有办法通过new一个对象来编写程序,那么我们怎么利用它里面的属性或者方法呢,这时候,就产生了反射机制。
反射机制是针对内存中运行的字节码文件进行操作的机制,当内存中产生了字节码,我们就可以根据这份字节码获取其对应的类、属性以及方法,了解了这些之后,我们就可以根据具体的内容编写程序了。
但反射机制只是对已有的字节码进行操作,而不能自己创造一份字节码出来,也就是说Class并没有支持的构造函数来干这个事,这让我疑惑不已。
Class类
Class
类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class
对象。基本的 Java 类型(boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为Class
对象。
简单的说,Class类代表的就是运行中的一份类字节码
那么如何获取一个运行中的Class字节码对象呢?
- 通过类名获得:类名.class
- 通过对象获得:对象.getClass()
- 通过字符串代表的类名获得:Class.forName(“完整的类名“)
1: //通过类名直接获得
2: System.out.println(Date.class);
3: //通过对象获得
4: System.out.println(new Date().getClass());
5: //通过一个字符串类名获得
6: //此处存在ClassNotFoundException异常
7: try {
8: //这里注意:需要完整的类名
9: System.out.println(Class.forName("java.util.Date"));
10: } catch (ClassNotFoundException e) {
11: // TODO Auto-generated catch block
12: e.printStackTrace();
13: }
打印结果:
class java.util.Date
class java.util.Date
class java.util.Date
由于是对未知的字节码文件进行操作,所以我们一般只能获得代表类名的字符串,所以一般采用第三种方式获取类的字节码对象
那么既然我们得到了类的字节码文件,类中的各个成员属性我们也就可以得到了
Method类代表类中的方法属性
Field类代表类中的各个字段
Constructor类代表类中的构造函数
下面我们分别介绍
这里提供一个ReflectPoint类用作测试
1: public class ReflectPoint {
2: public int x = 1;
3: private int y = 2;
4:
5: public ReflectPoint(){
6:
7: }
8:
9: public ReflectPoint(int x, int y) {
10: super();
11: this.x = x;
12: this.y = y;
13: }
14:
15: public void method(){
16: System.out.println(x+":"+y);
17: }
18:
19: @Override
20: public String toString() {
21: return "ReflectPoint ["+x+","+y+"]";
22: }
23:
24: }
Constructor类
public final class Constructor<T>- extends AccessibleObject
- implements GenericDeclaration, Member
Constructor
提供关于类的单个构造方法的信息以及对它的访问权限。
通过反射实例化对象
1: public class ConstructorTest {
2:
3: /**
4: * @param args
5: * @throws NoSuchMethodException
6: * @throws InvocationTargetException
7: * @throws IllegalAccessException
8: * @throws InstantiationException
9: * @throws SecurityException
10: * @throws IllegalArgumentException
11: */
12: public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
13:
14: //获取类的字节码对象
15: String className = "reflect.ReflectPoint";
16: Class cls = Class.forName("reflect.ReflectPoint");
17:
18: //获取类中的所有的构造函数对象
19: Constructor[] cons = cls.getConstructors();
20: System.out.println("---------构造函数列表----------");
21: for(Constructor con : cons){
22: Class[] paramTypes = con.getParameterTypes();
23: String consName = con.getName();
24: System.out.println(consName + Arrays.toString(paramTypes));
25: }
26: System.out.println("-------------------------------");
27: //获取无参的构造函数,并实例化对象
28: Constructor conNoParam = cls.getConstructor();
29: Object objNoParam = conNoParam.newInstance();
30: System.out.println("无参实例对象:"+objNoParam);
31:
32: //获取有参的构造函数,并实例化对象
33: Constructor conParam = cls.getConstructor(int.class,int.class);
34: Object objParam = conParam.newInstance(2,2);
35: System.out.println("有参实例对象:"+objParam);
36: System.out.println("-------------------------------");
37:
38: //通过Class类直接实例对象,此方法只针对无参构造函数
39: System.out.println("Class获取无参实例对象:"+cls.newInstance());
40: }
41: }
打印结果:
---------构造函数列表----------
reflect.ReflectPoint[]
reflect.ReflectPoint[int, int]
-------------------------------
无参实例对象:ReflectPoint [1,2]
有参实例对象:ReflectPoint [2,2]
-------------------------------
Class获取无参实例对象:ReflectPoint [1,2]
同样,我们也可以将获取对象的操作抽取成方法,以便以后使用
1: public class ConstructorTest2 {
2:
3: /**
4: * @param args
5: * @throws ClassNotFoundException
6: */
7: public static void main(String[] args) throws ClassNotFoundException {
8: //获取类的字节码对象
9: String className = "reflect.ReflectPoint";
10: Class cls = Class.forName(className);
11:
12: //获取无参对象
13: Object obj1 = getInstance(cls, null, null);
14: System.out.println(obj1);
15:
16: //获取有参对象
17: Class[] parameterTypes = new Class[]{int.class,int.class};
18: Object[] initargs = new Object[]{2,2};
19: Object obj2 = getInstance(cls, parameterTypes, initargs);
20: System.out.println(obj2);
21:
22: }
23:
24: /**
25: * 获取指定类指定构造函数的实例化对象
26: *
27: * @param target 目标类的字节码对象
28: * @param parameterTypes 需要的构造函数参数类型
29: * @param initargs 要传入的参数列表
30: * @return 目标类的对象
31: */
32: private static Object getInstance(Class target,Class[] parameterTypes,Object[] initargs) {
33: Object obj = null;
34: try {
35: Constructor con = target.getConstructor(parameterTypes);
36: obj = con.newInstance(initargs);
37: } catch (SecurityException e) {
38: // TODO Auto-generated catch block
39: e.printStackTrace();
40: } catch (IllegalArgumentException e) {
41: // TODO Auto-generated catch block
42: e.printStackTrace();
43: } catch (NoSuchMethodException e) {
44: // TODO Auto-generated catch block
45: e.printStackTrace();
46: } catch (InstantiationException e) {
47: // TODO Auto-generated catch block
48: e.printStackTrace();
49: } catch (IllegalAccessException e) {
50: // TODO Auto-generated catch block
51: e.printStackTrace();
52: } catch (InvocationTargetException e) {
53: // TODO Auto-generated catch block
54: e.printStackTrace();
55: }
56: return obj;
57: }
58: }
Field类
public final class Field- extends AccessibleObject
- implements Member
Field
提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
以下代码通过反射实现获取和设置对象的属性值
1: public class FieldTest {
2:
3: /**
4: * @param args
5: * @throws ClassNotFoundException
6: * @throws IllegalAccessException
7: * @throws InstantiationException
8: */
9: public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
10:
11: //获取类的字节码对象并创建对象
12: String className = "reflect.ReflectPoint";
13: Class cls = Class.forName(className);
14: Object obj = cls.newInstance();
15:
16: //获取x属性并修改x属性
17: String fieldName = "x";
18: System.out.println(getFieldValue(obj, fieldName));
19: setFiledValue(obj, fieldName, 2);
20: System.out.println(getFieldValue(obj, fieldName));
21:
22: //获取y属性并修改y属性,由于y是私有的,所以外部不可以设置
23: /*fieldName = "y";
24: System.out.println(getFieldValue(obj, fieldName));
25: setFiledValue(obj, fieldName, 3);
26: System.out.println(getFieldValue(obj, fieldName));*/
27:
28:
29:
30: }
31: /**
32: * 设置对象字段值
33: * @param obj 需要设置的对象
34: * @param fieldName 需要设置的字段名
35: * @param value 需要设置的属性值
36: */
37: private static void setFiledValue(Object obj,String fieldName,Object value){
38: Class cls = obj.getClass();
39: try {
40: //根据字段名获取字段
41: Field field = cls.getField(fieldName);
42: //设置字段属性
43: field.set(obj, value);
44: } catch (SecurityException e) {
45: // TODO Auto-generated catch block
46: e.printStackTrace();
47: } catch (IllegalArgumentException e) {
48: // TODO Auto-generated catch block
49: e.printStackTrace();
50: } catch (NoSuchFieldException e) {
51: // TODO Auto-generated catch block
52: e.printStackTrace();
53: } catch (IllegalAccessException e) {
54: // TODO Auto-generated catch block
55: e.printStackTrace();
56: }
57: }
58:
59: /**
60: * 获取对象字段值
61: * @param obj 需要获取值的对象
62: * @param fieldName 需要获取的属性名
63: * @return
64: */
65: private static Object getFieldValue(Object obj,String fieldName){
66: Class cls = obj.getClass();
67: Object retVal = null;
68: try {
69: //根据字段名获取字段
70: Field field = cls.getField(fieldName);
71: //获取字段属性
72: retVal = field.get(obj);
73: } catch (SecurityException e) {
74: // TODO Auto-generated catch block
75: e.printStackTrace();
76: } catch (IllegalArgumentException e) {
77: // TODO Auto-generated catch block
78: e.printStackTrace();
79: } catch (NoSuchFieldException e) {
80: // TODO Auto-generated catch block
81: e.printStackTrace();
82: } catch (IllegalAccessException e) {
83: // TODO Auto-generated catch block
84: e.printStackTrace();
85: }
86: return retVal;
87: }
88: }
一个小例题,通过反射将任意对象中所有可读String字段中的”a”转成”b”
1: public class FieldTest2 {
2:
3: /**
4: * @param args
5: * @throws ClassNotFoundException
6: * @throws IllegalAccessException
7: * @throws InstantiationException
8: */
9: public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
10:
11: //获取类的字节码对象并创建对象
12: String className = "reflect.Code";
13: Class cls = Class.forName(className);
14: Object obj = cls.newInstance();
15:
16: System.out.println(obj);
17: changeStringValue(obj);
18: System.out.println(obj);
19:
20: }
21:
22: /**
23: * 通过反射方法将类中可读String字段中的”a“转为”b“
24: * @param obj
25: */
26: private static void changeStringValue(Object obj){
27: //获取类字节码对象
28: Class cls = obj.getClass();
29: //获取类中所有字段
30: Field[] fields = cls.getFields();
31: //遍历字段,找到String类型字段,并修改
32: for(Field field : fields){
33: if(field.getType() == String.class){
34: try {
35: String s = (String)field.get(obj);
36: s = s.replaceAll("a", "b");
37: field.set(obj, s);
38: } catch (IllegalArgumentException e) {
39: // TODO Auto-generated catch block
40: e.printStackTrace();
41: } catch (IllegalAccessException e) {
42: // TODO Auto-generated catch block
43: e.printStackTrace();
44: }
45: }
46: }
47: }
48: }
Method类
public final class Method- extends AccessibleObject
- implements GenericDeclaration, Member
Method
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面我们来看看如何使用方法反射
1: public class MethodTest {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7:
8: //通过反射执行String对象的charAt方法
9: String str = "abcde";
10: Object obj = runMethod(String.class,str,"charAt",new Class[]{int.class},new Object[]{1});
11: System.out.println(obj);
12:
13: //通过反射执行Math的max方法
14: obj = runMethod(Math.class,null,"max",new Class[]{double.class,double.class},new Object[]{1.0,2.0});
15: System.out.println(obj);
16:
17: }
18: /**
19: * 执行任意对象的任意方法
20: * @param cls 类字节码对象
21: * @param obj 需要操作的对象
22: * @param methodName 方法名
23: * @param parameterTypes 方法参数列表类型
24: * @param args 需要传入的自定义参数列表
25: * @return
26: */
27: private static Object runMethod(Class cls,Object obj,String methodName,Class[] parameterTypes,Object[] args){
28: Object retVal = null;
29: try {
30: //通过方法名和参数列表获取方法对象
31: Method method = cls.getMethod(methodName, parameterTypes);
32: //通过方法的invoke方法执行对应对象的方法,同时传入参数列表
33: retVal = method.invoke(obj, args);
34: } catch (SecurityException e) {
35: // TODO Auto-generated catch block
36: e.printStackTrace();
37: } catch (IllegalArgumentException e) {
38: // TODO Auto-generated catch block
39: e.printStackTrace();
40: } catch (NoSuchMethodException e) {
41: // TODO Auto-generated catch block
42: e.printStackTrace();
43: } catch (IllegalAccessException e) {
44: // TODO Auto-generated catch block
45: e.printStackTrace();
46: } catch (InvocationTargetException e) {
47: // TODO Auto-generated catch block
48: e.printStackTrace();
49: }
50: return retVal;
51: }
52: }
我们注意到,当Method的invoke方法传入的对象引用为空时,其实代表的是类中的静态方法,因为静态方法可以直接由类名调用
Array类
数组的反射
在Class类的API文档里我们发现,只要是数据类型和维度都相同的数组,是共用同一份字节码的,下面的例子说明了这一问题
1: public static void main(String[] args) {
2: // TODO Auto-generated method stub
3: int[] a = new int[2];
4: int[] b = new int[3];
5: double[][] c = new double[3][];
6: double[][] d = new double[4][];
7:
8: //比较两份字节码是否相同
9: System.out.println(a.getClass() == b.getClass());
10: System.out.println(c.getClass() == d.getClass());
11:
12: //这句eclipse会自动判定编译错误
13: //Incompatible operand types Class<capture#5-of ? extends int[]>
14: //and Class<capture#6-of ? extends double[][]>
15: System.out.println(a.getClass() == c.getClass());
16: }
打印结果:
true
true
Array类应用
1: public class ArrayReflectTest {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7:
8: print(new String[]{"1","2"});
9: System.out.println();
10: print(new int[][]{{1,2,3},{4,5,6}});
11: }
12:
13: //打印对象,若为数组,则打印数组中元素
14: private static void print(Object obj){
15: Class cls = obj.getClass();
16: //判断所属类是否为数组类型
17: if(cls.isArray()){
18: //Array获取数组长度
19: int length = Array.getLength(obj);
20: for(int i = 0 ; i < length ; i ++){
21: //若元素仍为数组,即多维数组,则递归打印
22: Object objTemp = Array.get(obj, i);
23: if(objTemp.getClass().isArray())
24: print(objTemp);
25: else
26: System.out.print(objTemp+" ");
27: }
28: }
29: else{
30: System.out.println(obj);
31: }
32: }
33: }
好了,既然方法类和数组类都知道了,来处理一个小问题:参数为数组的方法的反射
请看下面例子,调用其他类的main函数引出该问题
1: class MainMethod{
2: public static void main(String[] args){
3: for(int i = 0 ; i < args.length ; i ++){
4: System.out.println(args[i]);
5: }
6: }
7: }
8:
9: public class MainTest {
10:
11: /**
12: * @param args
13: * @throws ClassNotFoundException
14: * @throws NoSuchMethodException
15: * @throws SecurityException
16: * @throws InvocationTargetException
17: * @throws IllegalAccessException
18: * @throws IllegalArgumentException
19: */
20: public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
21: // TODO Auto-generated method stub
22: Class cls = Class.forName("reflect.MainMethod");
23: Method mainMethod = cls.getMethod("main", String[].class);
24: mainMethod.invoke(null, new String[]{"1","2"});
25: }
26: }
上面的例子,会报错
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
之所以报错,是因为JDK1.5中Method的invoke方法接收的是可变参数Object… params,而JDK1.4中Method的invoke方法接收的是数组Object[] params,我们知道JDK1.5要向下兼容1.4的方法,所以当传入String类型数组时,JVM并不会将这个数组作为可变参数的第一个参数,而是按照JDK1.4的方法,将String数组转换成Object数组拆包,这样就相当于给main方法传递了两个参数”1”和”2”,而main方法只有一个String[]参数,所以出现了不合法的参数异常,那么既然知道了原因,修改方法也自然出来了
根据JDK1.4的修改方法:mainMethod.invoke(null, new Object[]{new String[]{"1","2"}});即为将参数列表封装成Object数组传递给invoke,令其拆包
根据JDK1.5的修改方法:mainMethod.invoke(null, (Object)new String[]{"1","2"});即为将参数列表的元素封装成Object对象,使可变参数接收
反射的意义
说了这么多反射的应用,但是究竟反射有何意义呢?
其实意义就在于它产生的原因,有了反射,就可以控制一些运行中不可见的因素,大大的提高了程序的扩展性,
今后的框架也是基于反射做的,开发框架的人在开发时并不知道需要调用哪些类,因为这些类都是使用者自己写的,他们根据反射将使用者将来写出的类做了分析,写出框架,而我们在使用框架的过程中只需要写自己的类,并将类的相关属性配置到框架的配置文件中即可
下面是一段模拟框架的小代码,包括框架代码和一个property配置文件
1: public class FrameTest {
2:
3: /**
4: * @param args
5: * @throws IOException
6: * @throws ClassNotFoundException
7: * @throws IllegalAccessException
8: * @throws InstantiationException
9: */
10: public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
11:
12: //通过Properties与字节流配合获取配置文件信息
13: InputStream ips = new FileInputStream("config.properties");
14: Properties prop = new Properties();
15: prop.load(ips);
16: //得到className
17: String className = prop.getProperty("className");
18:
19: //通过className创建对象,此处利用接口编程
20: Collection col = (Collection) Class.forName("java.util.ArrayList").newInstance();
21: col.add(1);
22: col.add(2);
23: col.add(3);
24:
25: System.out.println(col);
26: }
27: }
className = java.util.ArrayList
这就是一个简单的框架,利用接口编程,当我们需要更改使用的集合类时,只需要在配置文件中更改即可,非常方便