反射入门零基础

反射基础概念

  1. 什么是反射?

    1. 在传统(正向)编程中,你明确知道要使用的类(如 StringArrayList)及其方法(如 length()add()),并在编译时直接编写代码创建对象和调用方法。

    2. 反射是一种在程序运行时(Runtime) 动态操作程序自身的能力。它允许你的代码:

      1. 获取任意一个类的完整结构信息(类名、父类、接口、字段、方法、构造器、注解等)。

      2. 动态创建该类的对象实例。

      3. 动态访问修改对象的字段值(即使是 private 的!需谨慎)。

      4. 动态调用对象的方法(即使是 private 的!需谨慎)。

      5. 处理数组枚举

      6. 在运行时分析类的结构。

  2. 为什么需要反射?

    1. 框架开发的基石: (核心应用)

      1. 依赖注入 (DI/IoC - Spring): 容器读取配置(XML/注解),在运行时动态创建所需对象 (Class.newInstance()Constructor.newInstance()),并通过反射 (Field.set()Method.invoke()) 将依赖对象注入。

      2. 对象关系映射 (ORM - Hibernate, MyBatis): 根据实体类定义,在运行时通过反射读取字段信息 (Field),动态生成 SQL 进行数据库记录与 Java 对象的相互转换。

      3. 单元测试 (JUnit): 框架通过反射查找带有 @Test@Before 等注解的方法 (Method),并在运行时动态调用它们 (Method.invoke())。

    2. 动态代理 (AOP - Spring AOP): 创建实现接口的代理对象。调用代理方法时,动态转发给 InvocationHandler,其内部使用反射 (Method.invoke()) 调用目标方法,并可在前后插入通用逻辑(日志、事务)。

    3. 开发工具 (IDE): 代码自动补全、类型检查、调试器查看对象状态等功能依赖反射分析类信息。

    4. 插件/扩展机制: 应用在运行时加载外部 JAR,通过反射识别并实例化实现了特定接口或继承了基类的插件类 (Class.forName()Class.newInstance()Constructor.newInstance())。

    5. 序列化/反序列化 (Jackson, Gson): 库使用反射 (ClassField) 分析对象结构,将其转换为 JSON/XML 或反之。

    6. 访问非公开成员 (谨慎!): 突破封装访问 private 字段/方法 (测试私有方法、修改三方库行为)。通过 setAccessible(true) 实现,但破坏封装性。

反射核心类(java.lang.reflect包)

  1. 基石:Class 类

    1. 代表什么? 代表一个接口在运行时的类型信息。是反射操作的入口点。

    2. 获取 Class 对象的 3 种主要方式:

      1. 对象.getClass() 通过已有的对象实例获取。

        String str = "Hello";
        Class<?> clazz1 = str.getClass(); // clazz1 代表 java.lang.String

      2. 类名.class 使用类的字面常量。最常用、直接,不会初始化类

        Class<String> clazz2 = String.class; // 明确类型
        Class<?> clazz3 = int.class;        // 基本数据类型也可以
        Class<?> clazz4 = void.class;       // void 类型

      3. Class.forName("完整类名") 根据字符串形式的全限定类名(包名.类名)动态加载类并返回 Class 对象。会触发类的初始化

        Class<?> clazz5 = Class.forName("java.lang.String"); // 加载 String 类
        Class<?> clazz6 = Class.forName("com.mysql.jdbc.Driver"); // 动态加载数据库驱动

    3. Class 对象的核心功能:

      1. 获取类信息:

        • String getName():全限定类名 (e.g., "java.lang.String")

        • String getSimpleName():简单类名 (e.g., "String")

        • String getCanonicalName():规范名称 (通常同全限定名)

        • Class<? super T> getSuperclass():获取直接父类 Class 对象

        • Class<?>[] getInterfaces():获取实现的接口 Class 数组

        • int getModifiers():获取修饰符 (需配合 Modifier 解析)

        • Package getPackage():获取包信息

        • boolean isInterface() / isArray() / isPrimitive() / isEnum() / isAnnotation():判断类型

        • boolean isAssignableFrom(Class<?> cls):判断 cls 是否是该类/接口的子类/实现类

      2. 获取成员信息:

        • Field[] getFields():获取所有 public 字段(包括继承的)。

        • Field getField(String name):根据名字获取指定的 public 字段(包括继承的)。

        • Field[] getDeclaredFields():获取本类声明的所有字段(包括 private, protected, package-private),不包括继承的

        • Field getDeclaredField(String name):根据名字获取本类声明的指定字段(任何访问权限)。

        • Method[] getMethods():获取所有 public 方法(包括继承的)。

        • Method getMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型列表获取指定的 public 方法(包括继承的)。

        • Method[] getDeclaredMethods():获取本类声明的所有方法(任何访问权限),不包括继承的

        • Method getDeclaredMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型列表获取本类声明的指定方法(任何访问权限)。

        • Constructor<?>[] getConstructors():获取所有 public 构造器。

        • Constructor<T> getConstructor(Class<?>... parameterTypes):根据参数类型列表获取指定的 public 构造器。

        • Constructor<?>[] getDeclaredConstructors():获取本类声明的所有构造器(任何访问权限)。

        • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):根据参数类型列表获取本类声明的指定构造器(任何访问权限)。

        • Annotation[] getAnnotations():获取类上的所有注解。

        • <A extends Annotation> A getAnnotation(Class<A> annotationClass):获取指定类型的注解。

      3. 创建实例 (过时方式):

        • T newInstance():调用类的无参 public 构造器创建新实例。已过时(Java 9+),推荐使用 Constructor.newInstance()

      4. 其他:

        • ClassLoader getClassLoader():获取加载该类的类加载器。

        • TypeVariable<Class<T>>[] getTypeParameters():获取泛型类型参数(如果类是泛型类)。

  2. 操作字段:Field 类

    1. 代表什么? 代表类的一个字段(成员变量)。

    2. 获取 Field 对象: 通过 Class 对象的 getField(s) / getDeclaredField(s) 方法获得(见 3.3.2)。

    3. Field 对象的核心功能:

      1. 获取字段信息:

        • String getName():字段名

        • Class<?> getType():字段的声明类型 (Class 对象)

        • int getModifiers():修饰符 (需配合 Modifier 解析)

        • Annotation[] getAnnotations() / <A extends Annotation> A getAnnotation(Class<A> annotationClass):获取字段上的注解

      2. 访问字段值:

        • Object get(Object obj):获取指定对象 obj 上此字段的值。如果是静态字段,obj 可为 null

        • void set(Object obj, Object value):将指定对象 obj 上此字段的值设置为 value。如果是静态字段,obj 可为 null

        • 基本数据类型快捷方法: getXxx(Object obj) / setXxx(Object obj, Xxx value) (e.g., getIntsetBooleangetDoublesetChar) 避免装箱拆箱。

      3. 访问私有字段 (关键步骤):

        • 默认无法访问非 public 字段。

        • void setAccessible(boolean flag):将此对象的 accessible 标志设置为指示的布尔值。true 表示反射对象在使用时应该取消 Java 语言访问检查。

        • 重要: field.setAccessible(true); 必须在调用 field.get(obj) 或 field.set(obj, value) 之前执行。

        • 警告: 这破坏了封装性,应谨慎使用,并注意安全管理器可能禁止此操作。

  3. 操作方法:Method 类

    1. 代表什么? 代表类的一个方法

    2. 获取 Method 对象: 通过 Class 对象的 getMethod(s) / getDeclaredMethod(s) 方法获得(见 3.3.2)。必须指定参数类型列表 (Class<?>... parameterTypes) 来区分重载方法

      // 假设类有方法: public void setName(String name) 和 public void setName(String firstName, String lastName)
      Method setName1 = clazz.getMethod("setName", String.class);
      Method setName2 = clazz.getMethod("setName", String.class, String.class);

    3. Method 对象的核心功能:

      1. 获取方法信息:

        • String getName():方法名

        • Class<?> getReturnType():返回值类型 (Class 对象)

        • Class<?>[] getParameterTypes():参数类型数组 (Class 对象数组)

        • int getModifiers():修饰符 (需配合 Modifier 解析)

        • Class<?>[] getExceptionTypes():声明抛出的异常类型数组 (Class 对象数组)

        • Annotation[] getAnnotations() / getDeclaredAnnotations() / <A extends Annotation> A getAnnotation(Class<A> annotationClass):获取方法上的注解

        • boolean isVarArgs():是否声明为可变参数 (e.g., String...)

      2. 调用方法:

        • Object invoke(Object obj, Object... args):在指定对象 obj 上调用此方法,并传入参数 args

          • obj:调用方法的对象实例。如果是 静态方法obj 必须为 null

          • args:传递给方法的参数值。参数个数和类型必须与方法签名匹配。基本数据类型会自动装箱。

          • 返回值: 方法的返回值。如果方法返回 void,则为 null。基本类型返回值会被包装成对应包装类。

        • 示例:

          
          
          Object result = method.invoke(targetObject, arg1, arg2, arg3); // 调用实例方法
          Object staticResult = staticMethod.invoke(null, arg1);          // 调用静态方法

      3. 访问私有方法:

        • 同样需要 method.setAccessible(true); 才能调用 private 方法。

  4. 操作构造器:Constructor<T> 类

    1. 代表什么? 代表类的一个构造方法,用于创建对象实例。

    2. 获取 Constructor 对象: 通过 Class 对象的 getConstructor(s) / getDeclaredConstructor(s) 方法获得(见 3.3.2)。必须指定参数类型列表 (Class<?>... parameterTypes) 来区分重载构造器

      // 假设类有无参构造器和 public Person(String name, int age) 构造器
      Constructor<?> noArgCons = clazz.getDeclaredConstructor(); // 无参
      Constructor<?> paramCons = clazz.getDeclaredConstructor(String.class, int.class); // 有参

    3. Constructor 对象的核心功能:

      1. 获取构造器信息:

        • Class<?>[] getParameterTypes():参数类型数组

        • int getModifiers():修饰符

        • Class<?>[] getExceptionTypes():声明抛出的异常类型数组

        • Annotation[] getAnnotations() / getDeclaredAnnotations() / <A extends Annotation> A getAnnotation(Class<A> annotationClass):获取构造器上的注解

        • boolean isVarArgs():是否可变参数

      2. 创建对象实例 (推荐方式):

        • T newInstance(Object... initargs):使用此构造器创建并初始化类的新实例。参数 initargs 是传递给构造器的实际参数值。

        • 这是替代 Class.newInstance() 的现代方式,支持有参构造器。

        • 示例:

          Object obj1 = noArgCons.newInstance(); // 调用无参构造器
          Object obj2 = paramCons.newInstance("Alice", 30); // 调用有参构造器

      3. 访问私有构造器:

        • 同样需要 constructor.setAccessible(true); 才能调用 private 构造器。

  5. 解析修饰符:Modifier 工具类

    1. 作用: 解析 Class.getModifiers()Field.getModifiers()Method.getModifiers()Constructor.getModifiers() 返回的 int 值,判断具体修饰符。

    2. 常用静态方法: 都是 static boolean isXxx(int mod)

      • isPublic(mod) / isPrivate(mod) / isProtected(mod)

      • isStatic(mod) / isFinal(mod) / isAbstract(mod)

      • isSynchronized(mod) / isNative(mod) / isStrict(mod) / isTransient(mod) / isVolatile(mod)

      • isInterface(mod) / isAnnotation(mod) / isEnum(mod)

    3. 使用示例:

      int mods = someField.getModifiers();
      System.out.println("字段修饰符:");
      if (Modifier.isPublic(mods)) System.out.println("  public");
      if (Modifier.isPrivate(mods)) System.out.println("  private");
      if (Modifier.isStatic(mods)) System.out.println("  static");
      if (Modifier.isFinal(mods)) System.out.println("  final");

关键概念区分:getXxx vs getDeclaredXxx

  1. getXxx() (不带 Declared):

    • 目标:获取 public 成员。

    • 范围:包括本类及其所有父类/实现的接口中定义的 public 成员。

    • 示例:getFields()getMethods()getConstructors()getField(name)getMethod(name, paramTypes)getConstructor(paramTypes)

    • 无法获取非 public 成员。

  2. getDeclaredXxx()

    • 目标:获取本类直接声明的所有成员。

    • 范围:仅限本类中声明的成员(字段、方法、构造器),不考虑访问权限(public, protected, package-private, private 都能获取)。

    • 不包括从父类或接口继承来的任何成员。

    • 示例:getDeclaredFields()getDeclaredMethods()getDeclaredConstructors()getDeclaredField(name)getDeclaredMethod(name, paramTypes)getDeclaredConstructor(paramTypes)

综合示例 (带详细注释)

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

public class DetailedReflectionExample {

    public static void main(String[] args) throws Exception {
        // ===== 1. 获取 Class 对象 =====
        // 方式1: Class.forName (触发初始化)
        Class<?> personClass = Class.forName("com.example.Person");
        System.out.println("1. 获取Class对象: " + personClass.getName());

        // ===== 2. 使用 Class 对象获取信息 =====
        // 2.1 类基本信息
        System.out.println("\n2.1 类基本信息:");
        System.out.println("  简单类名: " + personClass.getSimpleName());
        System.out.println("  父类: " + (personClass.getSuperclass() != null ? personClass.getSuperclass().getName() : "无"));
        System.out.println("  是接口? " + personClass.isInterface());

        // 2.2 获取所有 public 字段 (包括继承的)
        System.out.println("\n2.2 Public 字段:");
        Field[] publicFields = personClass.getFields();
        for (Field field : publicFields) {
            System.out.println("  - " + field.getName() + " : " + field.getType().getSimpleName());
        }

        // 2.3 获取所有本类声明的字段 (包括 private)
        System.out.println("\n2.3 Declared 字段:");
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.print("  - " + field.getName() + " : " + field.getType().getSimpleName());
            // 解析修饰符
            int mods = field.getModifiers();
            System.out.print(" | 修饰符: ");
            if (Modifier.isPublic(mods)) System.out.print("public ");
            if (Modifier.isPrivate(mods)) System.out.print("private ");
            if (Modifier.isProtected(mods)) System.out.print("protected ");
            if (Modifier.isStatic(mods)) System.out.print("static ");
            if (Modifier.isFinal(mods)) System.out.print("final ");
            System.out.println();
        }

        // ===== 3. 创建对象实例 =====
        System.out.println("\n3. 创建对象实例:");
        // 3.1 获取无参构造器 (假设存在)
        Constructor<?> noArgConstructor = personClass.getDeclaredConstructor();
        // 3.2 使用构造器创建对象
        Object personObj = noArgConstructor.newInstance();
        System.out.println("  对象创建成功: " + personObj); // 会调用对象的 toString()

        // ===== 4. 操作字段 =====
        System.out.println("\n4. 操作字段:");
        // 4.1 获取 public 字段 "publicField" (假设存在)
        Field publicField = personClass.getField("publicField");
        // 4.2 获取字段值
        Object publicFieldValue = publicField.get(personObj);
        System.out.println("  4.2 publicField 原始值: " + publicFieldValue);
        // 4.3 设置字段值
        publicField.set(personObj, "新的Public值");
        System.out.println("  4.3 publicField 新值: " + publicField.get(personObj));

        // 4.4 获取 private 字段 "name" (假设存在)
        Field privateNameField = personClass.getDeclaredField("name");
        // 4.5 关键!设置可访问性 (突破 private)
        privateNameField.setAccessible(true);
        // 4.6 设置 private 字段值
        privateNameField.set(personObj, "张三");
        System.out.println("  4.6 设置 private name 为: " + privateNameField.get(personObj));

        // ===== 5. 操作方法 =====
        System.out.println("\n5. 操作方法:");
        // 5.1 获取 public 方法 "setName(String)" (假设存在)
        Method setNameMethod = personClass.getMethod("setName", String.class);
        // 5.2 调用方法 (修改 name)
        setNameMethod.invoke(personObj, "李四");
        System.out.println("  5.2 调用 setName('李四') 后, name: " + privateNameField.get(personObj)); // 验证

        // 5.3 获取 public 方法 "getName()" (假设存在)
        Method getNameMethod = personClass.getMethod("getName");
        // 5.4 调用方法获取返回值
        Object nameValue = getNameMethod.invoke(personObj);
        System.out.println("  5.4 调用 getName(): " + nameValue);

        // 5.5 获取 private 方法 "privateMethod()" (假设存在)
        Method privateMethod = personClass.getDeclaredMethod("privateMethod");
        // 5.6 设置可访问性
        privateMethod.setAccessible(true);
        // 5.7 调用 private 方法
        System.out.print("  5.7 调用 privateMethod(): ");
        privateMethod.invoke(personObj);
    }

    // 假设的 Person 类 (放在 com.example 包下)
    static class Person { // 这里用内部类简化示例,实际应在单独文件
        public String publicField = "原始Public值";
        private String name;
        private int age;

        public Person() {
            System.out.println("  (内部) Person 无参构造器被调用");
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
            System.out.println("  (内部) setName 被调用,name=" + name);
        }

        private void privateMethod() {
            System.out.println("  (内部) 私有方法 privateMethod 被调用!");
        }

        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + ", publicField='" + publicField + "'}";
        }
    }
}

反射的优缺点 (深入分析)

  1. 优点:

    1. 极致灵活性: 程序行为可在运行时动态决定,无需编译时硬编码类名、方法名。这是框架和可扩展系统的灵魂。

    2. 框架核心: 支撑了 Spring (DI, AOP), Hibernate/MyBatis (ORM), JUnit (测试), Tomcat (Servlet 容器) 等几乎所有主流 Java 框架/容器。

    3. 通用能力: 可编写代码分析器、调试器、序列化库、动态代理生成器等通用工具。

    4. 突破限制 (双刃剑): 在特定场景下(如测试、深度集成),可以访问和操作非公开成员。

  2. 缺点:

    1. 性能开销:

      • 反射操作(尤其是 Method.invoke())比直接方法调用慢得多(JIT 优化受限)。

      • 原因:动态解析类型、方法查找、访问检查、参数装箱/拆箱、异常包装等。

      • 重要: 在性能敏感的代码(高频循环、核心算法)中避免使用反射

    2. 破坏封装性:

      • setAccessible(true) 允许绕过 privateprotected 等访问控制,违反了面向对象的基本原则。

      • 可能导致预期外的行为、安全漏洞(访问敏感数据)、破坏类的内部不变性。

      • 最佳实践: 除非绝对必要(如框架内部、特定测试),否则不要使用。使用时务必明确风险。

    3. 代码复杂度:

      • 反射代码通常冗长、可读性差(大量字符串、异常处理)。

      • 调试困难:错误常在运行时才暴露(如 NoSuchMethodExceptionIllegalAccessException),堆栈信息可能不如直接调用清晰。

    4. 安全隐患:

      • 可能被恶意代码利用来访问内部 API、修改 final 字段、调用私有方法等。

      • 安全管理器 (SecurityManager) 可以限制反射操作(但现代 Java 中 SecurityManager 已被废弃)。

    5. 绕过泛型检查:

      • 泛型的类型参数信息在编译后会被擦除(Type Erasure)。

      • 反射在运行时操作,无法获取具体的泛型类型参数(List<String> 在运行时只是 List)。

      • 使用 Field.get()/Method.invoke() 时编译器无法进行泛型类型安全检查,可能导致 ClassCastException

最佳实践与学习建议

  1. 最佳实践:

    1. 优先直接编码: 如果编译时类型已知,绝不使用反射。直接用 new. 操作符。

    2. 限制使用范围: 仅在真正需要运行时动态性的地方使用(框架、插件、通用工具、特定测试)。

    3. 缓存反射元数据: 避免重复查找 ClassMethodFieldConstructor 对象。在初始化阶段获取并缓存它们(如放入 Map)。

    4. 慎用 setAccessible(true) 深刻理解其破坏封装的后果。仅在无其他替代方案且操作对象可控时使用。避免在生产代码中随意使用。

    5. 严格异常处理: 反射 API 抛出大量检查型异常 (ClassNotFoundExceptionNoSuchMethodExceptionIllegalAccessExceptionInvocationTargetExceptionInstantiationException)。必须用 try-catch 或 throws 妥善处理。InvocationTargetException 包裹了目标方法真正抛出的异常,需用 getCause() 获取。

    6. 考虑替代方案:

      • 对象创建:依赖注入框架 (Spring, Guice)。

      • 动态代理:标准接口 + java.lang.reflect.Proxy 或 CGLIB/Byte Buddy (Spring AOP)。

      • 测试私有成员:优先重构使方法可测,或使用 Mockito/PowerMock (后者也基于反射)。

      • 配置驱动:使用 Properties, YAML, JSON 配合工厂模式。

  2. 学习路径建议:

    1. 打牢基础: 精通 Java OOP(类、对象、继承、接口、访问控制、异常)。

    2. 掌握 Class 理解其意义,熟练三种获取方式。

    3. 逐个击破: 分别深入学习 Field (获取/设值, setAccessible), Method (获取/调用, 参数类型), Constructor (获取/创建实例)。

    4. 理解 Modifier 学会解析修饰符常量。

    5. 区分 Declared 牢记 getXxx 和 getDeclaredXxx 的本质区别。

    6. 动手实践: 编写大量小例子,操作一个简单类(如上面的 Person),尝试各种操作。

    7. 阅读源码 (进阶): 选读 Spring Core (Bean 创建、依赖注入) 或 JUnit (Runner, 发现测试方法) 的部分源码,观察反射的实际应用。

    8. 关注性能与安全: 时刻警惕反射的代价和风险。

总结

反射是 Java 赋予开发者的一项强大而危险的能力。它如同程序世界的“内窥镜”和“手术刀”,让你能在运行时洞察和修改类的内部结构。这份详细指南从零基础出发,通过清晰的序号结构,系统讲解了反射的核心概念(ClassFieldMethodConstructorModifier)、获取方式、核心 API 用法、关键区别 (getXxx vs getDeclaredXxx)、典型应用场景、重要优缺点以及最佳实践。记住:反射是框架开发者的利器,而非日常业务编码的常规工具。 在绝大多数情况下,清晰、直接、编译时确定的代码才是首选。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐