java中一个强大的功能,莫过于反射了。通常我们看看的Struct2、Struct1、Spring、Hibernate等等集合无一不使用了反射机制。那么什么是反射呢,到底有什么用呢?
一、反射机制概念
简单的讲,反射就是通过把指定的类中各种元素成分都映射成相关的反射包中的相应类,使得我们可以动态的调用类的相应成员,比如构造方法、成员方法、成员变量等。它被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。
换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。
二、反射的功能:
2.1在运行时判断任意一个对象所属的类 如 aclass.getClass() == bclass.getClass()
2.2在运行时构造任意一个类的对象 如Class.forName(className).netInstance(),className是运行时才得出来的类名
2.3在运行时判段任意一个类所具有的成员变量和方法
2.4在运行时调用任一个对象的方法
2.5在运行时创建新类对象
三、反射API中重点的几个类
Class:Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class 对象
没有构造方法,具体如何获取Class对象的实例,下面的示例代码中将会总结。
常用的方法用:
T cast(Object obj) 、static Class<?> forName(String className) 、 ClassLoader getClassLoader() 、 Constructor<T> getConstructor(Class<?>... parameterTypes) 、
Constructor<?>[] getConstructors() 、 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)、 Constructor<?>[] getDeclaredConstructors() 、
Field getDeclaredField(String name)、 Field[] getDeclaredFields() 、Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods() 、Field getField(String name) 、 Field[] getFields() 、 Method getMethod(String name, Class<?>... parameterTypes)
Method[] getMethods() 、int getModifiers() 、String getName() 、InputStream getResourceAsStream(String name)、 boolean isArray() 、T newInstance()
Constructor:Constructor 提供关于类的单个构造方法的信息以及对它的访问权限
没用构造方法,一般都是通过Class对象中的相应类来获得Constructor对象的。
常用的方法:
Class<T> getDeclaringClass() 、Class<?>[] getExceptionTypes() 、Type[] getGenericExceptionTypes()、Type[] getGenericParameterTypes() 、
int getModifiers() 、String getName() 、Class<?>[] getParameterTypes() 、boolean isVarArgs() 、 T newInstance(Object... initargs)
Method:Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
没有构造方法,也是通常Class对象中相应的方法来获取Method对象。
常用的方法:
Class<?> getDeclaringClass() 、int getModifiers() 、String getName() 、Class<?>[] getParameterTypes()
Class<?> getReturnType() 、Object invoke(Object obj, Object... args)
Field:Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
没有构造方法,也是通常Class对象中相应的方法来获取Method对象。
常用的方法:
Object get(Object obj) 、 int getModifiers() 、String getName() 、Class<?> getType() 、 void set(Object obj, Object value)。另外还支持相应的int getInt(Object obj)
与void setInt(Object obj, int value)等相应的直接对基本数据类型进行操作。
Modifier:Modifier 类提供了static 方法和常量,对类和成员访问修饰符进行解码。
这是没有构造方法的,都是通过相应的Class、Constructor、Method、Field类对象得到的,且常用的方法static boolean isPublic(int mod) 、static boolean isPrivate(int mod) 等,用个方法是static String toString(int mod)蛮好用的,此方法是返回描述指定修饰符中的访问修饰符标志的字符串。
四、反射举例代码:
在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其他的对象
- package com.enhance;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- public class ReflectDemo {
- private int pos ;
- public String name;
- private int size ;
- private final static double PI = 3.1415926;
- private ArrayList<String> alist = new ArrayList<String>();
- /**
- * 静态方法,反射调用invork时,第一个参数为null即无作用对象,由于也没有参数。
- * 所以最终的反射在调用时,肯定是invork(null)即可。
- * @return
- */
- public static double getPI(){
- return PI;
- }
- /**
- * @param args
- * @throws Exception
- */
- public static void main(String[] args) throws Exception {
- getClassTest();
- applyReflectChangeObjValDemo();
- reflectInvorkArrayDeom();
- //获取一个类的方法签名、构造函数签名、成员变量等。
- printClass("java.lang.String");
- arraysAsListTest();
- }
- public static void getClassTest() throws Exception{
- /*
- 三种方式来获得Class对象,三种方式得出来的对象都是一个都是此类的字节码对象
- 1> 对象.getClass()
- 2> 类名.class 不会加载类,即不会静态初始化等
- 3> Class.forName(完全限定类名) 会加载类,会静态初始化
- */
- String str1 = "abc";
- Class c1 = str1.getClass();
- Class c2 = String.class;
- Class c3 = Class.forName("java.lang.String");
- System.out.println("c1==c2::"+(c1==c2)); // true
- System.out.println("c2==c3::"+(c2==c3)); // true
- //Class对象中用isPrimitive()方法可判断是否基本类型字节码
- System.out.println("c1.isPrimitive()=="+c1.isPrimitive()); //false
- Class c4 = int.class;
- System.out.println("c1.isPrimitive()=="+c4.isPrimitive()); // true
- System.out.println("c3==c4::"+(c3==c4)); // false
- //包装类,可使用类名.TYPE得到基本类型的Class对象
- Class c5 = Integer.TYPE;
- System.out.println("c4==c5::"+(c4==c5)); // true
- //反射得到实例对象的,有两个方法一种是先得到反射中的构造器,通过构造器的newInstance来搞定
- //另一种是直接通过class的newInstance,但这个方法是要求类必须有个无参的构造方法
- Class strCl = Class.forName("java.lang.String");
- Constructor strCon = strCl.getConstructor(StringBuilder.class);
- String str = (String)strCon.newInstance(new StringBuilder("sdf"));
- String strr = (String)strCl.newInstance();
- }
- private static void arraysAsListTest() {
- int[] a1 = new int[]{4,5,6};
- List l1 = Arrays.asList(a1); //因为a1不是object[]数组,不会用jdk1.4中的asList(Object[] objs)来调用
- //而是用jdk1.5中的asList(T... args)所以int[]当成了一个Object来传入,此
- //此List中只有一个元素,是int[]型的 Object。
- System.out.println(l1+"-----------"+l1.size()); //size为1,只有一个元素 打印的是:[[I@10b30a7]-----------1
- for(int i=0;i<l1.size();i++){
- System.out.print(l1.get(i)+" ,"); //[I@c17164 只有此元素
- }
- }
- /*
- * 总结:反射在调用带数组参数的方法时,一定要注意invork方法调用的情况,因为传入invork方法是一个数组的话,编译器会采用JDK1.4折方法特性将数组拆包,
- * 数组中每个元素将作为一个参数传入到相应方法中。这样肯定会与之前的getMethod中的数组.class有冲突,会报
- * java.lang.IllegalArgumentException: wrong number of arguments 参数数量不对了。
- * 为了解决此问题,有两种方法,一种是不让JDK1.4插手,那么就可将数组前面强转成Object,这样编译器会使用JDK1.5的,不会将数组拆包。
- * 如:m1.invoke(rd, (Object)new String[]{"abc","bcd"});
- * 另一种方法是,还是让JDK1.4编,那么外面再用一个数组包起,这个数组。这样JDK1.4遇到这个数组的数组,拆包后还是一样数组正好与getMethod中是一致的。
- * 如m1.invoke(rd, new Object[]{new String[]{"abc","bcd"}});
- *
- * */
- private static void reflectInvorkArrayDeom() {
- ReflectDemo rd = new ReflectDemo();
- Method m1 = null ;
- try{
- Class lc = rd.getClass();
- int[] intArr = new int[]{3,5};
- m1 = lc.getMethod("printIntArray", int[].class);
- m1.invoke(rd, intArr);
- String[] strArr = new String[]{"abc","bcd"};
- m1 = lc.getMethod("printStringArray", String[].class);
- //m1.invoke(rd, strArr); //这样调用是有问题的,因为在invork方法发现第二个参数是String[]数组
- //此时invork会用1.4的Object[],在1.4中第二个参数会先被拆包,拆成多个参数传与相应的方法中
- //但此时已经与getMethod中声明的String[].class有冲突了,所以会报IllegalArgumentException异常
- //java.lang.IllegalArgumentException: wrong number of arguments 参数数量不对
- //解决这个问题有两个方法,一是用jdk1.5提供的可变参数,那么为让编译器知道去调用1.5的,那么就要将数组变成一个对象。
- m1.invoke(rd, (Object)strArr);
- //另一个解决方法是,1.4的是不要个数组吗,给个数组它new Object[],这个数组里呢,再传入个String[]数组,这样编译器在使用1.4的时
- //先将数组拆包后,取去其中元素,正好是一个数组与上面的String[].class是吻合的。即可以调用了。
- m1.invoke(rd, new Object[]{strArr});
- Integer[] integerArr = new Integer[]{12,56};
- m1 = lc.getMethod("printIntegerArray", Integer[].class);
- //m1.invoke(rd, integerArr); 同时这样调用也是有问题的
- m1.invoke(rd, (Object)integerArr);
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- /**
- * printClass用于打印,运行时才知道来的类,具体哪些构造方法,哪些方法以及哪些变量
- * @param className
- */
- private static void printClass(String className) {
- ArrayList<String> al = new ArrayList<String>();
- Class clazz = null ;
- try {
- clazz = Class.forName(className);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("类名有问题,请重新输入");
- }
- String modifierName = Modifier.toString(clazz.getModifiers());
- StringBuilder sb = new StringBuilder();
- if(modifierName != null && modifierName.length()>0){
- sb.append(modifierName);
- }
- sb.append(" "+clazz.getName()+"{");
- sb.append(System.getProperty("line.separator"));
- printConstructors(clazz,sb);
- printMethods(clazz,sb);
- printFields(clazz,sb);
- sb.append(System.getProperty("line.separator"));
- sb.append("}");
- System.out.println(sb.toString());
- }
- /**
- * 打印运行时类的,变量。
- * @param clazz 运行时类的字节码
- * @param sb 打印的内容先用StringBuilder缓存下
- */
- private static void printFields(Class clazz, StringBuilder sb) {
- sb.append(System.getProperty("line.separator"));
- sb.append("\t");
- Field[] fields = clazz.getFields();
- for(Field field:fields){
- sb.append(Modifier.toString(field.getModifiers()));
- sb.append(field.getName());
- sb.append(System.getProperty("line.separator"));
- sb.append("\t");
- }
- }
- /**
- * 打印运行时类的方法。
- * @param clazz 运行时类的字节码
- * @param sb 打印的内容先用StringBuilder缓存下
- */
- private static void printMethods(Class clazz, StringBuilder sb) {
- sb.append(System.getProperty("line.separator"));
- sb.append("\t");
- Method[] methods = clazz.getMethods();
- for(Method method:methods){
- Class[] paraClasses = method.getParameterTypes();
- sb.append(Modifier.toString(method.getModifiers()));
- sb.append(" "+method.getName()+"(");
- for(Class paraClass: paraClasses){
- sb.append(paraClass.getName()+",");
- }
- if(null != paraClasses && 0 != paraClasses.length){
- sb.deleteCharAt(sb.length()-1);
- }
- sb.append(")");
- sb.append(System.getProperty("line.separator"));
- sb.append("\t");
- }
- }
- /**
- * 打印运行时类的构造方法。
- * @param clazz 运行时类的字节码
- * @param sb 打印的内容先用StringBuilder缓存下
- */
- private static void printConstructors(Class clazz,StringBuilder sb) {
- sb.append("\t");
- Constructor[] cons = clazz.getConstructors();
- for(Constructor con : cons){
- String conModifier = Modifier.toString(con.getModifiers());
- sb.append(conModifier);
- sb.append(" "+con.getName()+"(");
- Class[] types = con.getParameterTypes();
- for(Class type:types){
- sb.append(type.getName()+",");
- }
- if(null!=types && 0 !=types.length){
- sb.deleteCharAt(sb.length()-1); //删除最后一个逗号,字符
- }
- sb.append(")");
- sb.append(System.getProperty("line.separator"));
- sb.append("\t");
- }
- }
- /**
- * 测试反射运行时得到的方法及变量,如何调用,如何设置值。
- */
- private static void applyReflectChangeObjValDemo() {
- ReflectDemo rd = new ReflectDemo();
- Method m1 = null ;
- try{
- Class lc = rd.getClass();
- m1 = lc.getMethod("setName", String.class); //setName方法,有参数参数是String的。
- Boolean b = (Boolean)m1.invoke(rd, "four");
- System.out.println("---------------:"+rd.getName());
- rd.setSize(21);
- m1 = lc.getMethod("getSize");
- Integer size = (Integer)m1.invoke(rd); //getSize方法返回int,没有参数。
- System.out.println("Size大小::"+size);
- m1 = lc.getMethod("getPI");
- Double d = (Double)m1.invoke(null); //静态方法,无参数且返回为double,静态方法第一个参数肯定是null,第二参数若无参数则空起来即可。
- System.out.println("PI的值为::"+d.doubleValue());
- Field f = lc.getDeclaredField("pos"); //私有的成员变量,反射想拿到的话,必须用getDeclaredField
- f.setAccessible(true); //私有的成员变量,反射想访问的话,必须setAccessible为true
- int value = 88;
- f.set(rd, value);
- System.out.println("------pos为---"+rd.getPos());
- f.set(rd, size);
- System.out.println("-----恢复pos后为---"+rd.getPos());
- rd.setName("sdfasdf");
- f = lc.getField("name");
- System.out.println("------------name的值为-------::"+f.get(rd));
- f.set(rd, "zhangsan");
- System.out.println("------------name的值为-------::"+f.get(rd));
- }catch(Exception e){
- e.printStackTrace();
- System.out.println("异常了------------------------------------------");
- }
- }
- /**
- * 理解,反射作用于数组时内容是采用什么机制的。需要注意JDK1.4与JDK1.5 反射数组的不同。
- * @param args
- */
- public static void printIntArray(int[] args){
- System.out.println("------printIntArray invorked---------");
- for(int arg:args){
- System.out.println(arg);
- }
- }
- public static void printStringArray(String[] args){
- System.out.println("------printStringArray invorked---------");
- for(String arg:args){
- System.out.println(arg);
- }
- }
- public static void printIntegerArray(Integer... args){
- System.out.println("------printIntegerArray invorked---------");
- for(Integer arg:args){
- System.out.println(arg);
- }
- }
- public int getPos() {
- return pos;
- }
- public void setPos(int pos) {
- this.pos = pos;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getSize() {
- return size;
- }
- public void setSize(int size) {
- this.size = size;
- }
- public ArrayList<String> getAlist() {
- return alist;
- }
- public void setAlist(ArrayList<String> alist) {
- this.alist = alist;
- }
- }
需要注意:反射在调用带数组参数的方法时,一定要注意invork方法调用的情况,因为传入invork方法是一个数组的话,编译器会采用JDK1.4折方法特性将数组拆包,数组中每个元素将作为一个参数传入到相应方法中。这样肯定会与之前的getMethod中的数组.class有冲突,会报java.lang.IllegalArgumentException: wrong number of arguments 参数数量不对了。
为了解决此问题,有两种方法,
一种是不让JDK1.4插手,那么就可将数组前面强转成Object,这样编译器会使用JDK1.5的,不会将数组拆包。如:m1.invoke(rd, (Object)new String[]{"abc","bcd"});
另一种方法是,还是让JDK1.4编,那么外面再用一个数组包起,这个数组。这样JDK1.4遇到这个数组的数组,拆包后还是一样数组正好与getMethod中是一致的。如m1.invoke(rd, new Object[]{new String[]{"abc","bcd"}});
五、内省
内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作,JavaBean是一种特殊的Java类,其中的某些方法符合某种命名规则,如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。通常的JavaBean是满足有setter与getter方法,且方法名去掉set前缀后,若第二个字母是小写,则属性名为第一个字母小写后的方法名后缀如setXxx 则 属性名为xxx。而若第二个字母也是小写,则属性名是去掉前缀后所有的后缀,如setCPU,则CPU为属性名。这点在后期学习的el表代式都有应用。
操作JavaBean另一个方法的工具包是BeanUtils,它需要loggin包的支持。
BeanUtils 官方下载地址:http://commons.apache.org/beanutils/
Logging 官方下载地址:http://commons.apache.org/logging/
具体示例代码:
- package com.enhance;
- import java.beans.BeanInfo;
- import java.beans.IntrospectionException;
- import java.beans.Introspector;
- import java.beans.PropertyDescriptor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.HashMap;
- import java.util.Map;
- import org.apache.commons.beanutils.BeanUtils;
- import org.apache.commons.beanutils.PropertyUtils;
- /**
- * 内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作,JavaBean是一种特殊的Java类,
- * 其中的某些方法符合某种命名规则,如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。
- * 通常的JavaBean是满足有setter与getter方法,且方法名去掉set前缀后,若第二个字母是小写,则属性名为第一个字母小写后的方法名后缀
- * 如setXxx 则 属性名为xxx。而若第二个字母也是小写,则属性名是去掉前缀后所有的后缀,如setCPU,则CPU为属性名。这点在后期学习的el表代式都有应用。
- *
- * */
- public class IntroSpectorDemo {
- public static void main(String[] args){
- ReflectDemo rfd = new ReflectDemo();
- rfd.setName("zhangsan");
- rfd.setPos(0);
- rfd.setSize(3);
- ArrayList<String> al = new ArrayList<String>();
- al.add("abc");
- al.add("bcd");
- rfd.setAlist(al);
- propertyDescriptorDemo(rfd);
- //beanInfo无法一次的得到某一个属性的PropertyDescriptor。
- //BeanInfo需要通过Introspector来获取
- beanInfoDemo(rfd);
- beanUtilsDemo(rfd);
- }
- private static void beanUtilsDemo(ReflectDemo rfd) {
- String name = null;
- PropertyDescriptor pd = null ;
- try{
- //通过BeanUtils的getProperty方法,获得属性的值
- name = BeanUtils.getProperty(rfd, "name");
- System.out.println(name);
- //通过BeanUtils的setProperty方法,获得属性的值
- BeanUtils.setProperty(rfd, "name", "wangwu");
- System.out.println(rfd.getName());
- //通过BeanUtils反射,若属性是List集合则只能得到集合中的第一个元素
- name = BeanUtils.getProperty(rfd, "alist");
- System.out.println(name);
- //但Collection等集合中的元素可以通过getArrayPropertys来获取元素中每个值
- String[] names = BeanUtils.getArrayProperty(rfd, "alist");
- System.out.println("------alist-------::"+Arrays.toString(names));
- //用PropertyDescriptor来拿属性的值
- pd = new PropertyDescriptor("alist",rfd.getClass());
- Class propClazz = pd.getPropertyType();
- System.out.println("属性的类型为::"+propClazz.getName());
- Method readMethod = pd.getReadMethod();
- Object obj = readMethod.invoke(rfd);
- System.out.println(obj.getClass().isArray()); //false,不是数组
- ArrayList<String> alist = (ArrayList<String>)obj;
- System.out.println(alist);
- System.out.println("----------------隔开,下面测试用Map的方式来玩转BeanUtils---------------");
- Map<String,Integer> map = new HashMap<String,Integer>();
- map.put("size", 88);
- map.put("age", 28);
- //BeanUtils可以直接操作Map对象,来获取其中的键值对呢
- String sizeStr = BeanUtils.getProperty(map, "size");
- String ageStr = BeanUtils.getProperty(map, "age");
- System.out.println("[size="+sizeStr+",age="+ageStr+"]");
- //还有个相似的类就是PropertyUtils,它与BeanUtils的区别是,getProperty返回的是属性的实际类型,
- //setProperty时,也就将实现类型值传下其中,而不像BeanUtils都是字符串,返回的是字符串,set设置时也是字符串。
- Integer age = (Integer)PropertyUtils.getProperty(map, "age");
- System.out.println(age);
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- private static void beanInfoDemo(ReflectDemo rfd) {
- BeanInfo bi = null;
- String propName = "name";
- try {
- bi = Introspector.getBeanInfo(rfd.getClass());
- PropertyDescriptor[] pds = bi.getPropertyDescriptors();
- for(PropertyDescriptor pd:pds){
- if(propName.equals(pd.getName())){
- System.out.println("beaninfo得到PropertyDescriptors数组后,再迭代PropertyDescriptor,得到name的值::"+pd.getReadMethod().invoke(rfd));
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private static void propertyDescriptorDemo(ReflectDemo rfd) {
- //牛逼的PropertyDescriptor类,可以对符合JavaBean属性的方法进行操作呢,直接得到getReadMethod与getWriteMethod
- PropertyDescriptor pd = null ;
- try {
- pd = new PropertyDescriptor("name",rfd.getClass());
- Class propClazz = pd.getPropertyType();
- System.out.println("属性的类型为::"+propClazz.getName());
- Method readMethod = pd.getReadMethod();
- Method writeMethod = pd.getWriteMethod();
- //调用读方法,读取目前name中的值
- System.out.println("调用读方法,读name的值为::"+readMethod.invoke(rfd));
- //调用写方法,设置name中的值
- writeMethod.invoke(rfd, "lisi");
- System.out.println("调用写方法,将name的值改变后为::"+rfd.getName());
- } catch (IntrospectionException e) {
- e.printStackTrace();
- }catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
- }
- }