day22-反射机制

反射机制

一、概念:

     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 });

    }

猜你喜欢

转载自www.cnblogs.com/zhiai007/p/9460078.html
今日推荐