Java反射机制详解

JAVA反射知识复习

1. 反射机制

  1. 场景引入(为什么需要反射?)

    如果我们需要根据一个配置文件去执行对应的对象里面的方法或者获取到属性等操作,传统的方式是不能实现的

    • 传统方式

      image-20240302174815936

      • 在文件re.properties里面创建以下配置文件

        classPath = Day01.com.way.Cat
        method = run
        
      • 然后创建一个Cat类

        package Day01.com.way;
        
        /**
         * 创建一个猫类
         */
        public class Cat {
            private String name = "小花";
        
            public void hello () {
                System.out.println("早上好" + name);
            }
        
            public void run() {
                System.out.println("跑步" + name);
            }
        
        }
        
        
      • 接着使用传统方式调用 + 反射机制调用

        package Day01;
        
        import Day01.com.way.Cat;
        
        import java.io.FileInputStream;
        import java.lang.reflect.Method;
        import java.util.Properties;
        
        /**
         * 要求:
         *  1. 根据配置文件指定的信息(re.properties),创建对象并调用方法   classPath = ....     method = hello   [通过外部的配置文件,在不修改源码的情况下,来控制程序的执行]  开闭原则
         */
        public class Demo01 {
            public static void main(String[] args) throws Exception {
                // 01. 使用传统方式执行  这样肯定能实现(但是这里没有根据配置文件来执行)
        //        Cat cat = new Cat();
        //        cat.hello();
        
                // 02. 可以尝试进行文件读取的方式来实现
                Properties properties = new Properties();  // 使用properties类来读取文件
                properties.load(new FileInputStream("D:\\学习资料\\JAVA 基础回顾 代码测试区\\Reflection\\Charpter01\\src\\main\\java\\Day01\\re.properties"));
                String classPath = properties.get("classPath").toString();
                String methodPath = properties.get("method").toString();
                System.out.println(methodPath);
        //        new classPath();   // 这里是不可以执行的因为classPath是一个字符串的数据  new后面只能是一个类型   所以该方法行不通
        
                // 03. 反射机制解决
                    // 加载class对象
                Class<?> clazz = Class.forName(classPath);
                    // 将clazz对象实例化可以得到对象的com.way.Cat的实例
                Object o = clazz.newInstance();  // 这里就是运行对象Cat
                    // 通过clazz得到加载的类的方法对象
                Method method = clazz.getMethod(methodPath);
                    // 最后通过方法.invoke(对象)  实现方法的调用
                method.invoke(o);
        
            }
        }
        
        

        运行截图

        image-20240302175051072

        【这里我们可以根据配置文件的method的值的更改 hi 或者 run来实现不修改Cat类源码的情况下,来调用cat类里面的方法,这就是反射的应用,同时满足开闭原则】

  2. 什么是反射:

    • 一个类加载完成之后会在堆中产生一个Class类型的对象(一个类有且只有一个class对像存储在堆中),这个Class类型对象包含了类的完整的结构信息,通过这个对象得到类的详细的结构(成员变量,成员方法,构造器等)。这个Class类型的对象就像一面镜子,反射了这个类的所有的结构。【反射】

    • 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,成员方法,构造器等),并能操作对像的属性以及方法。【反射在设计模式和框架底层都得到了大量的应用】

      老韩的原理图

      image-20240302195446426

    • 反射机制的作用

      • 判断任意一个对象所属的类
      • 构造任意一个类的对象
      • 得到和调用任意一个类所具有的成员变量和方法
      • 生成动态代理
  3. 反射相关的主要的类

    • Class类, 表示某个类加载之后在堆中的对象

      // class对象
      Class<?> clazz = Class.forName(classPath);
      
    • Method类的方法,该对象表示某个类的方法

      Method method = clazz.getMethod(methodPath);
      
    • Field类的成员变量,该对象表示某个类的成员变量

      Field age = clazz.getField("age");
      // 这里必须反过来使用才能获取到年龄   field.get(cat实例化对象[class对象.newInstance()])
      System.out.println(age.get(o) + "岁");
      
    • Constructor类的构造方法,表示某个类的构造器

      Constructor<?> constructor = clazz.getConstructor();
      
  4. 反射的优缺点:

    • 优点:
      • 可以动态的创建和使用对象,同时它也是框架底层的核心,使用灵活;如果没有反射机制,框架技术就失去了底层支撑
    • 缺点:
      • 使用反射基本是解释执行,对执行速度有影响(可以将访问检查关闭能稍微提升访问速度)

2. 反射获取类的结构信息(应用)

  1. 反射创建私有的对象实例

    场景:如果我们需要访问一个私有(private)的构造器,并创建其实例,这是我们应该怎么做?

    • 解决办法,使用java的反射机制(在反射面前,一切都是纸老虎)

      // 初始化一个类,里面包含三种构造器
      class User {
          private int age = 10;
          private String name = "王五";
          
          // 01. 无参数的构造
          public User() {}
          
          // 02. 有参数的构造
          public User(String name, int age) {
              this.name = name;
              this.age = age;
          }
          
          // 03. 有参数的私有构造
          private User(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
    • 补充知识:

      • Class类的相关方法:

        newInstance: 调用无参构造器,获取类对象

        getConstructor(class…clazz): 根据参数列表,获取对应的public构造器对象

        getDecalaredConstructor(class…clazz): 根据参数列表获取所有的构造器对象

      • Constructor类相关方法:

        setAccessible: 爆破

        newInstance(Object…obj): 调用构造器

    • 调用上面的构造器

      void main() throws Exception{
          // 01. 先获取到User类的class对象
          Class<?> userClass = Class.forName("com...User");
          // 02. 通过无参构造器创建实例
          Object o = userClass.newInstance();
      	sout(o);
          // 03. 通过有参构造器创建实例 (先获取到构造器,然后调用构造器的实例化方法且传入参数)
          Constructor<?> constructor = userClass.getConstructor(String.class, int.class);
          Object zs = constructor.newInstance("张三", 20);
          sout(zs);
          // 04. 通过私有的构造器创建实例 (先得到私有的构造器, 然后再设置爆破,最后调用构造器的实例化方法且传入参数)
          Constructor<?> constructor1 = userClass.getDecalaredConstructor(String.class, int.class);
          constructor1.setAccesssible(true); // 爆破
          Object zl = constructor1.newInstance("赵六", 22);
      	sout(zl);
      }
      
  2. 利用反射操作成员变量(属性)

    • 创建一个User类

      // 初始化一个类,里面包含公共的和私有的成员变量(属性)
      class User {
          public int age;
          private static String name;
          
          // 01. 无参数的构造
          public User() {}
          
          // 02. 有参数的构造
          public User(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
    • 使用反射进行属性的操作

      void main() throws Exception{
          // 01. 先获取到user类的class对象
          Class<?> userClass = class.forName("...User");
          // 02. 创建对象
          Object user = userClass.newInstance();
      	// 03. 使用反射的到age属性对象
          Field age = userClass.getField("age");
          // 04. 通过反射来操作属性
      	age.set(user, 10);
          
          // 05. 使用反射操作私有的name属性对象
          Field name = userClass.getDeclareField("name");
          // 06. 必须设置爆破,因为name属性是私有的
          name.setAccessible(true); 
          // 因为name是static类型,所以可以设置为null
          name.set(null, "晓晓");   name.set(user, "晓晓");  // 设置属性值
          sout(name.get(user)); sout(name.get(null));   // 获取属性值
      }
      
    • 补充知识:

      • 根据属性名获取Field对象

        Field f = clazz对象.getDeclaredField(属性名);

      • 爆破

        f.setAccessiable(true);

      • 访问

        f.set(o, 值); 设置属性值,o表示对象

        sout(f.get(o)); 获取属性值,o表示对象

      • 如果是静态属性set和get的对象o可以设置为null

  3. 使用反射操作操作成员方法

    • 创建一个Boss类里面包含静态的私有方法和共有方法

      package Day01.com.way;
      
      import java.io.Serializable;
      
      public class Boss implements Serializable {
          public String name;
          private static int age;
      
          // 私有的方法
          private static String say(String name, int age) {
              return "姓名:" + name + ", " + "年龄:" + age;
          }
      
          public void say(String name) {
              System.out.println(name + "总说:开工大吉!");
          }
      
          public Boss() {}
      
      }
      
      
    • 创建一个测试类通过反射的方式访问里面的私有方法

      package Day01;
      
      import java.lang.reflect.Method;
      
      /**
       * 通过反射操作boss类里面的方法
       */
      public class Demo02 {
          public static void main(String[] args) throws Exception {
              // 01. 获取到class类对象
              Class<?> clazz = Class.forName("Day01.com.way.Boss");
              // 02. 创建反射对象
              Object o = clazz.newInstance();
      //        System.out.println(o);
              // 03. 调用对象里面的方法
              Method sayPublicMethod = clazz.getMethod("say", String.class);
              sayPublicMethod.invoke(o, "刘");
      
              // 04. 调用对象里面的静态方法
              Method say = clazz.getDeclaredMethod("say", String.class, int.class);
              // 这里是静态的必须将检查权限关闭
              say.setAccessible(true);
              System.out.println(say.invoke(o, "赵四", 20));
              System.out.println(say.invoke(null, "赵四", 20));
          }
      }
      
      
  4. 小练习:使用反射操作file类,并创建一个text.txt文件

    实现步骤:

    1. 首先使用反射获取到class类对象
    2. 然后获取到file类对象的构造器
    3. 接着创建一个对象 file类型同时调用类对象的createNewFile方法
    4. 最后使用方法.invoke(file类对象) 实现文件的创建
    public class Demo03 {
        public static void main(String[] args) throws Exception {
            // 01. 获取file类对象
            Class<?> clazz = Class.forName("java.io.File");
            // 02. 获取所有的构造器对象
            Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
            for (Constructor<?> declaredConstructor : declaredConstructors) {
                System.out.println(declaredConstructor);
            }
            // 03.  得到指定的构造器  public java.io.File(java.lang.String)
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class);
            String filePath = "d:\\text.txt";
            // 04. 创建了一个file对象
            Object fileObj = declaredConstructor.newInstance(filePath);
            // 05. 拿到文件创建的方法
            Method fileCreate = clazz.getMethod("createNewFile");
            fileCreate.invoke(fileObj);
    
            System.out.println("创建成功");
        }
    }
    

    如图所示
    image-20240303213806296

  5. 总结:

    1. 反射可以动态的创建和使用对象
    2. 对于一个私有的构造器、属性或者方法能够访问吗?当然可以使用反射(爆破)的机制进行访问(反射面前一切都是纸老虎)
  • 33
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值