一、反射的介绍
JAVA反射机制是在运行状态中,能够获取任意一个类的所有属性和方法,对于任意一个对象,都能够调用它的任意
一个方法。这种动态获取的以及动态调用对象的方法的功能称为java语言的反射机制。JAVA编译时是先获取到类,然
后才是类里边的属性和方法,而反射则和编译相反,他是先获取类里边的对象和方法然后在告诉他是哪个类里的。简单
来说, 就可以把.class文件比做动物的尸体, 而反射技术就是对尸体的一种解剖.通过反射技术, 我们可以拿到该字节码
文件中所有的东西, 例如成员变量, 成员方法, 构造方法, 而且还包括私有。想要反射首先要获取到程序的“尸体”也就
是.class文件。
二、字节码文件的获取
获取字节码对象有3种方式:
1、类名.class - 这是一个静态的属性, 只要知道类名, 就可以获取
2、对象名.getClass() - Object类里的getClass()方法,对象已经存在的情况下, 可以使用这种方式
3、Claire.forName("类的全类名(包名+类名)") - 通过Class类里的静态方法forName来获取节码对象
举例:
1 public static void main(String[] args) throws ClassNotFoundException { 2 // 通过Object的getClass()方法获取,必须要有对象 3 Student s = new Student(); 4 Class clazz = s.getClass(); 5 // 通过类名获取字节码对象 6 Class clazz2 = Student.class; 7 // 通过Class类里的静态方法forName来获取节码对象 8 Class clazz3 = Class.forName("com.fanshe.Student"); 9 System.out.println(clazz == clazz2); 10 System.out.println(clazz == clazz3); 11 System.out.println(clazz); 12 13 }
字节码文件其实就是描述.class文件的对象。
三、对构造方法的操作
*通过反射获取公有构造方法的两种方式:
1、返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法,可以获取无参构造也可以
根据传入的类型来匹配对应的构造方法:getConstructor(Class>... parameterTypes)
2、返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法:
getConstructors()
3、创建此 Class 对象所表示的类的一个新实例:
newInstance()
*暴力获取(可以获取全部权限的):
1、返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法:
getDeclaredConstructor(Class>... parameterTypes)
2、返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法:
getDeclaredConstructors()
举例:
1 public static void main(String[] args) throws ReflectiveOperationException { 2 // Class.forName()获取字节码对象 3 Class> forName = Class.forName("com.fanshe.Student"); 4 // 获取所有公共构造方法 5 Constructor>[] constructors = forName.getConstructors(); 6 // 遍历 7 for (Constructor> constructor : constructors) { 8 // 打印结果 9 System.out.println(constructor); 10 } 11 System.out.println("--------------------------------------"); 12 // 暴力获取,可以获取所有的构造方法(包括私有的) 13 Constructor> c1 = forName.getDeclaredConstructor(); 14 c1.setAccessible(true); 15 System.out.println(c1); 16 //获取有参构造 17 Constructor> c2 = forName.getConstructor(String.class, int.class); 18 System.out.println(c2); 19 }
四、对成员变量和方法的操作
*公有成员变量获取方法:
1、返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段:
getField(String name) - 参数为要返回的变量名
2、返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段:
getFields() - 参数为要返回的变量名
*任意成员变量获取方法:
1、返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段:
getDeclaredField(String name) - 参数为要返回的变量名
2、返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段:
getDeclaredFields() - 参数为要返回的变量名
*获取公有的方法:
1、返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法:
getMethod(String name, Class>... parameterTypes) - name 方法名、parameterTypes 参数列表
2、返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口
声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法:
getMethods()
*暴力获取方法:
1、 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法:
getDeclaredMethod(String name, Class>... parameterTypes) - name 方法名、parameterTypes
参数列表
2、返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、
默认(包)访问和私有方法,但不包括继承的方法:
getDeclaredMethods()
需要注意的是想要获取私有的变量或者方法时应使用AccessibleObject类里的setAccessible(boolean flag)方法 - 参数
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问
检查。
Constructor, Field, Method都是AccessibleObject的子类所以可以直接使用父类的setAccessible(boolean flag)方法。
**通过反射获得变量的流程:
1. 通过反射获取该类的字节码对象
2. 创建该类对象
3. 获取该类中需要操作的字段(成员变量)
4. 通过字段对象中的方法修改属性值
**通过反射执行方法的流程
1. 通过反射获取该类的字节码对象
2. 创建该类对象
3. 调用getMethod方法获取Method对象, 方法形参接受方法的名字
4. 调用Method方法中的invoke()将方法运行
举例:
*被反射的学生01类
1 public class Student01 { 2 public String name; 3 private int age; 4 5 public Student01() { 6 super(); 7 // TODO Auto-generated constructor stub 8 } 9 10 public Student01(String name, int age) { 11 super(); 12 this.name = name; 13 this.age = age; 14 } 15 16 public void name() { 17 System.out.println("测试"); 18 19 } 20 21 public String getName() { 22 return name; 23 } 24 25 public void setName(String name) { 26 this.name = name; 27 } 28 29 public int getAge() { 30 return age; 31 } 32 33 public void setAge(int age) { 34 this.age = age; 35 } 36 37 private void mane1() { 38 System.out.println("这是个萌萌哒私有的"); 39 40 } 41 42 @Override 43 public String toString() { 44 return "Student01 [name=" + name + ", age=" + age + "]"; 45 } 46 47 }
*对学生01类进行反射
1 public static void main(String[] args) throws ReflectiveOperationException { 2 // 获取字节码对象 3 Class> clazz = Class.forName("com.fanshe.Student01"); 4 // 创建该类的对象 5 Object stu = clazz.newInstance(); 6 // System.out.println(stu); 7 // 获取学生类的name变量 8 Field f1 = clazz.getField("name"); 9 /* 10 * set将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 11 * 为stu对象里的name变量赋值 12 */ 13 f1.set(stu, "李四"); 14 // get()返回指定对象上此 Field 表示的字段的值。 15 Object name = f1.get(stu); 16 // 暴力获取age字段 17 Field f2 = clazz.getDeclaredField("age"); 18 System.out.println(f2); 19 // 让jvm不检查权限 20 f2.setAccessible(true); 21 // 为其赋值 22 f2.set(stu, 24); 23 // 获取stu对象的f2字段的值 24 Object age = f2.get(stu); 25 System.out.println(name); 26 System.out.println(age); 27 // 公有无参无返回值,name() 28 Method method = clazz.getMethod("name"); 29 // 使用Method类的invoke方法执行name方法 30 method.invoke(stu); 31 // 公有代参无返回值,参数为String类型 32 Method m2 = clazz.getMethod("setName", String.class); 33 // 执行stu对象的setName方法,传入参数 34 m2.invoke(stu, "李晨宇"); 35 // 公有无参有返回值 36 Method m3 = clazz.getMethod("getName"); 37 // 返回值为invoke 38 Object invoke = m3.invoke(stu); 39 System.out.println(invoke); 40 // 私有 41 Method m4 = clazz.getDeclaredMethod("mane1"); 42 // 让jvm不检查权限 43 m4.setAccessible(true); 44 // 执行stu对象的mane1方法 45 m4.invoke(stu); 46 }
需要注意的是,反射无参构造时被反射的类一定要有无参构造方法,默认生成的也算。
五、反射的应用
我们在开发的时候,由于要考虑到代码的重用性,就会用反射来处理一些问题。而JAVA的一些常用jar包和主流框架的配置
都用到了反射的原理,学习反射有助于我们对源码的阅读和理解。BeanUtils工具类(Apache开发的便于操作JavaBeen的工具类)
就用到了反射的方法。
*BeanUtils的部分实现:
1 public class MyBeanUtils { 2 //因为是工具类,不需要实例化。所以私有构造方法 3 private MyBeanUtils() { 4 super(); 5 // TODO Auto-generated constructor stub 6 } 7 8 /* 9 * 给对象中的属性赋值 传入类的对象类型,和要修改的属性的值不确定所以用Object类型,属性名用String类型 10 */ 11 public static void setProrerty(Object object, String name, Object values) 12 throws ReflectiveOperationException, SecurityException { 13 // 获取传入对象的字节码文件 14 Class clazz = object.getClass(); 15 // 根据传入的属性获取Field对象,因为不确定属性的权限,用的暴力反射 16 Field field = clazz.getDeclaredField(name); 17 // 让jvm不检查权限 18 field.setAccessible(true); 19 // 为object对象里的name属性赋值 20 field.set(object, values); 21 22 } 23 24 // 获取对象中的属性 25 public static String getProrerty(Object object, String name) 26 throws ReflectiveOperationException, SecurityException { 27 // 获取传入对象的字节码文件 28 Class clazz = object.getClass(); 29 // 根据传入的属性获取Field对象,因为不确定属性的权限,用的暴力反射 30 Field field = clazz.getDeclaredField(name); 31 // 让jvm不检查权限 32 field.setAccessible(true); 33 // 获取name属性的值 34 Object object2 = field.get(object); 35 // System.out.println(object); 36 // 将值返回 37 return object2.toString(); 38 } 39 40 // 给对象中的属性赋值(通过Map的方式),Map里key存的是属性名,value存的是要赋的值 41 public static void populat(Object object, Map map) throws ReflectiveOperationException, SecurityException { 42 // 获取传入对象的字节码文件 43 Class clazz = object.getClass(); 44 // 返回此集合中的key集合 45 Set keySet = map.keySet(); 46 // 遍历key 47 for (Object object2 : keySet) { 48 // 获得value值 49 Object value = map.get(object2); 50 try { 51 // 根据传入的key(属性)获取Field对象,因为不确定属性的权限,用的暴力反射 52 Field field = clazz.getDeclaredField(object2.toString()); 53 // 让jvm不检查权限 54 field.setAccessible(true); 55 // 赋值 56 field.set(object, value); 57 } catch (NoSuchFieldException e) { 58 // 出现异常,给出友好型提示 59 System.out.println("Mdzz,属性都记不住"); 60 } 61 } 62 } 63 }
*测试工具类:
1 public static void main(String[] args) throws RuntimeException, ReflectiveOperationException { 2 // 创建学生01对象 3 Student01 s1 = new Student01(); 4 // 使用MyBeanUtils工具类为学生01对象赋值 5 MyBeanUtils.setProrerty(s1, "name", "啦啦"); 6 MyBeanUtils.setProrerty(s1, "age", 15); 7 // 使用MyBeanUtils工具类为学生01对象取值 8 String name = MyBeanUtils.getProrerty(s1, "name"); 9 String age = MyBeanUtils.getProrerty(s1, "age"); 10 // 打印出来 11 System.out.println(name); 12 System.out.println(age); 13 System.out.println("------------------------------------ "); 14 // 创建HashMap作为数据源 15 HashMaphashMap = new HashMap<>(); 16 // 为HashMap赋值 17 hashMap.put("qqqqq", "大大的"); // 属性不存在会给出友好型提示 18 hashMap.put("name", "大大的"); 19 hashMap.put("age", 110); 20 // 使用MyBeanUtils工具类为学生01对象赋值 21 MyBeanUtils.populat(s1, hashMap); 22 System.out.println(s1); 23 }