程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • JavaSE - 基础篇

    • Java环境搭建
    • Java基础语法
    • Java数据类型
    • Java常量和变量
    • Java进制和存储
    • Java运算符
    • Java流程控制
    • Java数组
    • Java面向对象上
    • Java面向对象下
    • Java异常机制
    • Java枚举
    • Java反射机制
      • 1. 什么是反射机制 (Reflection)?
      • 2. 反射机制的核心作用与应用场景
      • 3. 反射机制相关的核心类
      • 4. 获取 Class 对象的三种主要方式
      • 5. 通过反射实例化对象 (Class.newInstance() - 已过时)
      • 6. 妙用 Class.forName():仅触发类加载与静态初始化
      • 7. 反射之 Field:访问和修改字段
        • Class 类中与 Field 相关的方法
        • Field 类中的核心方法
        • 通过反射设置和读取对象属性 (包括私有)
        • 反编译 Field(了解)
      • 8. 反射之 Method:动态调用方法
        • Class 类中与 Method 相关的方法
        • Method 类中的核心方法
        • 通过反射调用对象方法 (包括私有和静态)
        • 反编译 Method(了解)
      • 9. 反射之 Constructor:动态创建对象
        • Class 类中与 Constructor 相关的方法
        • Constructor 类中的核心方法
        • 创建对象的两步骤 (使用 Constructor)
        • 示例:通过反射调用构造方法实例化对象
        • 反编译 Constructor(了解)
      • 10. 反射中如何区分类的成员
      • 11. 获取类的父类与实现的接口
      • 12. 总结与思考
    • Java代理模式
    • Java泛型
    • Java序列化
    • Java多线程详解
    • Java线程池相关
  • Java
  • JavaSE - 基础篇
scholar
2023-11-20
目录

Java反射机制

# Java反射机制

# 1. 什么是反射机制 (Reflection)?

简单来说,Java 反射机制指的是程序在运行状态中:

  1. 自省 (Introspection): 对于任意一个类,都能够知道这个类的所有组成部分,包括它的字段(成员变量)、方法、构造器、父类、实现的接口、注解等等。
  2. 操作 (Manipulation): 对于任意一个对象,都能够动态地调用它的任意方法、访问或修改它的任意字段(甚至包括私有成员),以及动态地创建这个类的实例(通过构造器)。

这种在运行时动态获取类的信息以及动态调用对象方法/属性的功能,就被称为 Java 语言的反射机制。它打破了编译时期的静态绑定,赋予了程序在运行时发现和使用代码(以 .class 字节码文件形式存在)的能力。

# 2. 反射机制的核心作用与应用场景

反射机制的主要作用体现在其动态性上:

  • 动态加载类与创建对象: 在运行时根据配置文件、网络传输等信息加载未知的类,并创建其实例。许多框架的依赖注入 (DI) / 控制反转 (IoC) 容器就是基于此原理。
  • 动态调用方法与访问属性: 无需在编译时知道具体方法名或属性名,可以在运行时根据字符串等信息来调用方法或操作属性。常见于通用框架、工具类、代码生成器等。
  • 框架设计: 大量框架(如 Spring、Hibernate/MyBatis、JUnit 等)广泛使用反射来实现配置解析、对象关系映射、依赖注入、AOP(动态代理)、注解处理等核心功能。
  • IDE 和调试工具: IDE 的代码提示、自动完成、调试器中的变量查看等功能,都离不开反射来探知代码结构。
  • 注解处理器: 在编译期或运行时读取注解信息并进行相应处理。

核心包: 反射机制相关的核心类都位于 java.lang.reflect 包下。

# 3. 反射机制相关的核心类

理解 Java 反射,首先要掌握以下几个关键的类:

类名 含义与作用
java.lang.Class<T> 反射的入口点。代表一个类或接口在 JVM 中的字节码描述。每个加载到 JVM 中的类都有一个对应的 Class 对象。通过它可以获取类的所有信息。
java.lang.reflect.Method 代表类中的方法。封装了方法的所有信息(名称、返回类型、参数、修饰符、注解等),并提供了调用该方法的能力 (invoke)。
java.lang.reflect.Constructor<T> 代表类中的构造方法。封装了构造方法的所有信息(参数、修饰符、注解等),并提供了创建该类实例的能力 (newInstance)。
java.lang.reflect.Field 代表类中的字段 (成员变量),包括实例变量和静态变量。封装了字段的所有信息(名称、类型、修饰符、注解等),并提供了读取和设置该字段值的能力 (get, set)。

关键关系: 要获取 Method、Constructor 或 Field 对象,必须首先获得目标类的 Class 对象。Class 对象是进行反射操作的基础。

/**
 * 示例类:定义了字段、构造方法和普通方法
 */
public class User {
    // Field (字段)
    private int age;
    public String name = "DefaultName";

    // Constructor (构造方法)
    public User() { // 无参构造
        System.out.println("User 无参构造被调用");
    }

    public User(int age, String name) { // 有参构造
        this.age = age;
        this.name = name;
        System.out.println("User 有参构造被调用");
    }

    // Method (方法)
    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    private void privateMethod() { // 私有方法
        System.out.println("这是一个私有方法");
    }

    public static void staticMethod() { // 静态方法
        System.out.println("这是一个静态方法");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 4. 获取 Class 对象的三种主要方式

要对一个类进行反射操作,第一步就是获取该类的 Class 对象实例。常用的方式有三种:

获取方式 备注与特点
1. Class.forName("类的全限定名") 静态方法。最常用,通常用于加载驱动或在运行时根据字符串动态加载类。会触发类的初始化(执行静态代码块)。
2. 实例对象.getClass() 通过已有的对象实例获取其对应的 Class 对象。
3. 类名.class (或 基本类型.class) 静态属性。最安全、性能最好(编译时检查)。不会触发类的初始化。适用于编译时已知具体类的情况。

注意: 这三种方式最终返回的都是同一个类的唯一的 Class 对象实例。













 





 









 


 
 



















import java.util.Date;

/**
 * 演示获取 Class 对象的三种方式
 */
public class GetClassDemo {
    public static void main(String[] args) {
        try {
            // 方式一:使用 Class.forName("类的全限定名")
            // 需要提供类的完整包名 + 类名
            // 如果类不存在,会抛出 ClassNotFoundException
            // 这种方式会执行类的静态初始化块
            Class<?> userClass1 = Class.forName("com.example.reflect.User"); // User 类在 com.example.reflect 包下
            System.out.println("方式一获取: " + userClass1);

            // 方式二:使用 实例对象.getClass()
            // 需要先有一个该类的实例
            User userInstance = new User();
            Class<?> userClass2 = userInstance.getClass();
            System.out.println("方式二获取: " + userClass2);
            // 对于基本数据类型,需要使用包装类
            Date date = new Date();
            Class<?> dateClass = date.getClass();
            System.out.println("Date 对象的 Class: " + dateClass);

            // 方式三:使用 类名.class
            // 最直接、安全的方式,编译时即可检查类是否存在
            // 这种方式不会触发类的静态初始化块 (除非后续操作触发)
            Class<?> userClass3 = User.class;
            System.out.println("方式三获取: " + userClass3);
            // 也适用于基本数据类型和 void
            Class<Integer> intClass = int.class; // 获取 int 基本类型的 Class 对象
            Class<Void> voidClass = void.class;   // 获取 void 类型的 Class 对象
            System.out.println("int 的 Class: " + intClass);
            System.out.println("void 的 Class: " + voidClass);

            // 验证三种方式获取的是同一个 Class 对象实例
            System.out.println("userClass1 == userClass2 ? " + (userClass1 == userClass2)); // true
            System.out.println("userClass2 == userClass3 ? " + (userClass2 == userClass3)); // true

        } catch (ClassNotFoundException e) {
            System.err.println("类未找到: " + e.getMessage());
        }
    }
}

// User 类
// package com.example.reflect;
// public class User {
//     static { System.out.println("User 类静态初始化块执行"); }
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 5. 通过反射实例化对象 (Class.newInstance() - 已过时)

获取到 Class 对象后,一种创建该类实例的方式是调用 Class 对象的 newInstance() 方法。

语法: Object obj = class对象.newInstance();

重要限制:

  • 此方法只能调用类的公共 (public) 且无参数的构造方法。
  • 如果类没有公共的无参构造器,或者构造器是私有的,调用 newInstance() 会抛出 InstantiationException 或 IllegalAccessException。
  • 已过时 (Deprecated since Java 9): 从 Java 9 开始,Class.newInstance() 方法已被标记为过时。推荐使用 Constructor 类的 newInstance() 方法 (详见第 9 节),因为它功能更强大(可以调用任意参数的构造器,包括私有的)且能更好地处理异常。












 





















/**
 * 使用 Class.newInstance() (已过时) 实例化对象的示例
 */
public class ReflectNewInstanceDemo {
    public static void main(String[] args) {
        try {
            // 获取 User 类的 Class 对象
            Class<?> userClass = Class.forName("com.example.reflect.User"); // 替换为你的 User 类全路径

            // 使用 newInstance() 创建 User 对象
            // !!! 前提:User 类必须有一个 public 的无参构造方法 !!!
            // !!! 此方法自 Java 9 起已过时 !!!
            Object userObj = userClass.newInstance(); // 底层调用的是无参构造器

            // userObj 的编译时类型是 Object,如果需要调用 User 特有方法,需要强制类型转换
            // if (userObj instanceof User) {
            //     User user = (User) userObj;
            //     // ... 可以调用 user 的方法 ...
            // }

            System.out.println("通过反射 (Class.newInstance) 创建的对象: " + userObj);

        } catch (ClassNotFoundException e) {
            System.err.println("类未找到: " + e.getMessage());
        } catch (InstantiationException e) {
            // 如果类是抽象类、接口、数组类,或者没有无参构造器时抛出
            System.err.println("实例化异常 (可能缺少无参构造器或类无法实例化): " + e.getMessage());
        } catch (IllegalAccessException e) {
            // 如果无参构造器不是 public 的,抛出此异常
            System.err.println("非法访问异常 (无参构造器不可访问): " + e.getMessage());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 6. 妙用 Class.forName():仅触发类加载与静态初始化

有时我们只想加载一个类并执行其静态代码块 (static initializer block),而不需要创建该类的实例。这在某些场景下非常有用,例如经典的 JDBC 驱动加载。

使用 Class.forName("完整类名") 方法可以实现这个目的:

  1. 触发类加载: JVM 会查找并加载指定的 .class 文件。
  2. 执行静态初始化: 在类加载过程的初始化阶段,该类中定义的静态代码块会被自动执行。静态代码块通常用于初始化静态变量、注册驱动或执行只需进行一次的设置操作。
  3. 不创建实例: 该方法本身并不会创建类的对象实例。
/**
 * 演示一个仅包含静态代码块的类
 */
public class DriverInitializer {
    // 静态代码块:在类被加载到 JVM 并初始化时执行,且只执行一次
    static {
        System.out.println("DriverInitializer 类的静态代码块正在执行...");
        // 这里可以放置一些初始化逻辑,例如注册驱动、加载配置文件等
        // 模拟注册操作
        System.out.println("模拟驱动注册完成。");
    }

    // 类可以有其他成员,但 Class.forName 不会直接调用它们
    public DriverInitializer() {
        System.out.println("DriverInitializer 构造方法执行 (不会被 Class.forName 调用)");
    }

    public static void staticMethod() {
        System.out.println("DriverInitializer 静态方法执行 (不会被 Class.forName 调用)");
    }
}

/**
 * 演示使用 Class.forName 触发类加载和静态初始化
 */
public class ClassForNameInitDemo {
    public static void main(String[] args) {
        System.out.println("准备加载 DriverInitializer 类...");
        try {
            // 调用 Class.forName(),只指定类名
            // 这将导致 DriverInitializer 类被加载、链接和初始化
            // 其静态代码块会在此过程中被执行
            Class<?> loadedClass = Class.forName("com.example.reflect.DriverInitializer"); // 替换为实际包名

            System.out.println("DriverInitializer 类已加载。Class 对象: " + loadedClass);
            // 注意:此时 DriverInitializer 的构造方法和静态方法都没有被调用

            // 如果需要实例,后续可以再创建 (但这非 Class.forName 的主要目的)
            // Object instance = loadedClass.getDeclaredConstructor().newInstance();

        } catch (ClassNotFoundException e) {
            System.err.println("类未找到: " + e.getMessage());
        }
        // } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
        //    System.err.println("创建实例时出错: " + e.getMessage());
        // }
        System.out.println("主程序结束。");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

JDBC 驱动加载: 在早期 JDBC 中,Class.forName("com.mysql.jdbc.Driver") 就是利用这个机制来加载 MySQL 驱动类,驱动类的静态代码块会执行 DriverManager.registerDriver(new Driver()) 将自己注册到 DriverManager 中。

# 7. 反射之 Field:访问和修改字段

Field 类代表类的成员变量(字段)。通过反射可以获取 Field 对象,然后读取或修改任意对象(包括实例对象和静态字段所属的类对象)的该字段的值,即使是私有字段。

# Class 类中与 Field 相关的方法

方法名 描述
getFields() 返回一个 Field 数组,包含该类及其所有父类中声明为 public 的字段。
getDeclaredFields() 返回一个 Field 数组,包含在该类中直接声明的所有字段(包括 private, protected, package-private, public),不包括父类的字段。
getField(String name) 根据字段名 name 获取一个 public 的字段(可能在当前类或父类中)。如果字段不存在或不是 public,抛出 NoSuchFieldException。
getDeclaredField(String name) 根据字段名 name 获取在当前类中直接声明的指定字段(不限访问修饰符)。如果字段不存在于当前类,抛出 NoSuchFieldException。 常用于获取私有字段。
getModifiers() (在 Field 类中) 返回字段的修饰符(如 public, private, static, final)对应的整数表示。通常配合 java.lang.reflect.Modifier.toString() 使用。

# Field 类中的核心方法

方法名 描述
getName() 返回字段的名称 (String)。
getType() 返回字段的类型对应的 Class 对象 (例如 int.class, String.class)。
getModifiers() 返回字段修饰符的整数表示。
set(Object obj, Object value) 设置字段的值。obj 是要修改字段的对象实例 (如果是静态字段,obj 传入 null);value 是要设置的新值。需要注意类型匹配和访问权限。
get(Object obj) 读取字段的值。obj 是要读取字段的对象实例 (如果是静态字段,obj 传入 null)。返回字段的当前值 (Object 类型)。需要注意访问权限。
setAccessible(boolean flag) 打破封装。核心方法!默认 false。如果设为 true,则可以无视 Java 的访问控制(private, protected),直接访问或修改私有字段。

# 通过反射设置和读取对象属性 (包括私有)

import java.lang.reflect.Field;

/**
 * 演示通过反射访问和修改对象字段(包括私有字段)
 */
public class ReflectFieldDemo {

    public static void main(String[] args) {
        try {
            // 1. 获取 Student 类的 Class 对象
            Class<?> studentClass = Class.forName("com.example.reflect.Student"); // 替换为你的 Student 类全路径

            // 2. 创建 Student 对象实例 (假设有无参构造)
            Object studentObj = studentClass.getDeclaredConstructor().newInstance(); // 推荐使用 Constructor 创建

            // --- 操作 public 字段 'grade' ---
            System.out.println("--- 操作 public 字段 'grade' ---");
            // 2.1 获取名为 'grade' 的 public 字段
            Field gradeField = studentClass.getField("grade"); // getField 只能获取 public 的
            System.out.println("获取到字段: " + gradeField.getName() + ", 类型: " + gradeField.getType().getSimpleName());
            // 2.2 设置 'grade' 字段的值
            // set(对象实例, 值)
            gradeField.set(studentObj, 3); // 设置年级为 3
            // 2.3 读取 'grade' 字段的值
            // get(对象实例)
            Object gradeValue = gradeField.get(studentObj);
            System.out.println("设置后 grade 的值: " + gradeValue); // 输出 3

            // --- 操作 private 字段 'name' ---
            System.out.println("\n--- 操作 private 字段 'name' ---");
            // 3.1 获取名为 'name' 的 private 字段
            //     必须使用 getDeclaredField,因为 getField 找不到 private 字段
            Field nameField = studentClass.getDeclaredField("name");
            System.out.println("获取到字段: " + nameField.getName() + ", 类型: " + nameField.getType().getSimpleName());
            // 3.2 !!! 打破封装(关键步骤)!!!
            //     如果不设置 accessible 为 true,访问私有字段会抛出 IllegalAccessException
            nameField.setAccessible(true);
            // 3.3 设置私有 'name' 字段的值
            nameField.set(studentObj, "张三");
            // 3.4 读取私有 'name' 字段的值
            Object nameValue = nameField.get(studentObj);
            System.out.println("设置后 name 的值: " + nameValue); // 输出 张三

            // --- 操作 private static 字段 'school' ---
             System.out.println("\n--- 操作 private static 字段 'school' ---");
             Field schoolField = studentClass.getDeclaredField("school");
             System.out.println("获取到静态字段: " + schoolField.getName());
             schoolField.setAccessible(true); // 同样需要打破封装
             // 3.5 设置静态字段的值,第一个参数传 null
             schoolField.set(null, "清华大学");
             // 3.6 读取静态字段的值,第一个参数传 null
             Object schoolValue = schoolField.get(null);
             System.out.println("设置后静态 school 的值: " + schoolValue); // 输出 清华大学

        } catch (Exception e) { // 捕获所有可能的反射相关异常
            e.printStackTrace();
        }
    }
}

// Student 类
// package com.example.reflect;
// class Student {
//     public int grade; // Public 字段
//     private String name; // Private 字段
//     private static String school = "默认学校"; // Private static 字段
//
//     public Student() {} // 无参构造
//
//     @Override
//     public String toString() {
//         return "Student{" + "grade=" + grade + ", name='" + name + '\'' + ", school='" + school + '\'' + '}';
//     }
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

注意 setAccessible(true):

  • 这是反射强大之处,但也破坏了类的封装性,可能违反设计意图。
  • 可能带来安全风险,允许外部代码修改不应被修改的内部状态。
  • 在某些安全管理器 (Security Manager) 环境下,调用 setAccessible(true) 可能会失败。
  • 应谨慎使用,通常只在框架、测试或特定工具开发中使用。

# 反编译 Field(了解)

可以通过反射获取一个类的所有字段信息,模拟反编译的效果。

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

/**
 * 通过反射反编译类的字段信息 (了解即可)
 */
public class ReflectDecompileField {
    public static void main(String[] args) {
        try {
            // 要反编译的类
            Class<?> targetClass = Class.forName("com.example.reflect.Student"); // 替换为你的类名

            StringBuilder sb = new StringBuilder();

            // 获取类的修饰符 (public, class 等) 和简单名称
            sb.append(Modifier.toString(targetClass.getModifiers()))
              .append(" class ")
              .append(targetClass.getSimpleName())
              .append(" {\n");

            // 获取类中直接声明的所有字段 (包括私有)
            Field[] declaredFields = targetClass.getDeclaredFields();

            for (Field field : declaredFields) {
                sb.append("\t"); // 缩进

                // 获取字段的修饰符 (public, private, static, final 等)
                sb.append(Modifier.toString(field.getModifiers()));
                if (field.getModifiers() != 0) { // 如果有修饰符,加个空格
                    sb.append(" ");
                }

                // 获取字段的类型 (简单名称)
                sb.append(field.getType().getSimpleName());
                sb.append(" ");

                // 获取字段的名称
                sb.append(field.getName());
                sb.append(";\n"); // 添加分号和换行
            }

            sb.append("}"); // 类结束

            System.out.println(sb.toString()); // 打印反编译结果

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 8. 反射之 Method:动态调用方法

Method 类代表类中的方法。通过反射可以获取 Method 对象,然后动态地调用任意对象(或静态方法所属的类)的该方法,即使是私有方法。

# Class 类中与 Method 相关的方法

方法名 描述
getMethods() 返回一个 Method 数组,包含该类及其所有父类中声明为 public 的方法。
getDeclaredMethods() 返回一个 Method 数组,包含在该类中直接声明的所有方法(包括 private, protected, package-private, public),不包括父类的方法。
getMethod(String name, Class<?>... parameterTypes) 根据方法名 name 和参数类型列表 parameterTypes 获取一个 public 的方法(可能在当前类或父类中)。如果方法不存在或不是 public,抛出 NoSuchMethodException。
getDeclaredMethod(String name, Class<?>... parameterTypes) 根据方法名 name 和参数类型列表 parameterTypes 获取在当前类中直接声明的指定方法(不限访问修饰符)。如果方法不存在于当前类,抛出 NoSuchMethodException。 常用于获取私有方法。

# Method 类中的核心方法

方法名 描述
getName() 返回方法名 (String)。
getReturnType() 返回方法返回值类型的 Class 对象。
getParameterTypes() 返回一个 Class 数组,包含方法所有参数的类型。
getModifiers() 返回方法修饰符的整数表示。
invoke(Object obj, Object... args) 动态调用方法。obj 是调用该方法的对象实例 (如果是静态方法,obj 传入 null);args 是传递给方法的实际参数列表 (可变参数)。返回方法的返回值 (Object 类型,如果是 void 方法则返回 null)。需要注意访问权限和参数匹配。
setAccessible(boolean flag) 打破封装。同样可以用于方法,设为 true 后可以调用私有方法。

# 通过反射调用对象方法 (包括私有和静态)

import java.lang.reflect.Method;

/**
 * 演示通过反射调用对象方法
 */
public class ReflectMethodDemo {
    public static void main(String[] args) {
        try {
            // 1. 获取 UserService 类的 Class 对象
            Class<?> userServiceClass = Class.forName("com.example.reflect.UserService"); // 替换为你的类全路径

            // 2. 创建 UserService 对象实例
            Object userServiceObj = userServiceClass.getDeclaredConstructor().newInstance();

            // --- 调用 public 方法 login(String, String) ---
            System.out.println("--- 调用 public 方法 login ---");
            // 3.1 获取 login 方法的 Method 对象
            //     需要提供方法名和参数类型列表 (Class 对象)
            Method loginMethod = userServiceClass.getMethod("login", String.class, String.class);
            // 3.2 调用方法:invoke(对象实例, 参数...)
            Object loginResult = loginMethod.invoke(userServiceObj, "admin", "password123");
            System.out.println("login 方法返回值: " + loginResult); // 输出 true 或 false

            // --- 调用 private 方法 checkCredentials(String) ---
            System.out.println("\n--- 调用 private 方法 checkCredentials ---");
            // 4.1 获取私有方法的 Method 对象,必须用 getDeclaredMethod
            Method checkMethod = userServiceClass.getDeclaredMethod("checkCredentials", String.class);
            // 4.2 !!! 打破封装 !!!
            checkMethod.setAccessible(true);
            // 4.3 调用私有方法
            Object checkResult = checkMethod.invoke(userServiceObj, "secret-password");
            System.out.println("checkCredentials 方法返回值: " + checkResult);

            // --- 调用 public static 方法 validateInput(String) ---
            System.out.println("\n--- 调用 public static 方法 validateInput ---");
            // 5.1 获取静态方法的 Method 对象
            Method validateMethod = userServiceClass.getMethod("validateInput", String.class);
            // 5.2 调用静态方法:invoke 的第一个参数传 null
            Object validateResult = validateMethod.invoke(null, "valid-input");
            System.out.println("validateInput 方法返回值: " + validateResult);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// UserService 类
// package com.example.reflect;
// class UserService {
//     public boolean login(String username, String password) {
//         System.out.println("调用 login 方法: username=" + username + ", password=" + password);
//         return "admin".equals(username) && checkCredentials(password);
//     }
//
//     private boolean checkCredentials(String password) {
//         System.out.println("调用私有 checkCredentials 方法: password=" + password);
//         return "password123".equals(password) || "secret-password".equals(password);
//     }
//
//     public static boolean validateInput(String input) {
//         System.out.println("调用静态 validateInput 方法: input=" + input);
//         return input != null && !input.isEmpty();
//     }
//
//     public UserService(){} // 无参构造
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

invoke() 方法注意事项:

  • 第一个参数 obj: 对于实例方法,必须传入调用该方法的对象实例;对于静态方法,传入 null。
  • 第二个参数 args: 传递给方法的实际参数列表。如果方法没有参数,则不传或传 null;如果方法有参数,必须按照方法签名的顺序和类型传入对应的参数值。
  • 返回值: invoke() 方法返回 Object 类型,表示被调用方法的实际返回值。如果被调用方法返回 void,则 invoke() 返回 null。如果被调用方法返回基本类型,invoke() 会返回其对应的包装类实例。
  • 异常: 如果被调用的方法本身抛出异常,这个异常会被包装在 InvocationTargetException 中抛出。需要通过 getCause() 获取原始异常。

# 反编译 Method(了解)

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter; // Java 8+ 获取参数名

/**
 * 通过反射反编译类的方法信息 (了解即可)
 */
public class ReflectDecompileMethod {
    public static void main(String[] args) {
        try {
            // 要反编译的类
            Class<?> targetClass = Class.forName("com.example.reflect.UserService"); // 替换为你的类名

            StringBuilder sb = new StringBuilder();
            sb.append(Modifier.toString(targetClass.getModifiers()))
              .append(" class ")
              .append(targetClass.getSimpleName())
              .append(" {\n");

            // 获取所有直接声明的方法
            Method[] declaredMethods = targetClass.getDeclaredMethods();

            for (Method method : declaredMethods) {
                sb.append("\t"); // 缩进

                // 方法修饰符
                sb.append(Modifier.toString(method.getModifiers())).append(" ");
                // 返回值类型
                sb.append(method.getReturnType().getSimpleName()).append(" ");
                // 方法名
                sb.append(method.getName());
                sb.append("(");

                // 参数列表
                Class<?>[] parameterTypes = method.getParameterTypes();
                // Java 8+ 可以尝试获取参数名 (需要在编译时开启 -parameters 选项)
                Parameter[] parameters = method.getParameters();

                for (int i = 0; i < parameterTypes.length; i++) {
                    sb.append(parameterTypes[i].getSimpleName());
                    // 尝试添加参数名
                    if (parameters != null && i < parameters.length && parameters[i].isNamePresent()) {
                        sb.append(" ").append(parameters[i].getName());
                    } else {
                       sb.append(" arg").append(i); // 默认参数名
                    }
                    if (i < parameterTypes.length - 1) {
                        sb.append(", ");
                    }
                }
                sb.append(") {\n\t\t// method body\n\t}\n"); // 方法体用注释代替
            }

            sb.append("}");
            System.out.println(sb.toString());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 9. 反射之 Constructor:动态创建对象

Constructor 类代表类的构造方法。通过反射获取 Constructor 对象后,可以调用其 newInstance() 方法来创建类的实例,这种方式比 Class.newInstance() 更强大、更推荐。

# Class 类中与 Constructor 相关的方法

方法名 描述
getConstructors() 返回一个 Constructor 数组,包含该类所有 public 的构造方法。
getDeclaredConstructors() 返回一个 Constructor 数组,包含在该类中直接声明的所有构造方法(包括 private, protected, package-private, public)。
getConstructor(Class<?>... parameterTypes) 根据参数类型列表 parameterTypes 获取一个 public 的构造方法。如果不存在或不是 public,抛出 NoSuchMethodException。
getDeclaredConstructor(Class<?>... parameterTypes) 根据参数类型列表 parameterTypes 获取在当前类中直接声明的指定构造方法(不限访问修饰符)。如果不存在,抛出 NoSuchMethodException。 常用于获取私有构造器。

# Constructor 类中的核心方法

方法名 描述
getName() 返回构造方法名 (对于构造器,总是返回类的全限定名)。
getParameterTypes() 返回一个 Class 数组,包含构造方法所有参数的类型。
getModifiers() 返回构造方法修饰符的整数表示。
newInstance(Object... initargs) 创建类的新实例。initargs 是传递给构造方法的实际参数列表。返回创建的对象实例 (类型为 T)。需要注意访问权限和参数匹配。这是推荐的反射创建对象方式。
setAccessible(boolean flag) 打破封装。设为 true 后可以调用私有构造方法来创建实例。

# 创建对象的两步骤 (使用 Constructor)

  1. 获取 Constructor 对象: 使用 clazz.getDeclaredConstructor(paramTypes...) 获取指定参数列表的构造方法(即使是私有的)。如果需要调用无参构造,则不传参数 clazz.getDeclaredConstructor()。
  2. 调用 newInstance() 创建实例: 调用获取到的 Constructor 对象的 newInstance(args...) 方法,并传入构造方法所需的实际参数。

# 示例:通过反射调用构造方法实例化对象

import java.lang.reflect.Constructor;

/**
 * 演示通过反射调用构造方法创建对象 (推荐方式)
 */
public class ReflectConstructorDemo {
    public static void main(String[] args) {
        try {
            // 1. 获取 Vip 类的 Class 对象
            Class<?> vipClass = Class.forName("com.example.reflect.Vip"); // 替换为你的 Vip 类全路径

            // --- 调用 public 无参构造方法 ---
            System.out.println("--- 调用 public 无参构造 ---");
            // 1.1 获取无参构造方法对象
            Constructor<?> noArgConstructor = vipClass.getDeclaredConstructor(); // 无参,不传参数类型
            // 1.2 调用 newInstance() 创建对象 (无参,不传参数)
            Object vipObj1 = noArgConstructor.newInstance();
            System.out.println("通过无参构造创建: " + vipObj1);

            // --- 调用 public 有参构造方法 ---
            System.out.println("\n--- 调用 public 有参构造 ---");
            // 2.1 获取有参构造方法对象,需要指定参数类型
            Constructor<?> argsConstructor = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
            // 2.2 调用 newInstance() 创建对象,传入匹配的参数
            Object vipObj2 = argsConstructor.newInstance(101, "李四", "2000-05-20", true);
            System.out.println("通过有参构造创建: " + vipObj2);

            // --- 调用 private 构造方法 (假设存在) ---
            System.out.println("\n--- 调用 private 构造 (假设存在) ---");
            try {
                // 3.1 获取私有构造方法对象
                Constructor<?> privateConstructor = vipClass.getDeclaredConstructor(String.class); // 有一个 private Vip(String name)
                // 3.2 !!! 打破封装 !!!
                privateConstructor.setAccessible(true);
                // 3.3 调用 newInstance()
                Object vipObj3 = privateConstructor.newInstance("私有构造创建");
                System.out.println("通过私有构造创建: " + vipObj3);
            } catch (NoSuchMethodException e) {
                System.out.println("未找到指定的私有构造方法: " + e.getMessage());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// Vip 类
// package com.example.reflect;
// import java.util.Date;
// class Vip {
//     int no;
//     String name;
//     String birth; // 用 String 方便演示
//     boolean sex;
//
//     public Vip() { System.out.println("Vip public 无参构造"); }
//
//     public Vip(int no, String name, String birth, boolean sex) {
//         this.no = no; this.name = name; this.birth = birth; this.sex = sex;
//         System.out.println("Vip public 有参构造");
//     }
//
//     private Vip(String name) { // 私有构造
//         this.name = name;
//         System.out.println("Vip private 构造 (name)");
//     }
//
//     @Override
//     public String toString() { /* ... toString 实现 ... */ return "...";}
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

笔记:`Class.newInstance()` vs `Constructor.newInstance()`

  1. Class.newInstance() (已过时):
    • 只能调用 public 无参 构造器。
    • 异常处理不够明确(将构造器内部异常包装为 InvocationTargetException 嵌套在 InstantiationException 或 IllegalAccessException 中)。
  2. Constructor.newInstance() (推荐):
    • 可以调用任意访问权限(配合 setAccessible)、任意参数的构造器。
    • 异常处理更直接:如果构造器内部抛出异常,会直接包装在 InvocationTargetException 中抛出。
    • 类型安全:返回的是泛型 T,减少强制类型转换。

因此,现代 Java 开发中应始终优先使用 Constructor.newInstance() 来通过反射创建对象。

# 反编译 Constructor(了解)

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

/**
 * 通过反射反编译类的构造方法信息 (了解即可)
 */
public class ReflectDecompileConstructor {
    public static void main(String[] args) {
        try {
            // 要反编译的类
            Class<?> targetClass = Class.forName("com.example.reflect.Vip"); // 替换为你的类名

            StringBuilder sb = new StringBuilder();
            sb.append(Modifier.toString(targetClass.getModifiers()))
              .append(" class ")
              .append(targetClass.getSimpleName())
              .append(" {\n");

            // 获取所有直接声明的构造方法
            Constructor<?>[] declaredConstructors = targetClass.getDeclaredConstructors();

            for (Constructor<?> constructor : declaredConstructors) {
                sb.append("\t"); // 缩进

                // 构造方法修饰符
                sb.append(Modifier.toString(constructor.getModifiers())).append(" ");
                // 构造方法名 (就是类名)
                sb.append(targetClass.getSimpleName());
                sb.append("(");

                // 参数列表
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                Parameter[] parameters = constructor.getParameters(); // Java 8+

                for (int i = 0; i < parameterTypes.length; i++) {
                    sb.append(parameterTypes[i].getSimpleName());
                     if (parameters != null && i < parameters.length && parameters[i].isNamePresent()) {
                        sb.append(" ").append(parameters[i].getName());
                    } else {
                       sb.append(" arg").append(i);
                    }
                    if (i < parameterTypes.length - 1) {
                        sb.append(", ");
                    }
                }
                sb.append(") {\n\t\t// constructor body\n\t}\n");
            }

            sb.append("}");
            System.out.println(sb.toString());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 10. 反射中如何区分类的成员

在进行反射操作时,准确地定位到你想要的类成员(字段、方法、构造器)至关重要。它们的区分方式略有不同:

  1. 字段 (Field): 主要通过字段名称 (Name) 来唯一识别。在同一个类中,字段名不能重复。获取 Field 对象通常使用 getDeclaredField(String name)。
  2. 方法 (Method): 由于 Java 支持方法重载 (Overloading),仅靠方法名不足以唯一确定一个方法。因此,方法的唯一标识是方法名 (Name) + 参数类型列表 (Parameter Types)。获取 Method 对象需要同时提供这两者,使用 getDeclaredMethod(String name, Class<?>... parameterTypes)。
  3. 构造方法 (Constructor): 构造方法没有显式名称(其名称隐含为类名)。它们的区分完全依赖于参数类型列表 (Parameter Types)。获取 Constructor 对象使用 getDeclaredConstructor(Class<?>... parameterTypes)。

理解这一点对于正确使用 getDeclaredField, getDeclaredMethod, getDeclaredConstructor 这三个核心查找方法至关重要。

# 11. 获取类的父类与实现的接口

反射还提供了获取一个类的继承和实现关系信息的能力。

  1. 获取父类: Class<?> getSuperclass()

    • 返回表示当前 Class 对象所代表的那个类的直接父类的 Class 对象。
    • 如果当前类是 Object 类、一个接口、一个基本类型或 void,则返回 null。
  2. 获取实现的接口: Class<?>[] getInterfaces()

    • 返回一个 Class 数组,包含当前 Class 对象所代表的类直接实现的所有接口。
    • 顺序与类声明中 implements 子句后的接口顺序一致。
    • 如果类没有实现任何接口,返回一个长度为 0 的数组。
    • 注意: 此方法不返回父类实现的接口。如果需要获取所有接口(包括继承的),需要递归遍历父类的 getInterfaces()。
/**
 * 演示获取父类和接口
 */
public class ReflectInheritanceDemo {
    public static void main(String[] args) {
        try {
            // 以 ArrayList 为例
            Class<?> arrayListClass = Class.forName("java.util.ArrayList");

            // 获取父类
            Class<?> superclass = arrayListClass.getSuperclass();
            if (superclass != null) {
                System.out.println(arrayListClass.getSimpleName() + " 的直接父类是: " + superclass.getName());
                // 输出: ArrayList 的直接父类是: java.util.AbstractList
            } else {
                System.out.println(arrayListClass.getSimpleName() + " 没有直接父类 (可能是 Object 或接口)。");
            }

            // 获取直接实现的接口
            Class<?>[] interfaces = arrayListClass.getInterfaces();
            if (interfaces.length > 0) {
                System.out.println(arrayListClass.getSimpleName() + " 直接实现的接口有:");
                for (Class<?> intf : interfaces) {
                    System.out.println("\t- " + intf.getName());
                    // 输出可能包含:
                    // - java.util.List
                    // - java.util.RandomAccess
                    // - java.lang.Cloneable
                    // - java.io.Serializable
                }
            } else {
                System.out.println(arrayListClass.getSimpleName() + " 没有直接实现任何接口。");
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 12. 总结与思考

Java 反射机制是一把双刃剑。它赋予了程序在运行时进行自我检查和动态操作的强大能力,是实现许多高级功能和框架的基础。

优点:

  • 动态性: 增加了程序的灵活性和扩展性,可以在运行时加载、探知、使用编译时未知的类。
  • 通用性: 可以编写更通用的代码,适用于多种不同的类型。框架设计常用。

缺点:

  • 性能开销: 反射操作(如方法调用 invoke)通常比直接代码调用慢得多,因为它涉及更多的查找、检查和间接调用步骤。不应在性能敏感的核心路径上滥用。
  • 破坏封装: setAccessible(true) 可以绕过访问控制,访问和修改私有成员,破坏了类的封装性,可能导致代码难以理解和维护,并引入安全风险。
  • 类型安全: 反射操作在编译时缺乏类型检查,许多问题(如类型不匹配、方法/字段不存在)需要到运行时才能发现,可能导致 RuntimeException。
  • 代码复杂度: 反射相关的代码通常比直接调用更冗长、更复杂,可读性较差。

何时使用:

  • 开发通用框架(如 Spring IoC/AOP, ORM 框架, 序列化库)。
  • 编写需要动态加载类或执行逻辑的工具(如插件系统、代码生成器)。
  • 实现注解处理器。
  • 测试或调试工具。

总而言之,反射是 Java 提供的强大武器,但应明智、谨慎地使用。理解其原理和潜在成本,才能在合适的场景下发挥其最大价值,同时避免其带来的负面影响。


以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是scholar,一个在互联网行业的小白,立志成为更好的自己。

如果你想了解更多关于scholar (opens new window),可以关注公众号-书生带你学编程,后面文章会首先同步至公众号。

公众号封面

编辑此页 (opens new window)
上次更新: 2025/04/06, 10:15:45
Java枚举
Java代理模式

← Java枚举 Java代理模式→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式