Java基础全套教程(十)—— 反射机制详解

前言

反射是Java语言极具特色的核心特性,也是Java实现动态编程的基石。日常开发中使用的Spring框架、MyBatis框架、JUnit单元测试、动态代理等核心功能,底层全部依赖反射机制实现。

普通Java代码属于静态编码:编译阶段就固定了类、对象、方法的调用关系,运行时无法修改。而反射打破了编译期的限制,让程序在运行阶段可以动态解析类结构、创建对象、操作属性与方法。本节课将从原理、核心载体、实现方式、实操开发、性能优化、优缺点与实战应用,全方位详解反射机制。

一、反射机制核心概念

1.1 什么是反射

反射(Reflection)是Java提供的运行时自省与操作机制:程序在运行状态下,可以针对任意已加载的类,获取其全部内部结构信息,同时可以动态创建该类的实例、调用任意成员方法、修改任意成员变量

通俗理解:常规编码是“提前知道类,直接使用类”;反射是“运行时看透未知类,根据类的结构信息动态操作类”,哪怕是类中私有修饰的属性和方法,也可以通过反射突破权限限制进行操作。

1.2 反射的两大核心作用

反射的所有应用场景,都基于两大核心能力延伸而来:

  1. RTTI(运行时类型识别):程序运行过程中,动态识别对象真实类型、解析类的修饰符、构造器、属性、方法、父类、接口、注解等全部元信息。

  2. DC(动态组件装配):无需编译期绑定代码逻辑,运行时动态加载类、创建对象、调用方法、赋值属性,实现代码解耦与灵活扩展。

1.3 反射的核心价值

反射让Java从“静态固化语言”具备了“动态扩展能力”,核心价值体现在两点:

  • 低耦合高灵活:代码无需硬编码绑定具体类和方法,可通过配置、参数动态适配不同类,大幅提升代码通用性。

  • 框架底层支撑:几乎所有Java主流框架的核心功能都依赖反射实现,是进阶开发、源码阅读的必备知识点。

二、反射核心原理——Class对象

2.1 Class对象的本质

反射机制的唯一核心载体是 java.lang.Class 类,所有反射操作都必须基于Class对象完成。

当JVM加载一个类时,会在**方法区(元空间)**自动生成一个专属的Class对象,这个对象相当于当前类的“完整说明书”,存储了类的所有结构数据:构造方法、成员变量、成员方法、继承关系、注解信息等。

2.2 Class对象核心特性

  • 一个类在JVM整个生命周期中,有且仅有一个Class对象,无论实例化多少个对象,最终指向的Class对象完全一致。

  • 基本数据类型、数组、接口、枚举,都会对应专属的Class对象。

  • Class对象是反射的入口,没有获取Class对象,就无法执行任何反射操作。

2.3 类的加载与反射的关联

常规对象创建流程:编译生成class字节码文件 → JVM加载字节码 → 生成Class对象 → new创建实例对象。

反射对象创建流程:编译无需绑定类 → 运行时加载字节码 → 获取Class对象 → 动态创建实例、操作成员。

三、获取Class对象的三种方式(必备基础)

获取Class对象是所有反射操作的前提,Java提供三种标准方式,适配不同开发场景,下文统一使用全新的 User 实体类作为测试载体,全程独立案例,无原版重复代码。

3.1 通用测试实体类

public class User {
    // 私有成员变量
    private String username;
    private Integer age;
    // 公共成员变量
    public String phone;

    // 无参构造方法
    public User() {}

    // 公共有参构造
    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    // 私有有参构造
    private User(String phone) {
        this.phone = phone;
    }

    // 普通公共方法
    public void sayHello() {
        System.out.println("用户名:" + username + ",年龄:" + age);
    }

    // 私有方法
    private void updateInfo(String newName) {
        this.username = newName;
        System.out.println("用户名已更新为:" + newName);
    }

    // getter & setter
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

3.2 三种获取Class对象的方式

方式一:实例对象.getClass() (运行时获取)

通过类的实例对象调用原生方法获取Class对象,必须先创建对象,适用于已有实例对象的场景。

public class GetClassByInstance {
    public static void main(String[] args) {
        // 创建实例对象
        User user = new User();
        // 调用getClass()获取Class对象
        Class<? extends User> clazz = user.getClass();
        // 打印类全限定名
        System.out.println("类全限定名:" + clazz.getName());
    }
}

方式二:类名.class (编译期锁定,高效)

通过静态语法 类名.class 获取,无需创建对象,编译期即可识别,性能最优,适用于明确知道目标类的场景。

public class GetClassByStatic {
    public static void main(String[] args) {
        // 静态语法获取Class对象
        Class<User> clazz = User.class;
        System.out.println("类名:" + clazz.getSimpleName());
        System.out.println("全限定类名:" + clazz.getName());
    }
}

方式三:Class.forName() (动态加载,最常用)

通过Class类静态方法传入类全限定名动态加载类,无需提前知晓类,支持配置化加载,是框架开发中最常用的方式,需捕获编译异常。

public class GetClassByForName {
    public static void main(String[] args) throws ClassNotFoundException {
        // 传入类的全限定包名+类名
        Class<?> clazz = Class.forName("com.reflect.entity.User");
        System.out.println("加载的类:" + clazz.getName());
    }
}

3.3 三种方式对比总结

  • 对象.getClass():依赖实例对象,仅运行时可用,无法提前预判类类型。

  • 类名.class:高效、无异常,编译期校验,适合固定类的反射操作。

  • Class.forName():支持动态配置、热加载,灵活性最高,框架核心首选方式。

四、反射核心实操操作

获取Class对象后,可通过Class类提供的API,实现对类中构造器、成员变量、成员方法的全部操作,核心区分两组API:

  • getXXX():仅获取public修饰的公共成员(包含父类公共成员)。

  • getDeclaredXXX():获取当前类所有权限成员(public、private、protected、默认权限,不含父类)。

4.1 反射操作构造方法

常用API说明

方法名 功能描述
getConstructors() 获取所有公共构造方法数组
getDeclaredConstructors() 获取当前类所有权限构造方法数组
getConstructor(参数类型…) 获取指定参数列表的公共构造方法
getDeclaredConstructor(参数类型…) 获取指定参数列表的任意权限构造方法

实操代码:获取构造器并动态创建对象

import java.lang.reflect.Constructor;

public class ReflectConstructorDemo {
    public static void main(String[] args) throws Exception {
        Class<User> clazz = User.class;

        // 1.获取所有公共构造方法
        Constructor<?>[] publicConstructors = clazz.getConstructors();
        System.out.println("公共构造方法数量:" + publicConstructors.length);

        // 2.获取所有权限构造方法
        Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
        System.out.println("所有构造方法数量:" + allConstructors.length);

        // 3.通过公共有参构造创建对象
        Constructor<User> publicConstructor = clazz.getConstructor(String.class, Integer.class);
        User user1 = publicConstructor.newInstance("张三", 22);
        user1.sayHello();

        // 4.通过私有构造创建对象(需开启权限放行)
        Constructor<User> privateConstructor = clazz.getDeclaredConstructor(String.class);
        privateConstructor.setAccessible(true); // 关闭权限校验
        User user2 = privateConstructor.newInstance("13800138000");
        System.out.println("私有构造创建对象手机号:" + user2.phone);
    }
}

4.2 反射操作成员变量

常用API说明

方法名 功能描述
getFields() 获取所有公共成员变量数组
getDeclaredFields() 获取当前类所有权限成员变量数组
getField(字段名) 获取指定名称的公共成员变量
getDeclaredField(字段名) 获取指定名称的任意权限成员变量

实操代码:获取、修改私有/公共字段值

import java.lang.reflect.Field;

public class ReflectFieldDemo {
    public static void main(String[] args) throws Exception {
        Class<User> clazz = User.class;
        User user = clazz.newInstance();

        // 1.操作公共字段
        Field phoneField = clazz.getField("phone");
        phoneField.set(user, "13900139000");
        System.out.println("公共手机号:" + phoneField.get(user));

        // 2.操作私有字段
        Field nameField = clazz.getDeclaredField("username");
        nameField.setAccessible(true); // 放行私有权限
        nameField.set(user, "李四");
        System.out.println("私有用户名:" + nameField.get(user));
    }
}

4.3 反射操作成员方法

常用API说明

方法名 功能描述
getMethods() 获取所有公共方法(含父类)
getDeclaredMethods() 获取当前类所有权限方法
getMethod(方法名,参数类型…) 获取指定公共方法
getDeclaredMethod(方法名,参数类型…) 获取指定任意权限方法

实操代码:调用公共、私有方法

import java.lang.reflect.Method;

public class ReflectMethodDemo {
    public static void main(String[] args) throws Exception {
        Class<User> clazz = User.class;
        User user = clazz.getDeclaredConstructor().newInstance();

        // 1.调用公共无参方法
        Method helloMethod = clazz.getMethod("sayHello");
        user.setUsername("王五");
        user.setAge(25);
        helloMethod.invoke(user);

        // 2.调用私有有参方法
        Method updateMethod = clazz.getDeclaredMethod("updateInfo", String.class);
        updateMethod.setAccessible(true);
        updateMethod.invoke(user, "王五更新");
    }
}

4.4 获取类的拓展信息

通过Class对象还可以获取类的包名、父类、实现接口、类名等基础拓展信息,常用于通用工具类开发:

public class ReflectClassInfoDemo {
    public static void main(String[] args) {
        Class<User> clazz = User.class;
        // 获取简单类名
        System.out.println("简单类名:" + clazz.getSimpleName());
        // 获取全限定类名
        System.out.println("全限定类名:" + clazz.getName());
        // 获取包名
        System.out.println("包名:" + clazz.getPackage().getName());
        // 获取父类
        System.out.println("父类:" + clazz.getSuperclass().getSimpleName());
        // 获取实现的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> inter : interfaces) {
            System.out.println("实现接口:" + inter.getSimpleName());
        }
    }
}

五、关键方法:setAccessible() 权限放行

5.1 方法作用

setAccessible(boolean flag) 是反射核心优化与权限放行方法,作用是开启/关闭Java权限安全校验

  • true:关闭安全校验,突破private、protected权限限制,可操作私有成员,同时减少校验开销、提升反射效率。

  • false(默认):开启安全校验,仅可操作public成员,私有成员直接报错。

5.2 使用注意事项

该方法仅对当前反射操作生效,不会修改类的原生权限,属于运行时临时放行,不会破坏代码编译期权限规则。

六、反射性能分析与优化

6.1 反射效率低的原因

反射性能远低于普通直接调用,核心原因:

  • 反射运行时需要动态解析字节码文件,解析类结构信息,消耗额外资源。

  • 默认开启权限安全校验,增加大量判断逻辑。

  • JVM无法对反射代码做编译期优化、指令重排。

6.2 性能测试对比(全新案例)

循环10万次执行赋值方法,对比普通调用与反射调用的耗时差异:

import java.lang.reflect.Method;

public class ReflectPerformanceTest {
    public static void main(String[] args) throws Exception {
        User user = new User();
        int count = 100000;

        // 普通方法调用耗时
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            user.setAge(20);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("普通调用耗时:" + (end1 - start1) + "ms");

        // 反射方法调用耗时
        Class<User> clazz = User.class;
        Method setAgeMethod = clazz.getMethod("setAge", Integer.class);
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            setAgeMethod.invoke(user, 20);
        }
        long end2 = System.currentTimeMillis();
        System.out.println("反射调用耗时:" + (end2 - start2) + "ms");

        // 优化后反射耗时(关闭权限校验)
        setAgeMethod.setAccessible(true);
        long start3 = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            setAgeMethod.invoke(user, 20);
        }
        long end3 = System.currentTimeMillis();
        System.out.println("优化后反射耗时:" + (end3 - start3) + "ms");
    }
}

6.3 反射性能优化方案

  1. 优先调用 setAccessible(true) 关闭安全校验,大幅提升效率。

  2. 缓存Class对象、Method、Field对象,避免循环中重复获取。

  3. 高频业务场景尽量少用反射,框架底层、通用工具类场景优先使用。

七、反射优缺点总结

7.1 优点

  • 动态灵活:运行时动态操作类,无需编译期绑定,适配配置化、通用化开发。

  • 突破权限限制:可操作类中私有成员,实现常规代码无法实现的功能。

  • 解耦性强:降低代码硬编码耦合度,提升代码复用性与扩展性。

  • 框架基石:支撑几乎所有主流Java框架的核心功能实现。

7.2 缺点

  • 性能较低:相较于直接调用,反射存在额外解析与校验开销。

  • 安全性降低:突破访问权限,破坏类的封装性,存在安全风险。

  • 代码可读性差:反射代码繁琐、逻辑隐晦,增加维护成本。

  • 编译期无法校验:类名、方法名、字段名错误仅在运行时报错,调试难度更高。

八、反射实战应用场景

反射不会用于普通业务代码开发,核心用于通用工具、框架底层、中间件开发,常见场景如下:

  1. Spring IoC容器:通过反射动态创建Bean对象、注入属性,实现依赖注入。

  2. ORM框架:MyBatis、Hibernate通过反射将数据库查询结果自动封装为实体对象。

  3. 单元测试:JUnit通过反射调用测试方法,无需手动创建对象调用。

  4. 动态代理:JDK动态代理底层依赖反射实现方法拦截与增强。

  5. 通用工具类:对象拷贝、属性对比、JSON解析实体封装等通用工具。

  6. 配置化加载:通过配置文件类路径,动态加载类并执行对应逻辑。

九、本章核心总结

  1. 反射是Java运行时动态解析、操作类结构的核心机制,赋予Java动态编程能力。

  2. 反射唯一核心入口是Class对象,一个类仅对应一个Class实例。

  3. 获取Class对象三种方式:对象.getClass()类名.classClass.forName()

  4. 反射核心操作:动态创建对象、操作构造器、读写字段、调用方法。

  5. setAccessible(true) 可突破权限限制,同时优化反射性能。

  6. 反射灵活但性能较差、封装性弱,适合框架底层与通用工具开发,不适合高频业务场景。

更多推荐