java反射

Posted by lily on April 13, 2023

Java反射

什么是反射

反射是指在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射的作用

  • 在运行时判断任意一个对象所属的类
  • 获取类的完整结构,包括类的属性、方法、构造器、父类、实现的接口等从而可以动态的创建对象、调用对象的方法、访问属性等

    利用反射获取类的结构信息

    java的反射相关类都在java.lang.reflect包中,主要有以下几个类:Class、Field、Method、Constructor、Array、Proxy等。

    方法 构造函数
    getClass() 方法用于获取对象所属类的名称 getDeclaredMethod():它创建要调用的方法的对象。 getConstructors() 方法用于获取对象所属类的公共构造函数。
    - invoke():它在运行时调用类的一个方法,我们使用以下方法。 -
    • Class类的常用方法
      getName():返回类的全名包括包名
      getSimpleName():返回类的简单名称
      ---------------------
      getFields():返回类的所有公共属性
      getDeclaredFields():返回类的所有属性
      ---------------------
      getMethods():返回类的所有公共方法
      getDeclaredMethods():返回类的所有方法
      ------------------
      getConstructors():返回类的所有公共构造函数
      getDeclaredConstructors():返回类的所有构造函数
      ------------------
      getPackage():返回类所在的包
      getSuperclass():返回类的父类
      getInterfaces():返回类实现的所有接口
      ------------------
      getAnnotation():返回类的注解
      
    • Field类的常用方法
      getName():返回属性的名称
      getType():返回属性的类型
      getModifiers():返回属性的修饰符(默认修饰符为0 public为1private为2protected为4static为8final为16synchronized为32volatile为64transient为128native为256interface为512abstract为1024strictfp为2048)
      
    • Method类的常用方法
      getName():返回方法的名称
      getReturnType():返回方法的返回类型
      getModifiers():返回方法的修饰符
      getParameterTypes():返回方法的参数类型
      getExceptionTypes():返回方法的异常类型
      
    • Constructor类的常用方法
      getName():返回构造函数的名称
      getModifiers():返回构造函数的修饰符
      getParameterTypes():返回构造函数的参数类型
      getExceptionTypes():返回构造函数的异常类型
      
    • Array类的常用方法
      newInstance():创建一个数组对象
      getLength():返回数组的长度
      get():返回数组中指定位置的元素
      set():设置数组中指定位置的元素
      
    • Proxy类的常用方法
      newProxyInstance():创建一个代理对象
      
    • 通过反射获取类的结构信息
      public class Test {
      public static void main(String[] args) {
          try {
              // 1. 获取Class对象
              Class<?> c1 = Class.forName("com.atguigu.java.Person");
              Class<?> c2 = Person.class;
              Person p = new Person();
              Class<?> c3 = p.getClass();
              System.out.println(c1 == c2); // true
              System.out.println(c1 == c3); // true
              // 2. 获取类的完整结构
              // 2.1 获取类的属性
              Field[] fields = c1.getDeclaredFields();
              for (Field field : fields) {
                  System.out.println(field);
              }
              System.out.println();
              // 2.2 获取类的方法
              Method[] methods = c1.getDeclaredMethods();
              for (Method method : methods) {
                  System.out.println(method);
              }
              System.out.println();
              // 2.3 获取类的构造器
              Constructor<?>[] constructors = c1.getDeclaredConstructors();
              for (Constructor<?> constructor : constructors) {
                  System.out.println(constructor);
              }
      

反射的原理

反射机制原理图 反射机制原理图 ###java程序运行过程 java程序在计算机运行过程中有三个阶段

  • 编译阶段:javac将java源文件编译成class字节码文件
  • Class类加载阶段:java虚拟机将class字节码文件通过类加载器ClassLoader加载到内存中,(此时可以利用反射调用类加载器获取Class类),并将其转换成运行时数据结构,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构
  • Runtime运行时阶段:java虚拟机执行字节码指令,执行程序代码

    类加载

    ####类加载时机

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

      类加载过程

      类加载过程图 类加载过程图

      类加载三个阶段完成的任务

    5. 加载:JVM在该阶段主要目的是将字节码从不同的数据源(class文件、jar包、甚至网络资源)转换为二进制字节流加载到内存中,并生成该类的Class对象。 加载
  1. 验证:JVM在该阶段主要目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    1. 验证包括文件格式验证、元数据验证、字节码验证、符号引用验证。 验证
  2. 准备:JVM在该阶段主要目的是为类的静态变量分配内存,并将其初始化为默认值(对应数据类型的默认初始值,如0, null, false等)。
  3. 解析:JVM在该阶段主要目的是将常量池内的符号引用替换为直接引用。
    1. 符号引用:符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量。
    2. 直接引用:直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
  4. 初始化(Initialization):此阶段才开始真正的执行定义中的java程序代码,此阶段是执行clinit方法的过程
    • clinit方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并,编译器收集的顺序是由语句在源文件中出现的顺序决定。
      1. 类初始化阶段是执行类构造器<\clinit>()方法的过程。
      2. 类构造器<\clinit>()方法只执行一次,当一个类在初始化时,要求其父类全部都已经初始化完毕了。
      3. 如果一个类的初始化需要先初始化另外一个类,那么会先初始化那个类。
      4. 虚拟机会保证一个类的<\clinit>()方法在多线程环境中被正确加锁、同步。 初始化