<Java>21 反射

本文最后更新于:2023年3月30日 下午

21 反射

  1. 反射机制(Reflection)允许程序在执行期借助于 Reflection API 取得任何类的内部信息(如成员变量、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
  2. 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,形象地称之为:反射

ocp 原则(开闭原则):不修改源码来扩展功能

计算机的三个阶段

  1. 代码阶段 / 编译阶段

    编写代码 ——(Javac 编译)——> .class 字节码文件

  2. Class 类阶段 / 加载阶段

    字节码文件 ——(ClassLoader 类加载器)——> Class 类对象(堆中)· 字节码二进制数据 / 元数据(方法区)

    Class 类对象包含:成员变量 Field[] fields、构造器 Constructor[] cons、成员方法 Methord[] ms

  3. Runtime 运行阶段

    创建对象,该对象知道其属于哪个 Class 对象

反射机制可以完成

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

21.1 反射相关的常用类

  1. java.lang.Class:代表一个类。Class 对象表示某个类加载后在堆中的对象

    Class cls = Class.forName(classFullPath);			//[1]
    Object o = cls.newInstance();						//[2]
    1. 通过完整类名得到一个类的 Class 对象
    2. 通过该 Class 对象创建一个该类的 对象实例
  2. java.lang.reflect.Method:代表类的方法。Method 对象表示某个类的某个方法

    Method method = cls.getMethod(methodName);			//[1]
    method.invoke(o);									//[2]
    1. 通过该 Class 对象得到一个 方法对象
    2. 方法对象.invoke:调用该方法
  3. java.lang.reflect.Field:代表类的成员变量

    Field field = cls.getField(fieldName);				//[1]
    1. 该方法只能得到非私有对象
  4. java.lang.reflect.Constructor:代表类的构造方法

    Constructor constructor = cls.getConstructor();		//[1]
    Constructor constructor2 = cls.getConstructor(String.class)
        													//[2]
    1. 得到一个无参构造器
    2. 得到一个形参是 (String str) 的构造器

反射的优点和缺点

  • 优点:可以动态地创建和使用对象(也是框架底层核心),使用灵活。没有反射机制,框架技术就失去底层支撑
  • 缺点:使用反射基本是解释执行。这对执行速度有影响。

反射调用优化 - 关闭访问检查

  1. MethodFieldConstructor 对象都有 setAccessible() 方法

  2. setAccessible() 作用是启动和禁用访问安全检查的开关

  3. 参数值为 true,表示反射对象在使用时取消访问检查,这样能提高反射效率。

    为 false 表示执行访问检查

21.2 Class

  1. Class 也是类,因此也继承 Object
  2. Class 类不是 new 出来的,而是系统创建的
  3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个 Class 实例生成
  5. 通过 Class 可以完整地得到一个类的完整结构,通过一系列 API
  6. Class 对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的。有的地方称为类的元数据(包括 方法代码、变量名、方法名、访问权限 等)

21.2.1 Class 类的常用方法

  • Class.forName(String):返回指定类名的 Class 对象

  • newInstance():返回一个无参构造器创建的实例

  • getName():返回该 Class 对象表示的实体的全类名

  • getClass():返回该 Class 对象的运行类型 java.lang.Class

  • getPackage():返回该 Class 对象所在的包

  • getSuperClass():返回该 Class 对象的父类 Class 对象

  • getInterface():返回该 Class 对象的接口(数组)

  • getAnnotations():返回注解信息(Annotation[]

  • getClassLoader():返回该 Class 对象的加载器(ClassLoader 类型)

  • getSuperclass():返回该 Class 对象实体的超类的 Class

  • getConstructors():返回本类所有包含 public 修饰的构造器的 Constructor 对象数组

    该方法返回的构造器不含父类构造器!

  • getDeclaredConstructer():返回本类所有构造器的 Constructor 对象数组

  • getFileds():返回一个包含 public 修饰的属性的 Field 对象的数组

    getFiled(String name):返回指定的 Field

  • getDeclaredFields():获取本类中所有属性

  • field.get(instance):返回指定实例的指定属性

  • field.set(instance, ..):给指定实例的指定属性赋值

  • getMethod():获得所有 public 修饰的方法的 Method 对象

  • getMethod(String name, Class paramTypes, ...):返回一个 Method 对象,其形参类型为 paramType

  • getDeclaredMethod():获取本类中所有方法

21.2.2 获取 Class 对象

package com.melody.note;
class Test {}
  1. (编译阶段)已知一个类的全类名,且该类在类路径下:

    Class cls1 = Class.forName("com.melody.note.Test");

    应用场景:配置文件,读取类全路径,加载类。

    可能抛出 ClassNotFoundExcption

  2. (加载阶段)已知具体的类:

    Class cls2 = Test.class;

    应用场景:参数传递。

    该方法最为安全

  3. (运行阶段)已知某个类的实例:

    Class cls3 = new Test().getClass();

    应用场景:通过创建好的对象获取 Class 对象

  4. 通过类加载器:

    ClassLoader cll = new Test().getClass().getClassLoader();
    Class cls4 = cll.loadClass("com.melody.note.Test");
  5. 基本数据类型:

    Class clsB1 = int.class;
    Class<Boolean> clsB2 = boolean.class;
  6. 基本数据类型包装类:

    Class clsB3 = Character.TYPE;
    Class<Long> clsB4 = Long.TYPE;

21.2.3 哪些类有 Class 对象

  1. 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  2. 接口(interface)
  3. 数组
  4. 枚举(enum)
  5. 注解
  6. 基本数据类型
  7. void

21.3 类的加载

基本说明

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

  1. 静态加载:编译时加载相关的类,如果没有则报错。依赖性强
  2. 静态加载:运行时加载需要的类,如果运行时不用该类,则不报错。降低了依赖性

类加载时机

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

(类加载图_21.3)

  • 加载(Loading):

    将类的 .class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成

  • 连接(Linking):

    将类的二进制数据合并进 JRE 中

  • 初始化(initialization):

    JVM 负责对类进行初始化。这里主要是静态成员

21.3.1 类加载的五个阶段

  • 加载阶段

    JVM 在该阶段的主要目的是将字节码从不同数据源(.class 文件、jar 包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象

  • 连接阶段 - 验证

    目的是确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

    包括:文件格式验证(是否以魔数 0xcafebabe 开头)、元数据验证、字节码验证、符号引用验证

    可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机加载的时间

  • 连接阶段 - 准备

    JVM 会在该阶段对 静态变量 分配内存并执行默认初始化。这些变量使用的内存都将在方法区中进行分配

    public int n1 = 1;					//实例属性,非静态变量,此阶段不分配内存
    public static int n2 = 2;			//静态变量,默认初始化为 0
    public static final int n3 = 3;		//static final 常量,静态初始化为 3
  • 连接阶段 - 解析

    JVM 将常量池内符号引用替换为直接引用的过程

  • 初始化

    到初始化阶段,才真正开始执行类中定义的 Java 程序代码。此阶段是执行 <clinit>() 方法的过程

    <clinit>() 方法是由编译器按语句在文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并

    JVM 会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步。如果多个线程去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕

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

java.lang.Class 类(与前面的的重复)

  • getSuperClass():返回该 Class 对象的父类 Class 对象

  • getInterface():返回该 Class 对象的接口(数组)

  • getAnnotations():返回注解信息(Annotation[]

  • getClassLoader():返回该 Class 对象的加载器(ClassLoader 类型)

  • getSuperclass():返回该 Class 对象实体的超类的 Class

  • getConstructors():返回本类所有包含 public 修饰的构造器的 Constructor 对象数组

    该方法返回的构造器不含父类构造器!

  • getDeclaredConstructer():返回本类所有构造器的 Constructor 对象数组

  • getFileds():返回一个包含 public 修饰的属性的 Field 对象的数组

    getFiled(String name):返回指定的 Field

  • getDeclaredFields():获取本类中所有属性

  • field.get(instance):返回指定实例的指定属性

  • field.set(instance, ..):给指定实例的指定属性赋值

  • getMethod():获得所有 public 修饰的方法的 Method 对象

  • getMethod(String name, Class paramTypes, ...):返回一个 Method 对象,其形参类型为 paramType

  • getDeclaredMethod():获取本类中所有方法

java.lang.reflect.Field

  • getModifiers():以 int 形式返回修饰符

    默认修饰符 [0]、public [1]、private [2]、protected [4]、static [8]、final [16]

    示例:

    public static final int n = 0;

    这个变量的修饰符的 int 表示 = 1 + 8 + 16 = 25

  • getType():以 Class 形式返回类型

    上例变量的 getType() 等同于 Integer.getClass()

  • getName():返回属性名

java.lang.reflect.Method

  • getModifiers():以 int 形式返回修饰符(同上)
  • getName():返回方法名
  • getReturnType():以 Class 形式返回返回类型
  • getParameterTypes():以 Class[] 形式返回形参类型数组

java.lang.reflect.Constructer

  • getModifiers():以 int 形式返回修饰符
  • getName():返回构造器名(和全类名相等)
  • getParameterTypes():以 Class[] 形式返回形参类型数组

21.5 通过反射创建对象

  1. 调用类中的 public 修饰的无参构造器

    Object obj1 = cls.newInstance();
  2. 调用类中指定的构造器

    Constructer cons = cls.getConstructer(int.class, String.class, ...);
    Object obj2 = cons.newInstance(1, "nnn", ...);
  3. setAccessible(true):爆破(暴力破解)。使用反射可以访问 private 构造器

    Constructer cons2 = cls.getDeclaredConstructer(boolean.class ...);
    cons2.setAccessible(true);
    Object obj3 = cons.newInstance(false, ...);

21.6 通过反射访问成员

Field field = cla.getDeclaredField("name");
field.setAccessible(true);
field.set(o, "111");								//[1]
  1. o 表示一个类的实例

    如果该属性是静态属性,则对象 o 可以是 null

Method method = cls.getDeclaredMethod("m1");
method.setAccessible(true);
Object returnObj = method.invoke(o, 'c', ...);		//[1]
  1. o 表示一个类的实例,后面是实参列表

    同理,静态方法的场合,对象 o 可以是 null


<Java>21 反射
https://i-melody.github.io/2022/01/20/Java/入门阶段/21 反射/
作者
Melody
发布于
2022年1月20日
许可协议