在项目进行单元测试的时候,用到了反射,结合这篇文章《JAVA反射机制 》进行了学习。http://blog.csdn.net/justinavril/article/details/2873664,有个别地方进行了补充。
Reflection是Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。 Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。
JavaBean 是 reflection 的实际应用之一,它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件(类) 的属性。
1. 一个简单的例子
考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。
import java.lang.reflect.*; public class DumpMethods { public static void main(String args[]) { try { //获得一个 Class 对象 Class c = Class.forName("java.util.Stack"); //取得该类中定义的所有方法(包含private方法) Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); } catch (Throwable e) { System.err.println(e); } } }它的结果输出为:
public java.lang.Object java.util.Stack.push(java.lang.Object) public synchronized java.lang.Object java.util.Stack.pop() public synchronized java.lang.Object java.util.Stack.peek() public boolean java.util.Stack.empty() public synchronized int java.util.Stack.search(java.lang.Object)这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。
这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。
2.开始使用 Reflection
用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:
第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。
下面就是获得一个 Class 对象的方法之一:
Class c = Class.forName("java.lang.String");
这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:
Class c = int.class; 或者 Class c = Integer.TYPE;
它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。
第二步是调用诸如getDeclaredMethods的方法,以取得该类中定义的所有方法(包含private方法)的列表。调用getDeclaredFields方法,获得该类中定义的所有字段(包含private字段)。
第三步是一旦取得上面这个信息,就可以使用 reflection API 来操作这些信息,如下面这段代码:
Class c = Class.forName("java.lang.String"); Method m[] = c.getDeclaredMethods(); System.out.println(m[0].toString());它将以文本方式打印出 String 中定义的第一个方法的原型。
在下面的例子中,这三个步骤将为使用 reflection 处理特殊应用程序提供例证。
模拟 instanceof 操作符
得到类信息之后,通常下一个步骤就是解决关于 Class 对象的一些基本的问题。例如,Class.isInstance 方法可以用于模拟 instanceof 操作符:
class S { } public class IsInstance { public static void main(String args[]) { try { Class cls = Class.forName("S"); boolean b1 = cls.isInstance(new Integer(37)); System.out.println(b1); boolean b2 = cls.isInstance(new S()); System.out.println(b2); } catch (Throwable e) { System.err.println(e); } Class c = Double.TYPE; } }它的结果输出为:
false true在这个例子中创建了一个S 类的 Class 对象,然后检查一些对象是否是S的实例。Integer(37) 不是,但 new S()是。
3.找出类的方法
找出一个类中定义了些什么方法,这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法:
import java.lang.reflect.Method; public class Method1 { private int f1(Object p, int x) throws NullPointerException { if (p == null) throw new NullPointerException(); return x; } public static void main(String args[]) { try { Class cls = Class.forName("Method1"); Method methlist[] = cls.getDeclaredMethods(); for (int i = 0; i < methlist.length; i++) { Method m = methlist[i]; //获得方法名,和声明方法所在的类 System.out.println("name = " + m.getName()); System.out.println("decl class = " + m.getDeclaringClass()); //获得方法所有参数类型 Class pvec[] = m.getParameterTypes(); for (int j = 0; j < pvec.length; j++) System.out.println("param #" + j + " " + pvec[j]); //获得方法所有异常 Class evec[] = m.getExceptionTypes(); for (int j = 0; j < evec.length; j++) System.out.println("exc #" + j + " " + evec[j]); //获得方法返回值类型 System.out.println("return type = " + m.getReturnType()); System.out.println("-----"); } } catch (Throwable e) { System.err.println(e); } } }输出的结果如下:
name = f1 decl class = class Method1 param #0 class java.lang.Object param #1 int exc #0 class java.lang.NullPointerException return type = int ----- name = main decl class = class Method1 param #0 class [Ljava.lang.String; return type = void -----这个程序首先取得 method1 类的描述,然后调用 getDeclaredMethods 来获取一系列的 Method 对象,它们分别描述了定义在类中的每一个方法,包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods,你还能获得继承来的各个方法的信息。
取得了 Method 对象列表之后,要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型,都可以由描述类的对象按顺序给出。
4.获取构造器信息
获取类构造器的用法与上述获取方法的用法类似,如:
import java.lang.reflect.Constructor; public class Constructor1 { public Constructor1() { } protected Constructor1(int i, double d) throws Exception { } public static void main(String args[]) { try { Class cls = Class.forName("Constructor1"); //获得所有的构造方法 Constructor ctorlist[] = cls.getDeclaredConstructors(); for (int i = 0; i < ctorlist.length; i++) { Constructor ct = ctorlist[i]; //输出构造方法名 System.out.println("name = " + ct.getName()); System.out.println("decl class = " + ct.getDeclaringClass()); //获得所有参数类型 Class pvec[] = ct.getParameterTypes(); for (int j = 0; j < pvec.length; j++) System.out.println("param #" + j + " " + pvec[j]); //获得所有异常 Class evec[] = ct.getExceptionTypes(); for (int j = 0; j < evec.length; j++) System.out.println("exc #" + j + " " + evec[j]); System.out.println("-----"); } } catch (Throwable e) { System.err.println(e); } } }这个例子中没能获得返回类型的相关信息,那是因为构造器没有返回类型。
这个程序运行的结果是:
name = Constructor1 decl class = class Constructor1 ----- name = Constructor1 decl class = class Constructor1 param #0 int param #1 double exc #0 class java.lang.Exception -----5.获取类的字段(域)
找出一个类中定义了哪些数据字段也是可能的,下面的代码就在干这个事情:
import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class Field1 { private double d; public static final int i = 37; String s = "testing"; public static void main(String args[]) { try { Class cls = Class.forName("Field1"); //获得该类中定义的所有字段(包含private字段) Field fieldlist[] = cls.getDeclaredFields(); for (int i = 0; i < fieldlist.length; i++) { Field fld = fieldlist[i]; //输出字段名,所在类,数据类型 System.out.println("name = " + fld.getName()); System.out.println("decl class = " + fld.getDeclaringClass()); System.out.println("type = " + fld.getType()); //输出字段修饰符 int mod = fld.getModifiers(); System.out.println("modifiers = " + Modifier.toString(mod)); System.out.println("-----"); } } catch (Throwable e) { System.err.println(e); } } }这个程序的输出是:
name = d decl class = class Field1 type = double modifiers = private ----- name = i decl class = class Field1 type = int modifiers = public static final ----- name = s decl class = class Field1 type = class java.lang.String modifiers = -----这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier,它也是一个 reflection 类,用来描述字段成员的修饰语,如“private int”。这些修饰语自身由整数描述,而且使用 Modifier.toString 来返回以“官方”顺序排列的字符串描述 (如“static”在“final”之前)。
和获取方法的情况一下,获取字段的时候也可以只取得在当前类中申明了的字段信息 (getDeclaredFields),或者也可以取得父类中定义的字段 (getFields) 。
6.根据方法的名称来执行方法
文本到这里,所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情,比如执行一个指定了名称的方法。下面的示例演示了这一操作:
import java.lang.reflect.Method; public class Method2 { public int add(int a, int b) { return a + b; } public static void main(String args[]) { try { Class cls = Class.forName("Method2"); //声明一个存放参数类型的Class数组 Class partypes[] = new Class[2]; partypes[0] = Integer.TYPE; partypes[1] = Integer.TYPE; // 利用Class对象的getMethod方法,根据方法名、参数类型数组获得Method对象 Method meth = cls.getMethod("add", partypes); Method2 methobj = new Method2(); //声明存放实际参数的Object数组 Object arglist[] = new Object[2]; arglist[0] = new Integer(37); arglist[1] = new Integer(47); //利用Method对象的invoke方法,执行add方法, //参数1:方法所在类的实际对象,参数2:方法的实际参数数组,返回值为要执行的add方法的返回值 Object retobj = meth.invoke(methobj, arglist); Integer retval = (Integer) retobj; System.out.println(retval.intValue()); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
84假如一个程序在执行的某处的时候才知道需要执行某个方法,这个方法的名称是在程序的运行过程中指定的 (例如,JavaBean 开发环境中就会做这样的事),那么上面的程序演示了如何做到。
上例中,getMethod用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的Method 对象之后,在正确的对象实例中执行它。执行该方法的时候,需要提供一个参数列表,这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象,它封装了返回值 84。
7.创建新的对象
对于构造器,则不能像执行方法那样进行,因为执行一个构造器就意味着创建了一个新的对象 (准确的说,创建一个对象的过程包括分配内存和构造对象)。所以,与上例最相似的例子如下:
import java.lang.reflect.Constructor; public class Constructor2 { public Constructor2() { } public Constructor2(int a, int b) { System.out.println("a = " + a + " b = " + b); } public static void main(String args[]) { try { Class cls = Class.forName("Constructor2"); //声明一个存放参数类型的Class数组 Class partypes[] = new Class[2]; partypes[0] = Integer.TYPE; partypes[1] = Integer.TYPE; //根据指定的参数类型获得相应的构造函数的Constructor对象 Constructor ct = cls.getConstructor(partypes); //声明存放实际参数的Object数组,指定构造函数的实际参数 Object arglist[] = new Object[2]; arglist[0] = new Integer(37); arglist[1] = new Integer(47); //产生并执行构造函数 Object retobj = ct.newInstance(arglist); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
a = 37 b = 47根据指定的参数类型找到相应的构造函数并执行它,以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象,而不是在编译的时候创建对象,这一点非常有价值。
8.改变字段(域)的值
reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它,下面的例子可以说明这一点:
import java.lang.reflect.Field; public class Field2 { public double d=1.11; public String name="ZhangSan"; public static void main(String args[]) { try { Class cls = Class.forName("Field2"); Field2 fobj = new Field2(); System.out.println("d = " + fobj.d); System.out.println("d = " + fobj.name); //根据字段名获得字段对象 Field fld = cls.getField("d"); //更改字段的值。参数1:该字段所在类的对象,参数2:该字段的新值 fld.setDouble(fobj, 12.34); //根据字段名获得字段对象 Field flname = cls.getField("name"); //更改字段的值。参数1:该字段所在类的对象,参数2:该字段的新值 flname.set(fobj, "LiSi"); System.out.println("d = " + fobj.d); System.out.println("d = " + fobj.name); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
d = 1.11 d = ZhangSan d = 12.34 d = LiSi9.使用数组
本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型,一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的:
import java.lang.reflect.Array; public class Array1 { public static void main(String args[]) { try { Class cls = Class.forName("java.lang.String"); System.out.println(cls.getName()); //创建一个长度为10的数组 Object arr = Array.newInstance(cls, 10); //给数组第6个元素赋值 Array.set(arr, 5, "this is a test"); //获得数组中第6个元素中存放的值 String s = (String) Array.get(arr, 5); System.out.println(s); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
java.lang.String this is a test例中创建了 10 个单位长度的 String 数组,为第 5 个位置的字符串赋了值,最后将这个字符串从数组中取得并打印了出来。
下面这段代码提供了一个更复杂的例子:
import java.lang.reflect.Array; public class Array2 { public static void main(String args[]) { //创建了一个 5 x 10 x 15 的整型数组 int dims[] = new int[] { 5, 10, 15 }; Object arr = Array.newInstance(Integer.TYPE, dims); System.out.println(arr.getClass().getComponentType()); //获得 3 x 10 x 15处的整型数组 Object arrobj = Array.get(arr, 3); System.out.println(arrobj.getClass().getComponentType()); //获得 3 x 5 x 15处的整型数组 arrobj = Array.get(arrobj, 5); //在 3 x 5 x 15处的整型数组里,下标为10的元素里的值设为37 Array.setInt(arrobj, 10, 37); System.out.println(arrobj.getClass().getComponentType()); int arrcast[][][] = (int[][][]) arr; System.out.println(arrcast[3][5][10]); } }这个程序运行的结果是:
class [[I class [I int 37例中创建了一个 5 x 10 x 15 的整型数组,并为处于 [3][5][10] 的元素赋了值为 37。注意,多维数组实际上就是数组的数组,例如,第一个 Array.get 之后,arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素,即长度为 15 的数组,并使用 Array.setInt 为它的第 10 个元素赋值。
注意创建数组时的类型是动态的,在编译时并不知道其类型。
10.补充,利用反射,调用类的私有字段,并赋新值
在某些时候,特别在进行单元测试某个方法时,而此方法内调用一个私有成员变量,当此私有成员变量没被赋值,是null的时候,此有成员变量对象的成员也不能调用,甚至MOCK也不能。
假设有如下存在私有方法的类:
public class Student { private String stuName=null; public void sayName(){ System.out.println("My name is "+stuName.toUpperCase()); } private String sayNameAge(String name, int age){ return ("My name is "+name+", My age "+age); } }下面是调用方式:
import java.lang.reflect.Field; //利用反射,调用类的私有字段,并设置新值 public class Field3 { public static void main(String args[]) { try { Class cls = Class.forName("Student"); Student stuObj=new Student(); //根据字段名获得字段对象,也可以用getField Field fld = cls.getDeclaredField("stuName"); //设置该字段的可访问性 fld.setAccessible(true); //更改私有字段的值。参数1:该字段所在类的对象,参数2:该字段的新值 fld.set(stuObj, "Zhang San"); stuObj.sayName(); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
My name is ZHANG SAN通过setAccessible方法,把私有字段stuName的访问属性,该为public。如果私有变量stuName没有被初始化,则在调用stuName.toUpperCase()方法时就出错。所以在此种情况下,可以通过反射的形式,让私有变量也可以访问和初始化。
11.补充,利用反射,调用类的私有方法
继续利用上面的Student类,调用里面的私有方法sayNameAge(),代码如下:
import java.lang.reflect.Field; //利用反射,调用类的私有方法 public class Method3 { public static void main(String args[]) { try { Class cls = Class.forName("Student"); //声明一个存放参数类型的Class数组 Class partypes[] = new Class[2]; partypes[0] = String.class; partypes[1] = Integer.TYPE; // 利用Class对象的getMethod方法,根据方法名、参数类型数组获得Method对象 Method meth = cls.getDeclaredMethod("sayNameAge",partypes); Student stuObj=new Student(); //设置该方法的可访问性 meth.setAccessible(true); //声明存放实际参数的Object数组 Object arglist[] = new Object[2]; arglist[0] = new String("ZhangSan"); arglist[1] = new Integer(150); //利用Method对象的invoke方法,执行say方法, //参数1:方法所在类的实际对象,参数2:方法的实际参数数组,返回值为要执行的add方法结果 Object retobj = meth.invoke(stuObj, arglist); System.out.println(retobj); } catch (Throwable e) { System.err.println(e); } } }这个程序运行的结果是:
My name is ZhangSan, My age 150首先声明一个参数类型的Class对象数组,然后利用此数组和方法名,调用Class对象的getDeclaredMethod方法生成Method对象,并设置可访问性,然后定义方法实参Object对象数组,利用此实参数组,和方法所在类的实际对象,通过调用Method对象的invoke方法来执行私有方法,执行结果就是私有方法的返回值。