疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java学习笔记:反射


 

        我们知道,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

  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

  14: //and Class

  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: }

  配置文件为config.properties

  里面内容为

  className = java.util.ArrayList

  这就是一个简单的框架,利用接口编程,当我们需要更改使用的集合类时,只需要在配置文件中更改即可,非常方便