反射机制
一、概念:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。简单说就是,对于一个我们只有在运行时才知道这个类(class)的名称时候,我们就可以使用反射机制来加载这个类,能够动态的反向获取它的属性和方法,并随之调用。
二、反射使用步骤
1.获得Class类的一个实例。(Class类的实例表示正在运行的Java应用程序的类和接口)。
方式一:Class clazz = 类名.class;
方式二:Class clazz = 实例.getClass();
方式三:Class clazz = Class.forName("类名全路径");
//方式一:类的实例来获取 Person person = new Person(); Class clazz = person.getClass(); //方式二:类名获取 Class clazz = Person.class; //方式三:全路径类名获取 Class clazz = Class.forName("com.qfedu.reflect.bean.Person");
2.获得类的构造器(最简单的无参构造器),构造实例
public static void main(String[] args) throws Exception { // TODO 反射获得构造器 得到具体实例 //方式一:类的实例来获取 // Person person = new Person(); // Class clazz = person.getClass(); //方式二:类名获取 // Class clazz = Person.class; //方式三:全路径类名获取 Class clazz = Class.forName("com.qfedu.reflect.bean.Person"); // 获得一个无参数的构造器 Constructor<Person> constructor = clazz.getConstructor(); // 通过newInstance()方法 获得一个具体的实例 Object obj = constructor.newInstance(); Person p = (Person) obj; } }
3.当构造器的参数不确定时候,想要获得对应指定的构造器与自己需要传递的参数匹配(反射的高级用法)
public static void main(String[] args) throws Exception { // TODO 反射的动态操作 获得对应需要的构造器 Class clazz = Person.class; // 获得所有public的构造器 Constructor[] constructors = clazz.getConstructors(); // 遍历所有的构造器,将每一个构造器的全部信息和对应参数存放到集合中 Map<String, Class[]> map = new LinkedHashMap<>(); for (Constructor con : constructors) { System.out.println(con); // 声明一个用来存放每一个构造器参数列表的数组.长度等于每一个构造器参数列表(getParameterTypes()方法)返回数组的长度 Class[] parameters = new Class[con.getParameterTypes().length]; int i = 0;// 控制角标的 // 遍历每一个构造器的参数列表,并存放到数组中 for (Class para : con.getParameterTypes()) { parameters[i++] = para; } // 将一个构造器和对应的参数列表数组添加进map // 直接输出con构造器对象,也是调用了toString()方法 map.put(con.toString(), parameters); } // 声明自己需要的构造器的参数类型 List<Class> reqList = new ArrayList<>(); reqList.add(String.class); reqList.add(int.class); String key = null; // 将自己需要的类型跟原本存好的集合中的进行匹配,全部一致,则找到了那个对应的构造函数 for (Map.Entry<String, Class[]> entry : map.entrySet()) { // 定义一个标记 boolean flag = true; // 为了防止数组下标越界,因为参数列表长度可能会大于你实际需要的参数个数 // 也有可能你实际需要的参数个数,大于你参数列表长度。所以取两者最小值 int min = Math.min(entry.getValue().length, reqList.size()); // 遍历匹配参数列表与实际需要的参数类型是否相同(个数,跟类型都要相同) for (int i = 0; i < min; i++) { if (!(entry.getValue()[i].getName().equals(reqList.get(i).getName()))) { flag = false; } } if (flag ) { key = entry.getKey(); break; } } // 根据参数列表获取指定的构造器 Constructor constructor = clazz.getDeclaredConstructor(map.get(key)); System.out.println(constructor); // 用指定的构造器产生一个对应的实例 Object obj = constructor.newInstance("张三",20); System.out.println(obj); }
4.反射破解单例模式。(单例反破解)
单例的类:
public class Boss { // 单例设计模式 饿汉式 不管你用不用 先给你创建一个对象 private boolean isOnline = true; public boolean isOnline() { return isOnline; } public void setOnline(boolean isOnline) { this.isOnline = isOnline; } // 构造函数私有化 private Boss() { } // 对外提供一个单例的访问方法 // 饿汉式 // private static Boss boss = new Boss(); // 懒汉式 private static Boss boss = null; public static Boss getBoss() { if (null == boss) boss = new Boss(); return boss; } }
反射破解单例:
public static void main(String[] args) throws Exception{ //TODO 反射破解单例模式,获得多个不同对象 Boss boss1 = Boss.getBoss(); System.out.println(boss1); // 获得Boss的Class实例 Class<?> clazz = Boss.class; // 获得带私有的构造器 Constructor<?> constructor = clazz.getDeclaredConstructor(); // 设置私有可以访问权限 constructor.setAccessible(true); // 通过构造器创造实例 Object boss2 = constructor.newInstance(); System.out.println(boss2); //此时获得到的是一个新的Boss实例 System.out.println(boss2 == boss1);//false 说明获得了两个不同的实例对象,破解成功 }
单例反破解:
public class Boss { // 单例设计模式 饿汉式 不管你用不用 先给你创建一个对象 private boolean isOnline = true; public boolean isOnline() { return isOnline; } public void setOnline(boolean isOnline) { this.isOnline = isOnline; } // 构造函数私有化 private Boss() { //当你使用反射来获得我的构造器,然后进行实例化新的对象时候,我先判断,是否我已经创建过对象了 //如果已经创建过了,那么直接抛出异常,程序gg if (boss != null) { throw new RuntimeException("兄弟,洗洗睡吧,休想破坏我的单例模式"); } } // 对外提供一个单例的访问方法 // 饿汉式 // private static Boss boss = new Boss(); // 懒汉式 private static Boss boss = null; public static Boss getBoss() { if (null == boss) boss = new Boss(); return boss; } }
道高一尺魔高一丈,还可以使用对象序列化和反序列化来破解单例,同时又单例设计可以防止你反序列化操作使用readResolve()方法。
public class Boss implements Serializable{ // 单例设计模式 饿汉式 不管你用不用 先给你创建一个对象 private boolean isOnline = true; public boolean isOnline() { return isOnline; } public void setOnline(boolean isOnline) { this.isOnline = isOnline; } // 构造函数私有化 private Boss() { //当你使用反射来获得我的构造器,然后进行实例化新的对象时候,我先判断,是否我已经创建过对象了 //如果已经创建过了,那么直接抛出异常,程序gg if (boss != null) { throw new RuntimeException("兄弟,洗洗睡吧,休想破坏我的单例模式"); } } // 对外提供一个单例的访问方法 // 饿汉式 // private static Boss boss = new Boss(); // 懒汉式 private static Boss boss = null; public static Boss getBoss() { if (null == boss) boss = new Boss(); return boss; } //防止反序列化,读取对象时候,直接返回当前我已经创建好的一个单例的对象,维护单例模式 public Object readResolve() { return boss; } }
5.获取字段属性
public static void main(String[] args) throws Exception { // TODO 反射操作属性 Class<Student> clazz = Student.class; //获得一个空参数构造器,用来实例化一个对象 Constructor<Student> constructor = clazz.getConstructor(); //获得对应的实例对象 Student student1 = constructor.newInstance(); Student student2 = constructor.newInstance(); //获得属性 Field nameField = clazz.getDeclaredField("name"); Field ageField = clazz.getDeclaredField("age"); //设置访问权限 nameField.setAccessible(true); ageField.setAccessible(true); //修改对象的属性值 作用在对应的对象实例上,第一个参数表示要修改哪一个对象的属性 nameField.set(student1, "张三"); ageField.set(student1, 19); //获得对应对象的属性值,作用在对应的对象实例上 System.out.println("nameField:"+nameField.get(student1));//张三 //student2 对象并没有给其属性赋值,所以此时获得的是0. System.out.println("ageField:"+ageField.get(student2));//0 System.out.println(student1.getName()+":"+student1.getAge());//19 }
6.反射获取方法,并执行
public static void main(String[] args) throws Exception { // TODO 反射获得类中的方法,并执行 Class<Student> clazz = Student.class; Constructor constructor = clazz.getConstructor(); // 获得数组的Class 时候的写法注意 Class intClass = Class.forName("[I"); // 参数为基本数据类型数组的写法为:左中括号 + 基本数据类型封装类的首个大写字母 Class.forName("[I"); // 参数为引用类型数组的写法为:左中括号 + L + 类的全路径 + 分号 Class.forName("[Ljava.lang.String;"); // 获得确定对应的方法,通过方法名和参数列表 Method method = clazz.getDeclaredMethod("show", String.class, int[].class); //获取方法的返回值类型 Type type = method.getGenericReturnType(); System.out.println(type);//void // 执行方法,传递参数 需要一个对象,运行方法的参数需要 method.invoke(constructor.newInstance(), "张三", new int[] { 1, 2, 3, 4 }); }