Java中的反射机制详解

作者: zhl 分类: JavaSE 发布时间: 2024-04-18 16:31

反射的简单demo

  1. 声明的原始类

class Cat {
    private String name = "猫猫";
    public int age = 10;

    public Cat(){}

    public Cat(String name) {
        this.name = name;
    }

    public void hi() {
        System.out.println("hi~ " + name);
    }
    public void hi() {
        System.out.println(name + " cry");
    }
}

2. src目录下的配置文件re.properties

    classfullpath=com.zhl.Cat
    method=hi

    3. 编写反射代码

    public class Reflection01 {
        public static void main(String[] args) throws Exception {
            // 加载配置文件中的信息
            Properties properties = new Properties();
            InputStream inputStream = ReflectionQuestion.class.getClassLoader().getResourceAsStream("re.properties");
            properties.load(inputStream);
            String classfullpath = properties.getProperty("classfullpath");
            String methodName = properties.getProperty("method");
    
            // 使用反射创建对象
            Class<?> aClass = Class.forName(classfullpath);
            Object o = aClass.newInstance();
            System.out.println(o.getClass());
    
            // 通过反射获取成员方法
            Method method = aClass.getMethod(methodName);
            method.invoke(o);
    
            // 通过反射获取成员变量(不能获取私有的)
            Field ageField = aClass.getField("age");
            System.out.println(ageField.get(o));
    
            // 通过反射获取构造器
            // 无参
            Constructor<?> constructor = aClass.getConstructor();
            System.out.println(constructor);
            // 有参
            Constructor<?> constructor2 = aClass.getConstructor(String.class);
            System.out.println(constructor2);
        }
    }


      反射机制和传统调用方法的对比

      public class Reflection02 {
          public static void main(String[] args) throws Exception {
              m1();
              m2();
          }
          // 传统方法:直接创建对象
          public static void m1() {
              Cat cat = new Cat();
              long begin = System.currentTimeMillis();
              for (int i = 0; i < 1000000; i++) {
                  cat.hi();
              }
              long end = System.currentTimeMillis();
              System.out.println("传统方案耗时:" + (end - begin) + "ms");
          }
      
          // 反射机制调用方法
          public static void m2() throws Exception {
              Class<?> aClass = Class.forName("com.zhl.Cat");
              // 无参构造
              Constructor<?> constructor = aClass.getConstructor();
              // 关闭访问检查的开关
              constructor.setAccessible(true);
              Object o = constructor.newInstance();
      
              Method method = aClass.getMethod("hi");
              method.setAccessible(true);
              long begin = System.currentTimeMillis();
              for (int i = 0; i < 1000000; i++) {
                  method.invoke(o);
              }
              long end = System.currentTimeMillis();
              System.out.println("使用反射方案耗时:" + (end - begin) + "ms");
          }
      }

      反射的优点和缺点:

      • 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活。
      • 缺点:使用反射基本是解释执行,对执行速度有影响。

      Class类

      Class类的对象不是new出来的,是系统创建的,在类加载阶段生成的。
      对于某个类的Class类对象,类只加载一次,在内存中只有一份。
      每个类的实例都会记得自己是由哪个 Class 实例所生成。

      • 已知一个类的全类名 ,且该类在类路径下,可通过Class类的静态方法forName()获取
        • 应用场景:通过配置文件读取类的全路径,加载类
          String className;
          Class clazz = Class.forName(className);
      • 若已知具体的类,通过 类的class获取,该方式最为安全可靠,程序性能最高
        • 应用场景:多用于参数传递,比如通过反射得到对应构造器对象
          Class cat = Cat.class;
      • 已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:
        • 应用场景:通过创建好的对象,获取Class对象
          Class clazz = 对象.getClass();
      • 其他方式
        获取类加载器,加载指定类 。
      ClassLoader loader = 类.class.getClassLoader();  
      Class<?> clazz = loader.loadClass("com.zhl.Cat");

      类加载

      • 基本说明
        反射机制是 java实现动态语言的关键,也就是通过反射实现类动态加载

        1. 静态加载:编译时加载相关的类,如果没有则报错。
          依赖性太强
        2. 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错。
          降低了依赖性
        3. 举例说明 (伪代码)
          String key = scan.next();
          switch(key) {
          case "1" :
          Dog dog = new Dog();
          dog.hi();
          break;
          case "2":
          Class<?> clazz = Class.forName("Person");
          Object o = clazz.newInstance();
          Method methodName = clazz.getMethod("hi");
          method.invoke(o);
          break;
          default:
          break;
          }

          注意这段代码中,并没有创建 DogPerson类(当前module中没有Dog或Person类),但是编译时,只会报 Dog 类没有创建,而Person类并没有监测出来是否被创建,只有真正被执行到这行代码时才会被加载,判断是否创建过Person类。


      • 类加载时机

        1. 当创建对象时(new)
        2. 当子类被加载时,父类也加载
        3. 调用类中的静态成员时
        4. 通过反射(动态加载)

          关于JVM的类加载可参考https://blog.csdn.net/m0_72406127/article/details/137058092?spm=1001.2014.3001.5501

      通过反射获取类的结构信息

      • java.lang.Class

        1. getName:获取全类名
        2. getSimpleName:获取简单类名
        3. getFields:获取所有public修饰的属性,包含本类以及父类的
        4. getDeclaredFields:获取本类中所有属性
        5. getMethods:获取所有public修饰的方法,包含本类以及父类的
        6. getDeclaredMethods:获取本类中所有方法
        7. getConstructors:获取所有public修饰的构造器,包含本类
        8. getDeclaredConstructors:获取本类中所有构造器
        9. getPackage:以Package形式返回包信息
        10. getSuperClass:以Class形式返回父类信息
        11. getInterfaces:以Class[]形式返回接口信息
        12. getAnnotations:以Annotation[]形式返回注解信息
      • java.lang.reflect.Field

        1. getModifiers: 以int形式返回修饰符
          默认修饰符是0,public 是 1,private 是 2,protected 是 4,static 是 8 ,final是 16,多个不同的修饰符可以相加
        2. getType:以Class形式返回类型
        3. getName:返回属性名
      • java.lang.reflect.Method

        1. getModifiers:以int 形式返回修饰符
        2. getName:返回方法名
        3. getReturnType:以Class形式获取 返回类型
        4. getParameterTypes:以Class[]返回参数类型数组
      • java.lang.reflect.Constructor

        1. getModifiers: 以int形式返回修饰符
        2. getName:返回构造器名(全类名)
        3. getParameterTypes:以Class[]返回参数类型数组

      通过反射创建对象

      • 方式一:调用类中的publi 修饰的无参构造器
      • 方式二:调用类中的指定构造器
      • Class类相关方法
        • newlnstance:调用类中的无参构造器,获取对应类的对象
        • getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
        • getDeclaredConstructor(Class...clazz):根据参数列表,获取对应的所有构造器 对象
      • Constructor类相关方法
        • setAccessible: 设为true,可以访问private修饰的构造器
        • newinstance(Object...obj): 调用构造器

      在构造器/方法/属性中使用 getDeclaredXxx方法可以获取本类中的所有构造器/方法/属性,
      通过setAccessible()方法,设置参数为true,关闭访问检查的开关,就可以直接访问和修改被 private 修饰的构造器/方法/属性。

      发表回复

      您的邮箱地址不会被公开。 必填项已用 * 标注