- 浏览: 64079 次
- 性别:
- 来自: 重庆
文章分类
最新评论
二、Java反射API
Java中以反射(reflection)API实现了Java语言的自省,关于自省和反射的区别,笔者无法给出明确的定义。反射API使得Java语言更易实现运行时的动态性,获取Java程序在运行时刻的内部结构,如Java列中的构造方法、域和方法等。反射API的主要功能包括:
① 确定一个对象的类
② 取出类的修饰符(modifiers),字段,方法,构造器和超类
③ 找出某个接口里定义的常量和方法说明
④ 创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象)
⑤ 取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做倒。
⑥ 在运行时刻调用动态对象的方法
⑦ 创建数组,数组大小和类型在运行时刻才确定。也能更改数组成员的值。
1. 对类的分析
① 获取对象的类(Class对象)
Class c = obj.getClass();
② 获取一个类的超类
Class sc = c.getSuperclass();
下面一段代码打印一个对象所有的父类
③ 获取类的名字
String name = c.getName();
④ 获取类的修饰符
int m = c.getModifiers();
此处getModifiers()方法返回的是个整型的结果,结果m并不具体表示哪一种修饰符。先看java.lang.reflect.Modifier类,该类对修饰符进行了相应的包装。
类Modifier中定义了一系列的类描述符常量:Modifier.ABSTRACT、Modifier.FINAL、Modifier.PUBLIC等。getModifiers()方法返回的即是类的所有修饰符分别对应的整型值的或运算结果,如public abstract class MyClass{},new MyClass().getClass().getModfiers()即得到Modifier.ABSTRACT | Modifier.PUBLIC。
同时Modifier类提供了一些列的方法,用于判断getModifiers()返回的整型数字中包含的类修饰符:Modifier.isPublic(m)、Modifier.isAbstract(m)、Modifier.isFinal(m)等。
⑤ 确定一个类实现的接口
Class[] theInterfaces = c.getInterfaces();
从上面的语句中可以看到,接口在反射的API中也是用Class表示的,接口是一种特殊的抽象类。可以使用Class类的isInterface()方法判断一个Class类型是一个类还是接口。
⑥ 获取类的字段
一个类的字段(Field)可能来自本类、父类、实现的接口或者接口的接口,可以使用Class对象的getFields()方法获取类中所有的public属性的字段的数组(Field对象数组)。Field对象提供方法取得字段的名字、类型和描述符,甚至可以给字段赋值或者取字段的值。当然,Class类对象提供了getDeclaredFields()方法获取包括public属性在内的所有在类中声明了的域。
⑦ 获取构造方法
构造方法是在创建类对象时调用的特殊方法,构造方法可以重载,由它们的参数加以区别。调用getConstructors方法可以取得类构造方法的有关信息,这个方法返回一个数组的
Constructor对象。可以用 Constructor对象里的相关方法来确定构造方法的名字、描述符、参数类型和抛出的意外列表。也可以用Constructor.newInstance创建一个新的Constructor对象。
⑧ 获取成员方法
如何找出类的public方法呢?当然是调用getMethods方法。由getMethods方法返回一个数组,数组元素类型是Method对象。方法的名字,类型,参数,描述和抛出的意外都可
以由Method对象的方法来取得。用 Method.invoke 方法自己调用这个方法。
Method类的对象可以通过getName取方法名、getReturnType取返回值的类型、用
getParameterTypes取得参数类型(Class对象)的数组,对每个参数用getName取参数的类型名。
同样,对于非public的方法,可以使用getDeclaredMethods()方法获取。
2. 实现程序的动态性
① 创建对象
如果知道一个类型,很容易使用new操作符创建一个类的实例。但是如果在编译时并不知道具体要实例化的是哪个类的对象,如何创建该实例呢?
Java中提供Class.forName(String className)从一个字符串(含包的类全名称)加载一个类,再利用newInstance方法创建该类的实例。
当然,也可以先获取一个类的Constructor,再调用其newInstance方法创建对象。不同的是,constructor对象的newInstance方法需要传递一个对象数组作为构造方法的参数列表。
② 获取/设置字段值
利用Reflection API获取或者设置字段的值,首先要得到Class对象,然后利用Class对象的getField方法取得相应的字段Field对象,然后调用Field对象对应的getXXX/setXXX方法获取或者设置属性的值。Filed对象提供getInt/setInt、getLong/setLong等方法对基本类型的属性进行值的获取和设置,可以直接使用get/set方法获取复杂类型属性的值(返回一个对象值/传递一个对象值作为参数)。
具体操作方法见如下代码片段。当然,若试图获取/设置一个非public的字段值(getField方法也不能获取非pubic的属性对象,可以尝试使用getDeclaredField方法),将产生IllegalAccessException,可以通过setAccessible(true)使非public的字段可见,然后对字段值进行访问。
③ 调用方法
方法调用的过程类似于设置字段值的过程,首先要得到Class对象,然后调用getMethod方法得到方法Method的对象,getMethod方法要求传递两个参数,一个是方法名,第二个是Class[],即方法参数列表中各参数对应的类型的数组。然后执行Method对象的invoke方法,进行方法的调用,invoke方法需要传递两个参数,第一个是方法绑定的对象,第二个是方法的参数列表。如果是static的方法,则第一个参数将自动被忽略(可为null)。
同理,对于非public的方法,可以使用getDeclaredMethod方法获取Method对象,并使用setAccessible设置其可见性。
3. 其他操作
① 操作数组
反射API中对数组的操作方式不同于一般的java对象,需要通过专门的java.lang.reflect.Array工具类进行实现。Array类提供了创建和操作数组中元素的方法。Array.newInstance方法用来创建新数组,第一个参数为数组中元素的类型,后面的参数为数组各维度的长度(newInstance为变长参数的方法)。
② 关于权限和异常
使用Java反射API的一个重要好处是可以绕过Java语言中默认的访问控制权限。Constructor、Filed和Method都继承自java.lang.reflect.AccessibleObject,其中的setAccessible方法可以用于设置是否绕过默认的权限检查,否则,访问非public的方法或者字段将产生IllegalAccessException异常。
而在利用Invoke方法来调用方法是,如果方法本身抛出异常,invoke方法将抛出InvocationTargetException异常,通过getCause方法可以获取实际的异常信息。
在Java7中,为所有与反射相关的异常类添加了一个统一的父类java.lang.ReflectiveOperationException,在处理反射相关的异常时可以直接捕获该异常而不用分别捕获各个异常子类。
关于Java动态性,除了反射机制外,Java7中提供了一种新的动态调用Java程序的方法,即方法句柄MethodHandler,有些类似于C中的函数指针。此部分内容将在后续的文章中进行单独的分析。
附件中包含了该文章的测试代码。
Java中以反射(reflection)API实现了Java语言的自省,关于自省和反射的区别,笔者无法给出明确的定义。反射API使得Java语言更易实现运行时的动态性,获取Java程序在运行时刻的内部结构,如Java列中的构造方法、域和方法等。反射API的主要功能包括:
① 确定一个对象的类
② 取出类的修饰符(modifiers),字段,方法,构造器和超类
③ 找出某个接口里定义的常量和方法说明
④ 创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象)
⑤ 取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做倒。
⑥ 在运行时刻调用动态对象的方法
⑦ 创建数组,数组大小和类型在运行时刻才确定。也能更改数组成员的值。
1. 对类的分析
① 获取对象的类(Class对象)
Class c = obj.getClass();
② 获取一个类的超类
Class sc = c.getSuperclass();
下面一段代码打印一个对象所有的父类
// 分析类的父类 public static void printSuperclasses(Object o) { Class subclass = o.getClass(); Class superclass = subclass.getSuperclass(); while (superclass != null) { String className = superclass.getName(); System.out.println(className); subclass = superclass; superclass = subclass.getSuperclass(); } }
③ 获取类的名字
String name = c.getName();
// 分析对象的类 public static void classAnalysis(Object obj) { // 获得对象的类 Class c = obj.getClass(); System.out.println(obj + " is instance of class: " + c.getName()); // 获得对象所属类的父类 Class sc = c.getSuperclass(); System.out.println(c.getName() + " inherits from class: " + sc.getName()); }
④ 获取类的修饰符
int m = c.getModifiers();
此处getModifiers()方法返回的是个整型的结果,结果m并不具体表示哪一种修饰符。先看java.lang.reflect.Modifier类,该类对修饰符进行了相应的包装。
类Modifier中定义了一系列的类描述符常量:Modifier.ABSTRACT、Modifier.FINAL、Modifier.PUBLIC等。getModifiers()方法返回的即是类的所有修饰符分别对应的整型值的或运算结果,如public abstract class MyClass{},new MyClass().getClass().getModfiers()即得到Modifier.ABSTRACT | Modifier.PUBLIC。
同时Modifier类提供了一些列的方法,用于判断getModifiers()返回的整型数字中包含的类修饰符:Modifier.isPublic(m)、Modifier.isAbstract(m)、Modifier.isFinal(m)等。
// 分析类的修饰符 public static void printModifiers(Object obj) { System.out.print("Modifier: "); Class c = obj.getClass(); int m = c.getModifiers(); if (Modifier.isPublic(m)) System.out.print("public "); if (Modifier.isAbstract(m)) System.out.print("abstract "); if (Modifier.isFinal(m)) System.out.print("final "); System.out.println(c.getName()); }
⑤ 确定一个类实现的接口
Class[] theInterfaces = c.getInterfaces();
从上面的语句中可以看到,接口在反射的API中也是用Class表示的,接口是一种特殊的抽象类。可以使用Class类的isInterface()方法判断一个Class类型是一个类还是接口。
// 分析类的接口 public static void printInterfaceNames(Object o) { Class c = o.getClass(); Class[] theInterfaces = c.getInterfaces(); for (int i = 0; i < theInterfaces.length; i++) { String interfaceName = theInterfaces[i].getName(); System.out.println(interfaceName); } }
// 判断接口 public static void verifyInterface(Class c) { String name = c.getName(); if (c.isInterface()) { System.out.println(name + " 是接口."); } else { System.out.println(name + " 是类."); } }
⑥ 获取类的字段
一个类的字段(Field)可能来自本类、父类、实现的接口或者接口的接口,可以使用Class对象的getFields()方法获取类中所有的public属性的字段的数组(Field对象数组)。Field对象提供方法取得字段的名字、类型和描述符,甚至可以给字段赋值或者取字段的值。当然,Class类对象提供了getDeclaredFields()方法获取包括public属性在内的所有在类中声明了的域。
// 分析类的字段 public static void printFieldNames(Object o) { Class c = o.getClass(); //public的字段 Field[] publicFields = c.getFields(); for (int i = 0; i < publicFields.length; i++) { String fieldName = publicFields[i].getName(); Class typeClass = publicFields[i].getType(); String fieldType = typeClass.getName(); System.out.println("字段名: " + fieldName + ", 类型: " + fieldType); } //所有的字段 Field[] allFields = c.getDeclaredFields(); for (int i = 0; i < allFields.length; i++) { String fieldName = allFields[i].getName(); Class typeClass = allFields[i].getType(); String fieldType = typeClass.getName(); System.out.println("字段名: " + fieldName + ", 类型: " + fieldType); } }
⑦ 获取构造方法
构造方法是在创建类对象时调用的特殊方法,构造方法可以重载,由它们的参数加以区别。调用getConstructors方法可以取得类构造方法的有关信息,这个方法返回一个数组的
Constructor对象。可以用 Constructor对象里的相关方法来确定构造方法的名字、描述符、参数类型和抛出的意外列表。也可以用Constructor.newInstance创建一个新的Constructor对象。
//分析类的构造方法 public static void showConstructors(Object o) { Class c = o.getClass(); Constructor[] theConstructors = c.getConstructors(); for (int i = 0; i < theConstructors.length; i++) { System.out.print(theConstructors[i].getName()); System.out.print(" ( "); Class[] parameterTypes = theConstructors[i].getParameterTypes(); for (int k = 0; k < parameterTypes.length; k++) { String parameterString = parameterTypes[k].getName(); System.out.print(parameterString + " "); } System.out.println(")"); } }
⑧ 获取成员方法
如何找出类的public方法呢?当然是调用getMethods方法。由getMethods方法返回一个数组,数组元素类型是Method对象。方法的名字,类型,参数,描述和抛出的意外都可
以由Method对象的方法来取得。用 Method.invoke 方法自己调用这个方法。
Method类的对象可以通过getName取方法名、getReturnType取返回值的类型、用
getParameterTypes取得参数类型(Class对象)的数组,对每个参数用getName取参数的类型名。
同样,对于非public的方法,可以使用getDeclaredMethods()方法获取。
//分析类中的成员方法 public static void showMethods(Object o) { Class c = o.getClass(); Method[] theMethods = c.getMethods(); for (int i = 0; i < theMethods.length; i++) { String methodString = theMethods[i].getName(); System.out.println("Name: " + methodString); String returnString = theMethods[i].getReturnType().getName(); System.out.println(" Return Type: " + returnString); Class[] parameterTypes = theMethods[i].getParameterTypes(); System.out.print(" Parameter Types:"); for (int k = 0; k < parameterTypes.length; k++) { String parameterString = parameterTypes[k].getName(); System.out.print(" " + parameterString); } System.out.println(); } }
2. 实现程序的动态性
① 创建对象
如果知道一个类型,很容易使用new操作符创建一个类的实例。但是如果在编译时并不知道具体要实例化的是哪个类的对象,如何创建该实例呢?
Java中提供Class.forName(String className)从一个字符串(含包的类全名称)加载一个类,再利用newInstance方法创建该类的实例。
//动态创建类对象 public static Object createObject(String className) { Object object = null; try { Class classDefinition = Class.forName(className); object = classDefinition.newInstance(); } catch (InstantiationException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (ClassNotFoundException e) { System.out.println(e); } return object; }
当然,也可以先获取一个类的Constructor,再调用其newInstance方法创建对象。不同的是,constructor对象的newInstance方法需要传递一个对象数组作为构造方法的参数列表。
//使用Constructor动态创建对象 public static Object createObject(Constructor constructor, Object[] arguments) { System.out.println("Constructor: " + constructor.toString()); Object object = null; try { object = constructor.newInstance(arguments); System.out.println("Object: " + object.toString()); return object; } catch (InstantiationException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (IllegalArgumentException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } return object; }
② 获取/设置字段值
利用Reflection API获取或者设置字段的值,首先要得到Class对象,然后利用Class对象的getField方法取得相应的字段Field对象,然后调用Field对象对应的getXXX/setXXX方法获取或者设置属性的值。Filed对象提供getInt/setInt、getLong/setLong等方法对基本类型的属性进行值的获取和设置,可以直接使用get/set方法获取复杂类型属性的值(返回一个对象值/传递一个对象值作为参数)。
具体操作方法见如下代码片段。当然,若试图获取/设置一个非public的字段值(getField方法也不能获取非pubic的属性对象,可以尝试使用getDeclaredField方法),将产生IllegalAccessException,可以通过setAccessible(true)使非public的字段可见,然后对字段值进行访问。
// 获取字段值 static void getFiledValue(Object o, String filedName) { Class c = o.getClass(); Field filed; Object value; try { filed = c.getField(filedName); // filed = c.getDeclaredField(filedName); // filed.setAccessible(true); //修改字段访问权限 value = filed.get(o); //可使用getInt、getLong等(若知晓字段基本类型) System.out.println(filedName + ": " + value.toString()); } catch (NoSuchFieldException e) { System.out.println(e); } catch (SecurityException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } }
// 修改字段值 public static void setFieldValue(Object o, String filedName, Object value) { Field filed; Class c = o.getClass(); try { filed = c.getField(filedName); // filed = c.getDeclaredField(filedName); // filed.setAccessible(true); //修改字段访问权限 filed.set(o, value); //可使用setInt、setLong等(若知晓字段基本类型) } catch (NoSuchFieldException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } }
③ 调用方法
方法调用的过程类似于设置字段值的过程,首先要得到Class对象,然后调用getMethod方法得到方法Method的对象,getMethod方法要求传递两个参数,一个是方法名,第二个是Class[],即方法参数列表中各参数对应的类型的数组。然后执行Method对象的invoke方法,进行方法的调用,invoke方法需要传递两个参数,第一个是方法绑定的对象,第二个是方法的参数列表。如果是static的方法,则第一个参数将自动被忽略(可为null)。
同理,对于非public的方法,可以使用getDeclaredMethod方法获取Method对象,并使用setAccessible设置其可见性。
//动态调用方法 public static Object callMethod(Object o, String methodName, Class paramsType[], Object paramsValue[]) { Object result = null; Class c; Method method; try { c = o.getClass(); method = c.getMethod(methodName, paramsType); // method = c.getDeclaredMethod(methodName, paramsType); // method.setAccessible(true); result = method.invoke(o, paramsValue); } catch (NoSuchMethodException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } catch (InvocationTargetException e) { System.out.println(e); } return result; }
3. 其他操作
① 操作数组
反射API中对数组的操作方式不同于一般的java对象,需要通过专门的java.lang.reflect.Array工具类进行实现。Array类提供了创建和操作数组中元素的方法。Array.newInstance方法用来创建新数组,第一个参数为数组中元素的类型,后面的参数为数组各维度的长度(newInstance为变长参数的方法)。
// 反射API操作数组 public static void useArray() { String[] names = (String[]) Array.newInstance(String.class, 10); names[0] = "Hello"; Array.set(names, 1, "World"); String str = (String) Array.get(names, 0); int[][][] matrix1 = (int[][][]) Array.newInstance(int.class, 3, 3, 3); matrix1[0][0][0] = 1; int[][][] matrix2 = (int[][][]) Array.newInstance(int[].class, 3, 4); matrix2[0][0] = new int[10]; matrix2[0][1] = new int[3]; matrix2[0][0][1] = 1; }
② 关于权限和异常
使用Java反射API的一个重要好处是可以绕过Java语言中默认的访问控制权限。Constructor、Filed和Method都继承自java.lang.reflect.AccessibleObject,其中的setAccessible方法可以用于设置是否绕过默认的权限检查,否则,访问非public的方法或者字段将产生IllegalAccessException异常。
而在利用Invoke方法来调用方法是,如果方法本身抛出异常,invoke方法将抛出InvocationTargetException异常,通过getCause方法可以获取实际的异常信息。
在Java7中,为所有与反射相关的异常类添加了一个统一的父类java.lang.ReflectiveOperationException,在处理反射相关的异常时可以直接捕获该异常而不用分别捕获各个异常子类。
关于Java动态性,除了反射机制外,Java7中提供了一种新的动态调用Java程序的方法,即方法句柄MethodHandler,有些类似于C中的函数指针。此部分内容将在后续的文章中进行单独的分析。
附件中包含了该文章的测试代码。
- ReflectionDemo.jar (6.5 KB)
- 下载次数: 10
发表评论
-
SpingMVC第一个拦截器未执行BUG分析
2016-07-15 16:24 2368问题描述: SpringMvc项目中使用<mvc: ... -
MySQL JDBC的setFetchSize
2014-12-05 15:09 5247正常情况下MySQL的JDBC是不支持setFetc ... -
Java SE 6 HotSpot虚拟机的垃圾回收机制
2012-10-31 21:25 1215官方资料,关于Java SE 6 HotSpot虚拟机的gar ... -
Java语言的动态性支持(一)
2012-10-26 00:37 12207一、脚本语言的支持 JSR 223中规范了在Java ... -
Java并发程序设计-注解
2012-10-24 10:50 89841. 类Annotation 3个Annotation描述类的 ... -
Java Applet小结
2011-12-03 23:44 72941. Applet基础 在网页 ...
相关推荐
WWW的发展也是日新月异,它已不止局限于展示静止信息,正在不断增强交互和动态性。许多商家和企业也把目光瞄准了WWW,可以预料,WWW世界将变得越来越丰富多彩。 Internet(含WWW)为人们提供了许多有用的信息,然而...
Java语言的面向对象、跨平台、语言级并发支持、安全等特性不仅使它在互联网领域得到广泛应用,也引起了嵌入式领域研究人员的高度重视,他们希望能将Java语言改造成嵌入式及实时系统开发的主流语言来提高开发效率及...
java.lang.annotation 为 Java 编程语言注释设施提供库支持。 java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机...
Java 是 Sun 公司推出的新的一代面向对象程序设计语言,特别适合于 Internet应用程序开发,它的平台无关性直接威胁到 Wintel 的垄断地位。一时间,“连Internet,用 Java 编程”,成为技术人员的一种时尚。 Java 是...
动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的...
动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的...
许可证:Apache-2.0 开发语言:Java、JavaScript、TypeScript 官网:/ Appsmith 是一个用于构建管理面板、内部工具和仪表板的低代码项目。与超过 15 个数据库和任何 API 集成。构建你需要的一切,速度提高 10 倍。...
动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的...
动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的...
在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率。 主要特点 MVC 架构,设计精巧,使用简单 遵循 COC 原则,支持零配置,无 XML 独创 Db + Record 模式,灵活便利 ActiveRecord 支持,使...
在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 :) JFinal有如下主要特点: MVC架构,设计精巧,使用简单 遵循COC原则,零配置,无xml 独创...
在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 :) JFinal有如下主要特点 MVC 架构,设计精巧,使用简单 遵循 COC 原则,零配置,无 XML 独创 ...
动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的...
在拥有Java语言所有优势的同时再拥有ruby、python、php等动态语言的开发效率!为您节约更多时间,去陪恋人、家人和朋友 :) JFinal有如下主要特点: MVC架构,设计精巧,使用简单 遵循COC原则,零配置,无xml 独创...
Java是由Sun公司推出的Java程序设计语言和Java软件开发平台的总称。有一个庞大的库,库中包含很多可重用的代码和提供安全性、可移植性以及可自动垃圾回收等服务的执行环境。... 动态性:适应动态变化的环境。
java2级笔记 面向对象 java把所有的java应用和applet都将看成对象,按类封装 ... 动态性 高性能 Applet的特点:嵌入HTML中,支持java的浏览器上运行 java与c++相比最突出的是跨平台性 不允许使用指针—健壮性