zoukankan      html  css  js  c++  java
  • 反射;获取类的字节码对象;Class类实例化对象;获取类中的公共构造方法/成员变量/成员方法并执行;暴力反射 (Java Day27)

    一,反射【是框架的灵魂】

    • 概述:在程序运行过程中可以对任意类型中的任意资源进行操作,这种动态获取或操作资源的行为就叫做反射。
    • 场景:在不知道操作类型的基础进行操作采用反射,只有在要运行的时候才知道类型。
    • 反射就是正向运行的逆向过程。
    1. 正向:编写源代码---> 编译成字节码文件---->jvm加载字节码文件启动运行--->创建对象或类名--->调用对应的方法和属性运行
    2. 反射:代码运行的过程中直接获取到字节码文件对象--->通过字节码文件对象获取对应的元素的对象--->通过元素对象调用对应的方法开始执行功能
    • 字节码文件对象:字节码文件在内存中的对象表现形式【java中使用一个Class类对字节码文件这种事物进行相关特征和行为的描述 Class的对象就是jvm加载字节码文件的体现】
    • 【元素:指的是描述成员变量、成员方法、构造方法特征和行为的类】
    • jvm加载.class 文件的处理机制:
    1. ​ jvm通过类加载器将字节码文件以对象的形式加载到内存中【在方法区中创建了一个Class类型的对象】,jvm加载之后对这个Class对象的内容进行分类管理【属性、成员方法,构造方法】,jvm分完类发现内容各自有各自的共同特征,把这些类别内容分别的进行描述形成对应的类型【属性对应的类型Filed方法对应的类 Method构造对应的Constract】jvm把管理着三个类的权限给Class对象来管理。
    • 反射对象操作的元素除了对应的类对象还有Filed对象 Method对象 Constract对象。
    • 元素的操作变成了自己的对象操作自己和所属的类对象没有关系,实现解耦合。
    • 执行的顺序:jvm加载字节码文件创建Class对象,Class对象去统筹属性、方法、构造各自类的对象来进行具体的操作。
    • ​ 比如:使用反射执行一个方法
    1. Class对象直接获取方法的对象,使用这个方法的对象调用对应的执行方法直接执行。
    2. Class:他就是字节码文件在内存中的虚拟体现【字节码文件在内存中的具体描述】每一个字节码文件都对应有一个Class对象而且是唯一的。
    • 反射的好处:
    1. 解耦合,利于编程
    2. 提高代码的维护性。【实现代码和维护的分离】
    • 使用反射技术的步骤:
    1. 获取Class对象【获取那个字节码文件的对象】
    2. 通过字节码文件对象获取要操作的元素的对象、
    3. 使用元素对象调用对应的方法来执行相关的功能

    二,获取类的字节码对象的三种方式

    1. 第一种方式:对象名称.getClass():直接获取对象对应类型的字节码文件对象
    2. 第二种方式:类名.class :直接获取到类型的字节码文件对象
    3. 第三种方式:Class.forName(String pathName) :  根据 pathName 来创建对应类型的字节码文件对象

    ​           说明:pathName:指一个类的全名称【全限定类名:包路径+类名】

    代码示例

    //定义一个Person类public class Person {
        String name;
    int age;
    public Person(String name) { super(); this.name = name; }

    public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person(int age) { super(); this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } //定义一个测试类public class Demo_Class { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //使用反射技术 得到Person类的字节码文件的对象 //第一种方式 对象法 Person p = new Person(); //clazz1 是class类的对象 [它person.class这个文件在内存中的对象形式] //class类是所有字节码文件内存对象对应的类型 [一个class的对象对应一个.class为后缀名的文件] //class的对象是内存中的字节码文件资源的体现,.class文件是磁盘中字节码资源的体现 //参考 file类 file类是所有磁盘文件在内存在的对象对应的类型 Class clazz1 = p.getClass(); //对象名.getClass()得到内存中字节码文件对象 //第二种方式 类名表示.class 文件 ;类名.class 就是字节码文件对象 Class clazz2 = Person.class; //第三种方式 使用全限定类名 [类字节码文件所在的包路径] 得到字节码文件对象 Class<?>clazz3 = Class.forName("com.ujiuye.demo.Person"); } }

    三,Class类实例化对象的方法

    • 字节码文件对象:指字节码文件在内存中对应的 Class类型 对象
    • 字节码文件实例对象:指字节码文件对应类的对象
    • newInstance():实例化字节码文件对应类型的具体实例对象

    ​ 属于Class类的方法:

    代码示例

    public class Demo_Class {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //使用反射技术 得到Person类的字节码文件的对象
        //对象法
        Person p = new Person();
        //clazz1 是class类的对象 [它person.class这个文件在内存中的对象形式]
        //class类是所有字节码文件内存对象对应的类型 [一个class的对象对应一个.class为后缀名的文件]
        //class的对象是内存中的字节码文件资源的体现,.class文件是磁盘中字节码资源的体现
        //参考 file类 file类是所有磁盘文件在内存在的对象对应的类型
        Class clazz1=p.getClass();  //对象名.getClass()得到内存中字节码文件对象
        //第二种方式 类名表示.class 文件 ;类名.class 就是字节码文件对象
        Class clazz2 =Person.class; 
        //第三种方式 使用全限定类名 [类字节码文件所在的包路径] 得到字节码文件对象
        Class<?>clazz3 =Class.forName("com.ujiuye.demo.Person");
        
        //通过字节码文件对象得到对应类的对象
        Person person= (Person)clazz3.newInstance();
        System.out.println(person);
    }
    }

    注意事项:反射在实例化对象的时候默认走的是空参构造,使用反射技术的时候保证操作的类必须提供空参构造

    四,榨汁机案例

    • 榨汁机拥有榨汁的功能【需要传入水果】
    • 水果有产出果汁的功能
    • 分析:
    1. ​ 榨汁机需要的水果【写榨汁机类:写一个方法榨汁 方法需要参数水果】
    2. ​ 水果出果汁【写水果接口 共享方法出果汁】
    3. ​ 设计几个具体的水果类,实现接口

    代码示例1:【使用new对象的方式】

    //定义一个方法
    public class FruitMachine {
        // 榨果汁
        public void getJuice(Fruit f) {
            f.flowJuice();
        }
    }

    //定义一个水果接口
    public interface Fruit { // 出果汁 void flowJuice(); }

    //定义一个苹果类实现接口
    public class Apple implements Fruit{ @Override public void flowJuice() { System.out.println("苹果汁"); } }

    //定义一个橘子类实现接口
    public class Orange implements Fruit{
        @Override
        public void flowJuice() {
            System.out.println("橘子汁");
        }
    }
    //定义测试类
    public class Test { public static void main(String[] args) { FruitMachine machine = new FruitMachine(); // 想喝苹果汁 // Apple apple = new Apple(); // machine.getJuice(apple); Orange orange = new Orange(); machine.getJuice(orange); } }
    • 总结:
    1. 开始喝的是苹果汁,如果想要喝橘子汁,只能把程序停下来,对代码进行修改修改后再重新运行程序。
    2. 修改程序,容易出错,有可能影响其他的地方,维护成本比较大

    代码示例2:【使用反射方式】


    import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader;
    public class Test { public static void main(String[] args) throws Exception { FruitMachine machine = new FruitMachine(); // 想喝苹果汁 // Apple apple = new Apple(); // machine.getJuice(apple); /*Orange orange = new Orange(); machine.getJuice(orange);*/ // 使用反射技术 // 首先使用熟悉的io流技术把文件中的内容读到 使用字符缓冲流一次读一行读到内容 BufferedReader br = new BufferedReader(new FileReader("a.properties"));
    //a.properties文件里面的内容是 com.ujiuye.demo.Apple;com.ujiuye.demo.Orange
    String name = br.readLine();// 是一个类的全限定类名
    // 使用反射技术得到对应类的实例化对象
    Class<?> clazz = Class.forName(name);
    Fruit f =(Fruit) clazz.newInstance();
    machine.getJuice(f);
    br.close(); //关流
    }
    }
    • 反射获取类中的公共构造方法并使用

    • 如何获取构造方法对象?
    1. getContructor(Class params ):根据参数的类型及个数返回对应的构造方法对象
    2. ​ 只能获取到公共 (public) 的构造方法
    • ​ 说明:
    1. ​ params:指多个 class 类型的参数
    2. ​ params:传参的时候传的是构造方法形参类型的字节码文件对象
    3. ​ getContructors():返回字节码文件对象中所用的构造方法对象
    • 如何控制构造方法创建类型对象
    1. ​ 构造方法作用:创建对象并初始化值
    • ​ 使用Constructor类中的方法:
    1. newInstance():创建构造对象所在的字节码文件对象对应类型的实例化对象【叫构造方法自己执行自己】

    代码示例


    import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays;
    public class Demo_Constractor { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //操作person类 先得到person的字节码文件对象 //有三种方式 Class<?> clazz = Class.forName("com.ujiuye.demo.Person"); //得到一个构造方法的对象 得到 满参构造 Constructor<?> c1 = clazz.getConstructor(String.class,int.class); Constructor<?> c2 = clazz.getConstructor(); System.out.println(c1); //public com.ujiuye.demo.Person(java.lang.String,int) //c1就是满参构造方法在内存中的一个对象形式 //想要获取类中所有的公共的构造方法对象 //person类里面4个构造方法的对象都罗列出来了 Constructor<?>[] contructors = clazz.getConstructors(); System.out.println(Arrays.toString(contructors)); //输出结果 //满参 public com.ujiuye.demo.Person(java.lang.String,int) //age public com.ujiuye.demo.Person(int) //name public com.ujiuye.demo.Person(java.lang.String) //空参 public com.ujiuye.demo.Person() //构造方法的作用是什么? 创建对象 给属性赋值 Person p1 = (Person)c1.newInstance("baobao",30); System.out.println(p1); //Person [name=baobao, age=30] Person p2 =(Person)c2.newInstance(); //因为c2是空参所有输出来是null值 System.out.println(p2); //Person [name=null, age=0] } }
    • <!--记住一句话,Class对象在调用方法时,方法的实参都是字节码文件对象-->
    • 反射获取类中的成员变量并使用

    • 反射如何获取公共权限成员变量【Field】对象?
    1. ​ getField(String name):根据给定的属性名称获取对应属性的对象
    2. ​ getFields():获取所有公共的属性对象
    3. ​ 只能获取到 public 修饰的属性对象
    • ​ 说明:
    1. ​ name:是指属性名【字段名】
    • 如何来获取属性对象里面的属性值?
    1. ​ 使用Field类中的方法:get(Object o):获取到指定对象的属性值
    • ​ 说明:
    1. ​ o:指定对象【getFiled的调用字节码对象的实例对象】
    • 如何设定属性值使用Field里面的方法:
    1. ​ set(Object o,Object V):给指定对象的指定属性对象赋值。
    • ​ 参数说明:
    1. ​ o:  指定对象【getFiled的调用字节码对象实例对象】
    2. ​ V:要赋值的新值
    • 使用前提:属性公共(public)权限修饰才可以用。

    代码示例


    import java.lang.reflect.Field; import java.util.Arrays;
    public class Demo_Filed { public static void main(String[] args) throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException { //得到对应的类的反射对象 [Preson] Class clazz= Person.class; //获取对应的属性对象,​ 只能获取到public修饰的属性对象 Field f1 = clazz.getField("name"); Field f2 = clazz.getField("age"); System.out.println(f1); //public java.lang.String com.ujiuye.demo.Person.name System.out.println(f2); //public int com.ujiuye.demo.Person.age //获取所有公共的属性 Field[] fields = clazz.getFields(); System.out.println(Arrays.toString(fields)); //输出:[public java.lang.String com.ujiuye.demo.Person.name, public int com.ujiuye.demo.Person.age] //属性的操作,获取值 设置值 Person person =(Person) clazz.newInstance(); String name =(String )f1.get(person); //name属性的对象对应的name属性属于哪个类对象 [Person类对象] System.out.println(name); //空参,没有赋值 null f1.set(person, "花花"); String name1 = (String)f1.get(person); System.out.println(name1); //花花 } }
    • 获取类中的成员方法并且执行

    • 如何成员方法对象:
    1. ​ 使用Class类中的方法: getMethod(String name, Class<?>... parameterTypes):根据给定的名称和参数类型获取对应的方法对象
    • ​ 参数说明:
    1. ​ name:方法名称
    2. ​ parameterTypes:方法形参类型的字节码对象
    3. ​ getMethods():获取所有的方法的对象
    4. 只能获取public修饰的方法
    • 如何运行得到的方法的效果:
    1. ​ 通过Method类中方法:invoke(Object obj, Object... args):使调用的方法对象的方法执行【让方法执行】
    • ​ 参数说明:
    1. ​ obj:指定对象【getMethod 的调用字节码对象实例对象】
    2. ​ args:方法需要的实参

    代码示例

    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    public class Demo_Method {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            // 获取person类中所有的方法,先得到person类字节码文件对象
            // 三种获取方式
            Class clazz = Person.class;
            Class<? extends Person> clazz2 = new Person().getClass();
            Class<?> clazz3 = Class.forName("com.ujiuye.demo.Person");
            // 获取想要操作方法的对象
            // getname是空参所有后面的参数可以不用
            Method m1 = clazz.getMethod("getName");
            // setName有参数,后面跟上参数类型.class
            Method m2 = clazz.getMethod("setName", String.class);
            System.out.println(m1); // public java.lang.String com.ujiuye.demo.Person.getName()
            System.out.println(m2); // public void com.ujiuye.demo.Person.setName(java.lang.String)
    
            Method[] methods = clazz.getMethods();
            // 遍历所有的方法
            for (Method method : methods) {
                System.out.println(method);
            }
            System.out.println(Arrays.toString(methods));
            // 输出的是所有的方法包括object父类的方法
            
            //方法用来干嘛?调用的 方法的对象得到了,想要方法对象对应的方法执行
            Person person = (Person) clazz.newInstance();
            String name = (String) m1.invoke(person);
            System.out.println(name); // 得到的值是null值
            
            //要执行哪个方法就用哪个方法对象去invoke
            m2.invoke(person, "花花");
            //获取值m1  get方法
            String name1=(String)m1.invoke(person);
            System.out.println(name1);  //花花
        }
    }

    五,暴力反射

    • 通过Class类中:getDeclaredXxx方法:可以获取类中的所有声明的成员(属性、方法、内部类),私有的成员也可以获取到。

    ​         Xxx代表 Field Constractor Method

    • 修改该对象的访问权限:通过AccessibleObject类中的
    1. ​ setAccessible(boolean b):改变对象的访问权限
    2. ​ isAccessible():判断对象的是否可以访问

    代码示例


    import java.lang.reflect.Field; import java.lang.reflect.Method;
    public class Demo_Declared { public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException { //得到反射对象 Class clazz = Person.class; /*Field name= clazz.getField("name"); Field age = clazz.getField("age"); System.out.println(name); //报错 System.out.println(age); //报错 */ //暴力反射方法,访问属性 //person类里面的属性默认的或私有的都可以获取到 Field name = clazz.getDeclaredField("name"); Field age = clazz.getDeclaredField("age"); //增加这一步,把权限修饰符给改了,100%保证暴力的访问到 name.setAccessible(true); System.out.println(name); //private java.lang.String com.ujiuye.demo.Person.name System.out.println(age); //private int com.ujiuye.demo.Person.age System.out.println(name.isAccessible()); //true //暴力反射方法,访问构造方法 Method getName = clazz.getDeclaredMethod("getName"); System.out.println(getName); //private java.lang.String com.ujiuye.demo.Person.getName() } }
    • 暴力反射的步骤:
    1. 获取字节码文件对象
    2. 采用getDeclaredXxx方法获取到操作的的对象【解决获取不到操作对象的问题】
    3. 使用setAccessible(true)方法改变操作对象访问的权限【解决了操作不了问题】
    4. 使用操作对象的内部功能进行具体操作。
    • 练习

    • //有如下集合
    • ​ ArrayList<Integer> list = new ArrayList<>();
    • ​ list.add(666);
    • //设计代码,将字符串类型的"abc",添加到上述带有Integer泛型的集合list中
    • 分析:正向编程的时候放不进去,因为list集合指定泛型为Integer,在编译的过程中这个指定的泛型是起作用的,他是要去对放进来的数据进行类型和范围的审核,符合泛型类型的要求可以添加,不符合直接报错不让添加。泛型只在编译时期起作用,一旦类开始运行的时候失去作用。类加载到方法区泛型就消失。我可以在集合字节码文件被加载到方法区以后去给list添加元素,就不会有泛型的约束,就可以添加进去了。
    • ​ 能做到运行时进行添加操作的只能是反射技术。采用反射来实现

    代码示例

    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    public class Test_02 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        ArrayList<Integer>list = new ArrayList<>();
        list.add(666);
        //list.add("abc"); //编译阶段泛型约束着添加的元素的数据类型
        //获取list集合字节码文件对象
        Class<? extends ArrayList> clazz = list.getClass();
        //通过反射对象获取 add方法的对象
        Method add = clazz.getDeclaredMethod("add", Object.class);
        //执行add方法添加"abc"字符串
        add.invoke(list, "abc");
        System.out.println(list); //[666, abc]
    }
    }
    • 注意:
    1. ​ 泛型的擦除:到了运行时期泛型就不起作用【相当于消失】
    2. ​ 泛型只在编译时期起作用叫做伪泛型
  • 相关阅读:
    window 操作
    idea使用
    安装zookeeper
    resource和autowired
    python浅见 (Python 3000)
    Tomcat服务器
    servlet
    事件是一种委托吗?什么是委托?什么是事件?
    int值类型的ToString()方法是否装箱
    抽象类,虚方法与普通类的区别
  • 原文地址:https://www.cnblogs.com/nastu/p/12605055.html
Copyright © 2011-2022 走看看