反射笔记


一、反射初接触

  1. 定义:将类中的各个组成部分封装为对象

    比如:

    • 构造器对象Constructor
    • 成员变量对象Field
    • 成员方法对象Method
  2. 好处
    1. 可以在程序运行过程中操作这些对象
    2. 解耦合,提高程序的可扩展性
  3. 获取类的Class对象的三种方式
    1. 【SOURCE 源代码阶段】 Class.forName("packageName.className");将字节码文件加载如内存,返回Class对象【路径是全路径】
    2. 【CLASS 类加载器已经执行】className.class; :已经编译,通过类名的属性class获取Class对象
    3. 【RUNTIME 运行期实例阶段】 Object.getClass(); 已经创建了对象,使用对象的方法getClass() 获取Class对象【这是Object类定义的方法】
  4. 实现
    1. //Demo01Reflect.java
      
      //1. 获取class对象第一种方式: Class.forName()  【已经编译】
           //注意!!参数为字符串类型,传递的是类的全路径
      Class personClass = Class.forName("com.rapjoee.day01.domain.Person");
      System.out.println("第一种方式:" + personClass);
      
      //2. 第二种方式:类名.class
      Class personClass1 = Person.class;
      System.out.println("第二种方式:" + personClass1);
      
      //3.第三种方式: 已经创建了对象,则使用对象的getClass()方法
      Person person0 = new Person();
      Class personClass2 = person0.getClass();
      System.out.println("第三种方式:" +  personClass2);
      
      //判断下几个class对象是否是同一个      【是同一个】
          //true
      System.out.println(personClass == personClass1);
      System.out.println(personClass2 == personClass1);

      image-20200307175343869

  5. 备注
    1. 同一个字节码文件(.class)在一次出现运行过程中,只会加载一次,不论哪种方式获得的Class对象是同一个【不同的字节码文件对应的Class对象是不同的
    2. 适用性
      1. 第一种方式多应用于配置文件。可以把类名定义在配置文件中,读取文件。传递的是字符串
      2. 第二种方式多用于参数的传递
      3. 第三种方式多用于已经获取对象后,对象的字节码文件获取

二、Class对象的功能之一 -- 获取类的成员变量们

  1. 通过类的Class对象可以获取其成员变量的Field对象
  2. 方法
    1. Field getField(String name):获取指定名称的public修饰的成员变量【此类方法不能获取public关键字之外修饰的成员变量】
    2. Field[] getFields():获取所有public修饰的成员变量
      获取后的操作:
             1. 设置值:`Object get(Object obj)`  返回该所表示的字段的值 Field ,指定的对象上。
             2. 获取值:`void set(Object obj, Object value)`  将指定对象参数上的此 Field对象表示的字段设置为指定的新值。
             3. `setAccessible(boolean x)` 参数为true则忽略访问修饰符的安全检查【暴力反射】
    3. Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
    4. Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
  3. 实现
    //获取Person类的Class对象
    Class personClass = Person.class;
    
            //获取成员变量的方法
    //Field getField(String name):获取指定名称的public修饰的成员变量
    Field a = personClass.getField("a");
    System.out.println("getField():" + a);
    
    //对获取得到的成员变量进行操作
    Person person0 = new Person();
    a.set(person0, "Smith");
    Object o = a.get(person0);
    System.out.println("get():" + o + "    person0:" + person0);
    
    //Field[] getFields()
    Field[] fields = personClass.getFields();
    System.out.println("getFields():" + Arrays.toString(fields));
    //对获取的成员变量值进行操作
    System.out.println("==========================================");
    
    //Field getDeclaredField(String name)
    Field[] declaredFields = personClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);
    }
    //Field[] getDeclaredFields()
    Field name = personClass.getDeclaredField("name");
    
    //忽略访问修饰符的安全检查,不然下面的代码会抛出异常【private修饰中】
        //暴力反射
    name.setAccessible(true);
    name.set(person0, "吐槽星人");
    System.out.println("name.get(person1):" + name.get(person0));
    System.out.println("name:" + name);
    
    //Person类的成员变量
    private String name;
    private int age;
    public String a;
    public String b;

    image-20200307175151179

三、Class对象的功能之二 -- 获取类的构造器们

  1. 通过类的Class对象获取类的构造器对象Constructor
  2. 方法
    1. Constructor<T> getConstructor(class<?>... parameterTypes)

      • 获取指定参数类型的被public关键字修饰的构造器

      • 比如获取public Person(String name, int age){}构造器的写法为

        • Constructor cons = personClass. getConstructor(String.class, int.class);
    2. Constructor<?>[] getConstructors():获取被public修饰的构造器们

    3. Constructor<T> getDeclaredConstructor(class<?>... parameterTypes) :获取所有的指定参数类型的构造器,不考虑访问控制修饰符

    4. Constructor<?>[] getDeclaredConstructors():获取所有的构造器,忽略修饰符

    5. 获取构造器之后可以进行的操作: 创建对象

      • T newInstance(Object... initargs)

        使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。

      • 这里我们知道,如果使用空参构造创建对象,可以使用newInstance()方法进行构造达到一样的效果

        Object o = constructor.newInstance("Smith", 123);
  3. 实现
    //获取一个Class对象
    Class personClass = Person.class;
    
    //    Constructor<T> getConstructor(类<?>... parameterTypes)
        //构造器的区别就是传递的参数不同,这里我们对该方法传递String 和 int
    Constructor constructor = personClass.getConstructor(String.class, int.class);
    System.out.println("constructor --> " + constructor);
    
    //获取构造器后,对其操作
        //创建对象
    //这里如果获取的 constructor 在第34行没有参数,则此处也没有参数,就是一个空参的构造器
    Object o = constructor.newInstance("Smith", 123);
    System.out.println("o --> " + o);
    
    //Person类的构造器
    private Person(String a) {
        this.a = a;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    image-20200307182158072

四、Class对象的功能之三 -- 获取类的类/包与方法们

  1. 通过类的Class对象获取类的构造器对象Method
  2. 方法
    1. Method getMethod(String name, class<?>... parameterTypes):获取指定名称与指定参数类型的方法
    2. Method[] getMethods()
    3. Method getDeclaredMethod(String name, class<?>... parameterTypes)
    4. Method[] getDeclaredMethods():获取所有的方法【包括自定义类中的与Object类定义的】
    5. 获取方法对象后可以执行的操作
      1. Object invoke(Object obj, Object... args):Method类的invoke方法可以用来执行方法。其参数:
        • Object obj:真实的对象
        • Object... args:参数列表
      2. String getName():获取方法的名称
    6. String getName() 获取的是类的全路径【com.rapjoee.day01.domain.Person 】
    7. packageName getPackage() 获取这个类的全包名【 package com.rapjoee.day01.domain 】
  3. 实现
    Class personClass = Person.class;
    //    Method getMethod(String name, class<?>... parameterTypes)
        //获取指定名称的方法,传递1.方法名 2. 参数列表可变参数【一个方法的要素:方法名、参数】
    //获取一个无参的方法Method对象
    Method speak = personClass.getMethod("speak");
    
    //对获取的Method对象进行操作【使用】
        //先准备一个对象
    Object person = personClass.newInstance();
    speak.invoke(person);
    System.out.println("=======================================");
    
    //获取到一个带参的Method对象
    //方法参数类型是String  ,所以获取时传递参数类型为String.class
    Method eat = personClass.getMethod("eat", String.class);
    
    //调用Method类的invoke方法执行方法eat,传递eat方法的参数
    eat.invoke(person, "Apple");
    
    //Person类的两个测试方法
    public void eat(String food) {
         System.out.println("The eat method is executed... --> " + food);
    }
    public void speak() {
         System.out.println("The speak method is executed...");
    }

    image-20200307181822116

五、反射小练习

  1. 需求:

    写一个"框架", 不改变该类的任意代码的前提下,可以帮我们创建任意类的对象,并且执行其中的任意的方法

  2. 思路
    1. 要求不改变代码的内容,可以把类等的信息放在配置文件夹中,其中配置类名与方法名等等
    2. 获取配置文件,拿到其中配置的内容
    3. 使用反射获取该类的Class对象、Method对象等等
    4. 利用构造器对象可以创建对象、利用方法对象可以执行方法等等
  3. 代码实现
    1. ReflectPractice.java

      //获取本类的类加载器 ClassLoader对象
      ClassLoader classLoader = ReflectPractice.class.getClassLoader();
      
      //找到配置文件,以输入流的形式返回
      InputStream proInputStream = classLoader.getResourceAsStream("pro.properties");
      //加载配置文件进入内存--创建Properties集合
      Properties prop = new Properties();
      try {
          //把配置文件信息装载进入内存中刚创建的集合中
          prop.load(proInputStream);
      
          //获取配置文件中的类名与方法名信息      
          //【Person类的eat方法,带一String参/无参】
          String className = prop.getProperty("className");
          String methodName = prop.getProperty("methodName");
      
          //使用反射机制获取拿到的类名的Class对象
          Class<?> cls = Class.forName(className);
          //获取方法对象
          Method methodEatWithFood = cls.getMethod(methodName, String.class);
          Method methodWithoutFood = cls.getMethod(methodName);
      
          //获取一个带int和String参数的构造器对象
          Constructor<?> constructor = cls.getConstructor(String.class, int.class);
          //利用上面这个构造器创建一个带参对象,可以打印下其属性
          Object obj = constructor.newInstance("地星人", 33);
          if (obj instanceof Person) {
              Person person = (Person) obj;
              System.out.println("person.getAge() : " + person.getAge() +
                      "\r\nperson.getName(): " + person.getName());
          }
          //执行方法,传递一个默认构造的对象
          Object o = cls.newInstance();
          methodWithoutFood.invoke(o);methodEatWithFood.invoke(o, "香蕉");
      } catch (IOException |
               ClassNotFoundException |
               NoSuchMethodException |
               InstantiationException |
               IllegalAccessException |
               InvocationTargetException e) {
          e.printStackTrace();
      }finally {
          //输入流使用完毕释放资源
          try {
              proInputStream.close();
          } catch (IOException e) {
              e.printStackTrace();}}
    2. Person.java【com.rapjoee.day01.domain.Person】

      public void eat(String food) {
          System.out.println("带参 eat 方法执行... --> " + food);}
      
      public void eat() {
          System.out.println("无参 eat 方法执行 --> no food!!");}
      
      public void speak() {
          System.out.println("The speak method is executed...");}
    3. 配置文件pro.properties

      className = com.rapjoee.day01.domain.Person
      methodName = eat
  4. 备注

    后期需要改动不用动大规模的代码,改动配置文件即可