Java 反射

  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. 生成动态代理

反射相关的常用类

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

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

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

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

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

反射的优点和缺点

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

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

  1. Method 和 FieldConstructor 对象都有 setAccessible() 方法

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

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

    为 false 表示执行访问检查

Class 类

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

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():获取本类中所有方法

获取 Class 对象

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

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

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

    可能抛出 ClassNotFoundExcption

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

    1
    Class cls2 = Test.class;

    应用场景:参数传递。

    该方法最为安全

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

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

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

  4. 通过类加载器:

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

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

    1
    2
    Class clsB3 = Character.TYPE;
    Class`<Long>` clsB4 = Long.TYPE;

哪些类有 Class 对象

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

类的加载

基本说明

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

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

类加载时机

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

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

  • 连接(Linking):

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

  • 初始化(initialization):

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

类加载的五个阶段

  • 加载阶段

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

  • 连接阶段 - 验证

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

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

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

  • 连接阶段 - 准备

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

    1
    2
    3
    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>() 方法完毕

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

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]

    示例:

    1
    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[] 形式返回形参类型数组

通过反射创建对象

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

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

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

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

通过反射访问成员

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

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

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

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