Java 基础-反射

133 阅读8分钟

概述

反射符合设计模式的 OCP 原则(开闭原则:不修改源码,开扩内容)

  • 反射机制可以完成的事情
    1. 在运行时判断一个对象的所属类
    2. 在运行时构造任意一个类的对象
    3. 在运行时得到任意一个类所具有的成员变量和方法
    4. 在运行时调用任意一个对象的成员变量和方法
    5. 生成动态代理

快速入门

    @Test
    public void testDemo() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {

        // ReflectionExample.pojo.Cat
        // 加载类,返回 Class类型的对象
        Class cls = Class.forName("ReflectionExample.pojo.Cat");
        Object o = cls.newInstance();
        System.out.println("运行类型"+ o);
        // 通过 cls 得到你加载的类 ReflectionExample.pojo.Cat 的 hi 方法对象
        // 在反射中,可以把方法视为对象(万物皆对象)
        Method methodHi = cls.getMethod("hi");
        // 通过 methodHi 调用方法:通过方法对象实现调用方法
        methodHi.invoke(o);

        // 获取字段
        Field nameField = cls.getField("name");
        System.out.println(nameField.get(o));
        // getField 不能获取私有属性
        // Field age = cls.getField("age");

        // 获取构造器
        Constructor constructor = cls.getConstructor();
        System.out.println(constructor);
        Constructor constructor1 = cls.getConstructor(String.class,int.class);

        System.out.println(constructor1);
        Object o2 = constructor1.newInstance("小黑", 1);
        System.out.println(o2);

    }

反射调用优化

  1. Method, Field, Constructor 对象都有 setAccessible()方法
  2. setAccessible 作用是启动和禁用访问安全检查开关
  3. 参数值为 true 表示,反射的对象在使用时取消访问检查,提高反射的效率,参数纸为 false 则表示反射的对象执行访问检查
        @Test
        public void setAccessibleDemo() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
            Class cls = Class.forName("ReflectionExample.pojo.Cat");
            Object o = cls.newInstance();
            Method methodHi = cls.getMethod("hi");
            methodHi.setAccessible(true);  // 在访问的时候取消检验
    
            Field nameField = cls.getField("name");
            nameField.setAccessible(true);  // 在访问的时候取消检验
    
            Constructor constructor = cls.getConstructor();
            constructor.setAccessible(true);  // 在访问的时候取消检验
        }
    

反射常用方法

@Test
public void classDemo() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    String classPath = "ReflectionExample.pojo.Cat";

    //1. <?> 表述不确定的java类型
    Class<?> aClass = Class.forName(classPath);

    //2. 输出cls
    System.out.println(aClass); // 显示这个class 是哪个类的 Class 的对象,这里是 toString() => ReflectionExample.pojo.Cat
    System.out.println(aClass.getClass()); // 运行类型 java.lang.Class

    //3. 获取包名
    System.out.println(aClass.getPackage().getName());

    //4. 获取全类名
    System.out.println(aClass.getName());

    //5. 通过 aClass 创建实例对象
    Cat o = (Cat)aClass.newInstance();
    System.out.println(o);

    //6. 通过反射获取属性 只能是 public
    Field nameField = aClass.getField("name");
    System.out.println(nameField.get(o));

    //7. 通过反射设置属性
    nameField.set(o,"小红花");
    System.out.println(nameField.get(o));

    //8. 获得所有属性(字段)
    Field[] fields = aClass.getFields();
    for (Field f : fields){
        System.out.println(f.getName());
    }

}

反射爆破方式

  • 注意
    1. 如果需要通过有参构造创建实例 需要先通过反射找到对应构造器,再通过这个构造器创建实例
    2. 如果是静态属性或静态方法, 实例对象(o1)可以写成 null,
        Class<?> aClass = Class.forName("ReflectionExample.pojo.Person");
        
        // 私有构造器
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, int.class);
        // ----爆破构造器----
        constructor.setAccessible(true);
        Object o1 = constructor.newInstance("hah",2);
        System.out.println(o1);
    
        // ----爆破属性----
        Field age = aClass.getDeclaredField("age");
        age.setAccessible(true);
        System.out.println(age);
        age.set(o1,201);
        System.out.println(o1);
        System.out.println(age.get(o1));
    
        // 如果这么写要求 f1 是静态的
        Field f1 = aClass.getDeclaredField("f1");
        f1.setAccessible(true);
        // 如果是静态属性可以设置 null
        f1.set(null,"ff2");
        System.out.println("@f1"+f1.get(null));
    
        // ----爆破方法----
        Method m4 = aClass.getDeclaredMethod("m4");
        m4.setAccessible(true);
        m4.invoke(o1);
        // 如果是静态方法可以写成null
        // m4.invoke(null);
    

Class 类

  1. Class 也是类,Class 类对象不是 new 出来的,是系统创建的
  2. 对于某个类的Class类对象,在内存中只有一份,因为加载一次
  3. 每个类的实力都会记得自己是由哪个Class 实例所生成的
  4. Class 对象是存放在堆的
  5. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
public abstract class ClassLoader {
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
}

反射加载过程

WeChat7a5b3b6fe7893463328b8148e42066dd.png

获取 Calss 方式

 /*
 *  1. class.forName
 *    前提: 已知一个类的全类名,且该类的类路径下,可以通过 class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException,
 *    应用场景: 多用于配置文件,读取类全路径加载类
 * */

     Class cls = Class.forName("ReflectionExample.pojo.Cat");

 /*
 *  2. 前提: 若已知具体的类,通过类的 class 获取,该方式最为安全可靠,程序性能最高实例
 *     应用场景: 多用于参数传递,比如通过反射得到的对应构造器对象
 * */
     System.out.println(Cat.class);

 /*
  *  3. 前提: 已知某个类的实力,调用该实例的 getClass() 方法获取 Class 对象
  *     应用场景: 通过创建好的对象,获取 Class 对象
  * */

     Cat cat = new Cat();
     Class aClass = cat.getClass();
     System.out.println(aClass);

 /*
 *   4. 通过加载器 4种方式来获取到类 Class 对象
 *
 * */

     // (1) 先得到类加载器 Cat
     ClassLoader classLoader = cat.getClass().getClassLoader();
     // (2) 通过类加载器得到Class对象
     Class<?> aClass1 = classLoader.loadClass("ReflectionExample.pojo.Cat");
     System.out.println(aClass1);

 /*
 *   5. 基本数据类型( int char boolean float double byte long short) 获取Class 类对象
 * */
 Class<Integer> integerClass = int.class;
 Class<Character> characterClass = char.class;
 Class<Boolean> booleanClass = boolean.class;

 /*
 *   6. 包装类通过 .TYPE得到 Class 类对象
 * */

 Class<Integer> type1 = Integer.TYPE;
 Class<Character> type2 = Character.TYPE;


//  int.class Integer.TYPE 的 hashCode() 相等
 // 说明底层 CLass 类相等
 System.out.println(integerClass.hashCode());
 System.out.println(type1.hashCode());
  • Java 中的 Class 对象
    1. 外部类,成员类,静态内部类,局部内部类,匿名内部类
    2. interfce:接口
    3. 数组
    4. enum(枚举)
    5. annotation (注解)
    6. 基本数据类型
    7. void
    Class<String> stringClass = String.class; // 外部类的class类型
    Class<Serializable> serializableClass = Serializable.class; // 接口的class类型
    Class<Integer[]> aClass = Integer[].class; // 数组的class类型
    Class<float[]> aClass1 = float[].class; // 二维数组的class类型
    Class<Deprecated> deprecatedClass = Deprecated.class; // 注解的class类型
    Class<Thread.State> stateClass = Thread.State.class; // 枚举的class类型
    Class<Long> longClass = long.class; // 基础数据的class类型
    Class<Void> voidClass = void.class; // void 的class类型
    Class<Class> classClass = Class.class; // Class 的class类型
    

加载类

反射机制是 Java 实现动态语言的关键吗,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
  3. 类加载的时机
    1. 静态加载:当创建对象时 (new Cat())
    2. 静态加载:当子类被加载时
    3. 静态加载:调用类中的静态成员时
    4. 动态加载:通过反射

加载类的五个阶段

WeChat15d360580b27e20cd539fde807246f36.png

  • 加载阶段

    1. JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 calss文件,也可能是jar包,网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class
  • 连接阶段:验证

    1. 目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
    2. 包括:文件格式验证(是否以魔数 oxcafebabe 开头),元数据验证,字节码验证和符号引用验证
    3. 可以考虑使用 Xverify:none 参数来关闭大部分的验证措施,缩短虚拟机类加载的时间
  • 连接阶段:准备

    1. JVM 会在该阶段对静态变量,分配内存并默认初始化,(对数据类型的默认初始值,如:0,0L,null,false 等)。这些变量所使用的内存都将在方法区中进行分配
    public class A {
        // n1 是实例属性,不是静态变量,因此在准备阶段,是不会分配内存
        public int n1 =10;
        // n2 是静态变量,分配内存 n2 是默认初始化 0,而不是20
        public static  int n2 = 20;
        // n3 是static final 是常量,他和静态变量不一样,因为一旦赋值就不变 n3 = 30
        public static final int n3 =30;
    }
    
  • 连接阶段:解析

    1. 虚拟机将常量池内的符号因替换为直接引用的过程
  • 初始化(Initialization)

    1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段执行<clint>()方法的过程
    2. <clint>()方法时由编译器按语句子啊源文件中出现的顺序,依次自动搜集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
    3. 虚拟机会保证一个类的<clint>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个现成去执行这个类的<clint>()方法,其他线程都需要阻塞等待,知道活动线程执行<clint>()方法完毕

    /*
    ClassLoader 类中的源码
    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
        // 正因为有这个机制,才能保证某个类在内存中 只有一个
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded

        }
    }*/

    /*
    * 加载 B 并生成 B 的 class 对象
    * 链接 num = 0
    * 初始化阶段
    *  依次搜集类中的所有静态变量的赋值动作和静态代码块的语句,并合并 如下
    *  按照代码中编写的顺序,执行合并或覆盖
    *  clinit(){
    *         System.out.println("B 静态代码块");
    *         // num = 300;
    *         num = 100;
    *   }
    *
    * */
    public class B {
        public static int num = 100;
        static  {
            System.out.println("B 静态代码块");
            num = 300;
        }
        public B() {
            System.out.println("B 构造器");
        }
    }

获取类的结构信息

  • java.lang.Class

        Class<?> aClass = Class.forName("ReflectionExample.pojo.Person");
        // getName:获取全类名
        System.out.println(aClass.getName());
        // getSimpleName 获取简单类名
        System.out.println(aClass.getSimpleName());
        // getFields 获取所有 public 修饰符属性,包含本类以及父类的
        System.out.println(aClass.getFields());
        // getDeclaredField 获取本类中的所有属性(包括私有)
         System.out.println(aClass.getDeclaredFields());
        // getMethods 获取所有 public 修饰符方法,包含本类以及父类的
        System.out.println(aClass.getMethods());
        // getDeclaredField 获取本类中的所有方法(包括私有)
        System.out.println(aClass.getDeclaredMethods());
        // getConstructors 获取所有 public 修饰符构造器,包含本类
        System.out.println(aClass.getConstructors());
        // getDeclaredConstructors 获取本类中的所有构造器(包括私有)
        System.out.println(aClass.getDeclaredConstructors());
        // getPackage     以Package形式返回
        System.out.println(aClass.getPackage());
        // getSuperclass  以Class的形式返回父类信息
        System.out.println(aClass.getSuperclass());
        // getInterfaces  以Class[]的形式返回接口信息
        System.out.println(aClass.getInterfaces());
        // getAnnotations 以annotation[]的形式返回接口信息
        System.out.println(aClass.getAnnotations());
    
  • java.lang.reflect.Method

    1. getModifiers以int形式返回修饰符,默认修饰符:0,public:1,private:2,protected:4,static:8,final:16
    2. getReturnType 以Class形式获取返回类型
    3. getName 返回方法名
    4. getParameterTypeClass[] 返回参数类型数组
  • java.lang.reflect.Constructor

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