Skip to content

反射


1. 反射引出

在配置文件中存储了不同的类信息和方法,要求创建该类,然后对该类的属性和方法进行操作

思路

  • 首先创建 properties 类,读取配置文件的信息
  • 问题来了:我们只能获取信息,但是不能通过返回的信息创建类示例因为返回的类型是字符串,不可能取创建一个字符串~~😂😂

2. 基本介绍

  1. 反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

  2. 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个 Class 对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

  3. 内容补充

    设计模式的 OCP 原则(开闭原则): 不能修改源码,但是需要扩展功能,这就能用到反射(在后续学习框架还会遇到很多的需求,都需要使用反射解决)

  4. 简单理解:通过反射可以直接操作类的属性和方法,这会非常方便

    引出问题的解决:无论配置文件写什么内容,只要知道类的名称就可以操作,提高了代码的复用性,提高开发效率

3. ⚠️ 反射原理图

alt text

  1. 一个类可以创建多个多项实例,但是类加载只会执行一次

  2. 在进行类加载时,通过类加载器堆中会生成一个该类的 Class 对象,同时加载该类的信息(字段,构造器,方法),把他们当作对象对待,存放在一个数组中

  3. 类加载后,对象在堆中被创建,同时,这个对象知道他是属于哪个 Class 对象即 Class 对象的类型)这也就解释了为什么可以通过这个对象拿到他所关联的那个 Class 对象拿到该类的 Class 对象之后,就可以在不修改源码的条件下 对该类进行操作

4. 反射机制的作用

alt text

5. 反射相关的类

alt text

补充方法

  • 获取类的 Class 对象: Class.forName(类的路径)会有异常,需要抛出或者捕获
  • 通过该类的 Class 对象创建该类的一个实例对象:newInstance()返回的是 Object 对象会有异常,需要抛出或者捕获

(1)Method

  • getMethod(字符串):通过 该类的 Class 对象调用该方法,传入方法名
  • invoke(类对象):得到 Method 对象后,通过这个方法调用类的方法,会有异常,需要抛出或者捕获

(2)Field

  • getField():通过 该类的 Class 对象得到字段对象,之后调用 get()方法根据字段获得属性值
  • get(类对象):通过 Field 对象获得某个类的指定字段值
  • 注意:只能获取 public 修饰的属性,否则会抛出 NoSuchFieldException 异常

(3)Constructor

  • 注意:如果是有参构造器,传入参数类型应该是该数据类型的 Class 对象

代码示例

java
package reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class reflect {
    public static void main(String[] args) throws Exception {

        // 拿到 dog 类的 Class 对象
        Class cls = Class.forName("reflect.Dog");

        // 通过 Class 对象创建 dog 类的一个对象示例
        Object o = cls.newInstance();

        // Method 方法
        Method method = cls.getMethod("hi"); // 首先获得 method 对象
        method.invoke(o); // invoke 方法调用类的方法

        // Field 方法
        Field field = cls.getField("name");
        String name = field.get(o).toString(); // 获取 dog 类的 name属性
        System.out.println("dog类中name:" + name);

        // Constructor 方法

        // 无参构造器
        Constructor constructor = cls.getConstructor();
        System.out.println(constructor);

        // 含有 String 参数的构造器
        Constructor constructor1 = cls.getConstructor(String.class);
        System.out.println(constructor1);
    }
}

// 输出示例
hi~
dog类中name:white
public reflect.Dog()
public reflect.Dog(java.lang.String)

6. ⭐ 常用方法(获取类信息)

第一组:Class 类

方法名功能描述
getName获取全类名
getSimpleName获取简单类名
getFields获取所有 public 修饰的属性,包含本类以及父类的
getDeclaredFields获取本类中所有属性
getMethods获取所有 public 修饰的方法,包含本类以及父类的
getDeclaredMethods获取本类中所有方法
getConstructors获取本类所有 public 修饰的构造器
getDeclaredConstructors获取本类所有构造器
getPackage以 Package 形式返回包信息
getSuperClass以 Class 形式返回父类信息
getInterfaces以 Class[] 形式返回接口信息
getAnnotations以 Annotation[] 形式返回注解信息
注意
  1. getConstructors,不包含父类构造器

  2. 包含 Defalut 就可以获得本类所有的(字段,构造器,方法...),但是无法访问 private

第二组:Feild 类

序号方法名说明
1getModifiers以 int 形式返回修饰符
2getType返回 Class 类型
3getName返回属性名

getModifiers 说明

  • 默认修饰符是 0
  • public 是 1
  • private 是 2
  • protected 是 4
  • static 是 8
  • final 是 16
  • 如果是组合使用,则相加,如 public(1) + static (8) = 9

第三组:Method 类

序号方法名说明
1getModifiers以 int 形式返回修饰符
2getName返回方法名
3getParameterTypesClass[] 返回参数类型数组
4getReturnTypeClass 形式获取返回类型

getModifiers 说明

  • 默认修饰符是 0
  • public 是 1
  • private 是 2
  • protected 是 4
  • static 是 8
  • final 是 16
  • 如果是组合使用,则相加,如 public(1) + static (8) = 9

第四组:Contstructor 类

方法名功能说明
getModifiers以 int 形式返回修饰符
getName返回构造器名(全类名
getParameterTypesClass[] 返回参数类型数组

getModifiers 说明

  • 默认修饰符是 0
  • public 是 1
  • private 是 2
  • protected 是 4
  • static 是 8
  • final 是 16
  • 如果是组合使用,则相加,如 public(1) + static (8) = 9

7. 反射操作对象

(1)创建对象(构造器)

方法名功能说明
newInstance调用类中的无参构造器,返回 Object 对象
getConstructor(Class... clazz)根据参数列表,获取对应的 public 构造器对象
getDeclaredConstructor(Class... clazz)根据参数列表,获取对应的所有构造器对象(包含非 public 修饰的)

注意:如果需要指定构造器创建对象实例,拿到构造器对象后,使用构造器对象 . newInstance()方法创建对象实例

(2)字段

方法名功能说明
getFields获取所有 public 修饰的属性,包含本类以及父类的
getDeclaredFields获取本类中所有属性
Method 对象.set(对象 / null,属性值)设置属性值,但是不修改源码
Method 对象.get(对象 / null)根据 Method 对象获取属性值
说明:如果是静态属性,可以写成空,因为静态变量是在类的加载完成初始化的,不需要依赖任何对象实例

(3)方法

方法名功能说明
getMethods获取所有 public 修饰的方法,包含本类以及父类的
getDeclaredMethods获取本类中所有方法
Method 对象.invoke(对象 / null)调用对象的方法
说明
(1)如果是静态方法,可以写成空,因为静态方法是直接通过类名来调用的,不需要依赖任何对象实例
(2)如果方法有返回值,统一返回 Object 对象

8. ⭐ 反射爆破

(1)解决的问题

无法访问 private的问题

方法:setAccesible(true)

(2)适用对象

  • Field
  • Method
  • Constructor

9. 反射的优缺点

1. 优点: 可以动态的创建和使用对象(也是框架底层核心), 使用灵活, 没有反射机制,框架技术就失去底层支撑。

2. 缺点: 使用反射基本是解释执行, 对执行速度有影响.

10. 反射调优

(1)什么是反射检查

  • 定义:反射检查的含义通常是指通过反射去查看或验证某个类的结构(如方法、字段、构造函数等),并在程序运行时进行相应的检查操作。
  • 具体实现
    • 检查类是否包含某个方法或字段
    • 检查方法或字段的可访问性
    • 检查类的类型信息

(2)反射调优

>>核心思想:关闭反射检查

>>关闭反射检查

1. Method 和 Field、Constructor 对象都有 setAccessible()方法

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

3. 参数值为 true 表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false 则表示反射的对象执行访问检查

代码示例

java
package reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class reflect {
    public static void main(String[] args) throws Exception {
        m1(); // 反射
        m2(); // 关闭反射检查
    }

    public static void m1() throws Exception {
        long start = System.currentTimeMillis();

        Class cls = Class.forName("reflect.Dog");
        Object o = cls.newInstance();

        // Method 方法
        Method method = cls.getMethod("hi");
        method.setAccessible(true);  // 关闭反射检查
        for (int i = 0; i < 999999999; i++) {
            method.invoke(o); // invoke 方法调用类的方法
        }

        long end = System.currentTimeMillis();

        long gap = end - start;

        System.out.println("反射用时:" + gap);
    }

    public static void m2() throws Exception {
        long start = System.currentTimeMillis();

        Class cls = Class.forName("reflect.Dog");
        Object o = cls.newInstance();

        // Method 方法
        Method method = cls.getMethod("hi");
        method.setAccessible(true);  // 关闭反射检查
        for (int i = 0; i < 999999999; i++) {
            method.invoke(o); // invoke 方法调用类的方法
        }

        long end = System.currentTimeMillis();

        long gap = end - start;

        System.out.println("关闭反射检查用时:" + gap);
    }
}

// 输出
反射用时:518
关闭反射检查用时:488