Java的反射

java反射

本文将从反射的基本概念、核心 API、使用场景到优缺点,全面解析 Java 反射机制。

什么是Java反射

Oracle 官方文档对 Java 反射核心能力的定义如下:

Reflection allows programmatic access to information about the fields, methods, and constructors of loaded classes, and the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within encapsulation and security restrictions.

Java 反射机制(Reflection)是 JDK 内置的一套标准化 API(核心类库分布在 java.lang 包和 java.lang.reflect 包),在遵循 JVM 安全策略的前提下,允许程序在运行时完成两类核心操作:一是获取已加载至 JVM 的类的完整元信息,包括类的字段、方法、构造器、继承关系、注解、访问修饰符等;二是基于该元信息动态操作类的实例成员,即使是被封装访问控制符(如 private)修饰的成员,也可通过显式关闭访问检查机制突破封装限制。本质上,反射绕开了 Java 编译期的静态绑定机制,将类的解析、实例化及成员调用等操作延迟至运行时完成。

Java 反射是 Java 语言的核心特性之一,其赋予的运行时动态性使其成为各类框架开发(如 Spring IOC/AOP、MyBatis 映射器解析)、注解处理器、JDK 动态代理等场景的技术基石。

反射的基本使用步骤

Java 反射的核心操作始终围绕 Class 对象(JVM 中每个已加载类的字节码文件对应的唯一实例)展开,所以首先要了解如何获取 class 字节码文件的对象,再了解如何从字节码文件中以编程方式访问和操作到成员变量、成员方法和构造方法信息。

反射的核心API

java.lang.Class:代表正在运行的 Java 应用程序中的实例,是获取类元信息、开展所有反射操作的唯一入口;

java.lang.reflect.Constructor:代表类的构造器,用于动态创建类的实例;

java.lang.reflect.Method:代表类的方法,用于动态调用方法;

java.lang.reflect.Field:代表类的字段,用于动态 获取 / 修改 字段值;

java.lang.reflect.Modifier:修饰符解析工具类,通过静态方法解析修饰符标识。

获取 Class 对象

反射的本质是程序在运行时操作类的元信息,而 Class 对象是 JVM 为每个类生成的、包含该类全部元信息的核心载体。因此,获取 Class 对象是所有反射操作的必要前提,只有拿到这个元信息载体,才能进一步访问类的内部信息。

获取 Class 对象有三种方式,这三种方式恰好对应了 Java 程序的三个主要阶段(源代码阶段、加载阶段、运行阶段),具体如下:

方式 1:Class.forName("全类名")

Class.forName("全类名") 这种方式通过类的全类名(包名+类名)动态获取Class对象,无需依赖类的实例或类的显式引用,仅需知道类名即可。常用于动态加载类如配置文件中指定类名,运行时动态加载,典型场景是JDBC加载数据库驱动(Class.forName("com.mysql.cj.jdbc.Driver"))。

对应 Java 程序的源代码阶段

源代码阶段是指开发者编写.java源代码,通过javac编译为.class字节码文件的阶段,此时程序仅以文本形式存在,未进入 JVM。

java 复制代码
// 获取 java.lang.String 的 Class 对象
Class<?> cls = Class.forName("java.lang.String");

方式 2:类名.class

类名.class 这种方式通过类名直接访问 class 属性获取 Class 对象,无需实例化对象也无需知道全类名,编译期即可确定类型,典型应用场景是在各种框架的配置类中作为方法参数传递。

对应 Java 程序的加载阶段

类加载阶段是指 JVM 通过类加载器将.class字节码文件加载到内存,最终在方法区生成一个唯一的Class对象的过程,需要注意此时类尚未实例化。

java 复制代码
// 获取 String 的 Class 对象
Class<String> cls = String.class;

方式 3:对象.getClass()

对象.getClass() 这种方式通过对象实例调用从 Object 类继承而来的 getClass() 方法该对象运行时类的 Class 对象。需要注意必须先创建对象实例才能调用、适用于所有 Java 对象、返回的是运行时类型。典型应用场景是日志记录中输出对象类型、类型检查时获取实际类型。

对应 Java 程序的运行阶段

运行阶段是指 JVM 执行已加载类的字节码,创建对象、调用方法、操作数据的阶段,类已加载初始化且已创建对象实例,正在内存中运行,此时Class对象已存在于方法区当中。

java 复制代码
String str = "hello";
Class<? extends String> cls = str.getClass();

通过Class对象操作类成员

获取 Class 对象后,即可通过反射 API 从字节码文件中获取和操作成员变量、成员方法和构造方法

获取构造方法并创建对象

Class类中用于获取构造方法的方法

  • Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
  • Constructor<?>[] getDeclaredConstuctors():返回所有构造方法对象的数组
  • Constructor<T> getConstructor(Class<?>...parameterTypes):返回单个公共构造方法对象
  • Constructor<T> getDeclaredConstructor(Class<?>..parameterTypes):返回单个构造方法对象

Constructor类中用于创建对象的方法:
T newInstance(Object...initargs):根据指定的构造方法创建对象
setAccessible(boolean flag):设置为true,表示暴力反射,关闭访问检查,允许访问私有成员

下面进行代码演示,我们来创建一个自定义的类 Student 类,在该类中实现了 Comparable<Student> 接口,包含成员变量、构造方法、普通方法以及重写的接口方法

java 复制代码
public class Student implements Comparable<Student>{

    private String name;
    public int age;
    int height;
    protected String sex;

    public Student(){}

    private Student(String name,int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    Student(int age){
        this.age = age;
    }

    public void start(String name,Integer age){
        System.out.println("开始");
    }

    private void run(int age){
        System.out.println("---------");
    }

    protected void stop(float height,char sex){
        System.out.println("结束");
    }

    void show(){
        System.out.println("show");
    }
    
    @Override
    public String toString() {
        return "Student{" +"name='" + name + '\'' +", age=" + age +", height=" + height +'}';
    }
    
    @Override
    public int compareTo(Student o) {
        return 0;
    }
}

我们来看如何通过反射机制获取类的构造方法并创建对象

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.security.Policy.Parameters;


public class Test {
    public static void main(String[] args) throws Exception {
        
    	//获取class字节码文件
    	Class clazz = Student.class;
    	
    	System.out.println("获取所有构造方法:");
    	Constructor[] cons = clazz.getDeclaredConstructors();
    	for(Constructor con : cons) {
    		System.out.println(con);
    	}
    	
    	System.out.println("\n获取空参构造方法:");
        Constructor declaredConstructor1 = clazz.getConstructor();
        System.out.println(declaredConstructor1);
        
        System.out.println("\n空参构造方法创建的对象:");
        Student student1 = (Student) declaredConstructor1.newInstance();
        System.out.println(student1);
        
        System.out.println("\n获取三个参数构造方法:");
        Constructor declaredConstructor2 = clazz.getDeclaredConstructor(String.class,int.class,int.class);
        System.out.println(declaredConstructor2);

        
        System.out.println("\n获取构造方法的每个参数:");
        Parameter[] Parameters = declaredConstructor2.getParameters();
        for(Parameter Parameter : Parameters) {
        	System.out.println(Parameter);
        }
        
        System.out.println("\n返回三个参数构造的对象:");
        declaredConstructor2.setAccessible(true);
        Student student2 =  (Student) declaredConstructor2.newInstance("张三", 10, 180);
        System.out.println(student2);
    }
}

输出结果:

获取成员变量并修改值

Class类中用于获取成员变量的方法:

  • Field[] getFields():返回所有公共成员变量对象的数组
  • Field[] getDeclaredFields():返回所有成员变量对象的数组
  • Field getField(String name):返回单个公共成员变量对象
  • Field getDeclaredField(String name):返回单个成员变量对象

Field类中用于并修改值的方法:
void set(Object obj, Object value) 赋值
Object get(Object obj) 获取值

java 复制代码
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;

public class Test {
    public static void main(String[] args) throws Exception {
        
    	//获取class字节码文件
    	Class clazz = Student.class;
    	
    	System.out.println("获取所有的成员变量:");
    	Field[] Fields = clazz.getDeclaredFields();
    	for(Field Field:Fields) {
    		System.out.println(Field);
    	}
    	
    	System.out.println("\n获取单个成员变量:");
        Field name = clazz.getDeclaredField("name");
        System.out.println(name);
        
        System.out.println("\n获取成员变量的名字:");
        String n = name.getName();
        System.out.println(n);
        
        
        System.out.println("\n获取成员变量的数据类型:");
        Class type = name.getType();
        System.out.println(type);
        
        
        System.out.println("\n修改和获取单个成员变量对象:");
        Student student = new Student(20);
        name.setAccessible(true);
        name.set(student,"小花");
        System.out.println(name);
        System.out.println(student);
    }
}

输出结果:

获取成员方法并调用

Class类中用于获取成员方法的方法:

  • Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
  • Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
  • Method getMethod(String name,Class<?>...parameterTypes):返回单个公共成员方法对象
  • Method.getDeclaredMethod(String name, Class<?>..parameterTypes):返回单个成员方法对象

Method类中用于创建对象的方法:
Method.invoke(Object, parameter) 其中用 Object 对象调用该方法,调用方法的传递的参数 parameter(如果类的方法不接受任何参数,则将 null 作为参数传递)

java 复制代码
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        
    	//获取class字节码文件
    	Class clazz = Student.class;

    	System.out.println("获取所有公共成员方法(getMethods):");
    	Method[] methods = clazz.getMethods();
    	for(Method method : methods) {
    		System.out.println(method);
    	}
    	
    	System.out.println("\n获取所有成员方法(getDeclaredMethods):");
    	Method[] methods1 = clazz.getDeclaredMethods();
    	for(Method method : methods1) {
    		System.out.println(method);
    	}
    	
    	System.out.println("\n获取指定的单个方法(getMethod):");
        Method start = clazz.getMethod("start", String.class, Integer.class);
        System.out.println(start);
        
        System.out.println("\n创建对象的方法:");
        Student student = new Student();
        start.invoke(student,"张三",10);

        Method run = clazz.getDeclaredMethod("run", int.class);
        run.setAccessible(true);
        run.invoke(student,10);

        Method declaredMethod = clazz.getDeclaredMethod("stop",float.class, char.class);
        declaredMethod.invoke(student,10.0f,'a');
    }
}

输出结果:

如下代码是 《Java核心技术 卷1 基础知识 原书第10版》的代码示例

这段代码是一个通用的类结构解析工具,通过 Java 反射机制,接收输入的类全限定名,动态解析并打印该类的完整结构信息,包括:

类的修饰符、类名、父类;

类的所有构造方法,含修饰符、参数列表;

类的所有成员方法,含修饰符、返回值、方法名、参数列表;

类的所有成员字段,含修饰符、字段类型、字段名。

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

public class Reflection {
    public static void main(String[] args){
        String name;
        if(args.length > 0) name = args[0];
        else{
            Scanner in = new Scanner(System.in);
            System.out.println("Enter class name (e.g. java.util.Date):");
            name = in.next();
        }
        try {
            Class cl = Class.forName(name);
            Class supercl = cl.getSuperclass();
            String modifiers = Modifier.toString(cl.getModifiers());
            if(modifiers.length() > 0) System.out.println(modifiers + " ");
            System.out.println("class "+ name);
            if(supercl != null && supercl != Object.class) System.out.println(" extends "+ supercl.getName());
            System.out.println("\n{\n");
            printConstructors(cl);
            System.out.println();
            printMethods(cl);
            System.out.println();
            printFields(cl);
            System.out.println("}");
        }
        catch (ClassNotFoundException e){
            e.printStackTrace();
        }
        System.exit(0);
    }

    /**
     * Prints all Constructors of a class
     * @param cl a class
     */
    private static void printConstructors(Class cl) {
        Constructor[] constructors = cl.getDeclaredConstructors();
        for (Constructor c : constructors) {
            String name = c.getName();
            System.out.println("   ");
            String modifiers = Modifier.toString(c.getModifiers());
            if(modifiers.length()>0) System.out.println(modifiers +" ");
            System.out.println(name + "(");

            Class[] paramTypes = c.getParameterTypes();
            for(int j = 0;j < paramTypes.length; j++){
                if(j > 0) System.out.println(", ");
                System.out.println(paramTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * Prints all methods of a class
     * @param cl a class
     */
    private static void printMethods(Class cl) {
        Method[] methods = cl.getDeclaredMethods();
        for(Method m : methods){
            Class retType = m.getReturnType();
            String name = m.getName();
            System.out.println("   ");
            // Print modifiers,return type and method name
            String modifiers = Modifier.toString(m.getModifiers());
            if(modifiers.length() > 0) System.out.println(modifiers + " ");
            System.out.println(retType.getName() + " " + name + "(");
            // print parameter types
            Class[] paramTypes = m.getParameterTypes();
            for(int j = 0; j< paramTypes.length; j++){
                if(j>0) System.out.println(", ");
                System.out.println(paramTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * Prints all fields of a class
     * @param cl a class
     */
    private static void printFields(Class cl) {
        Field[] fields = cl.getDeclaredFields();
        for (Field f : fields) {
            Class type = f.getType();
            String name = f.getName();
            System.out.println("   ");
            String modifiers = Modifier.toString(f.getModifiers());
            if(modifiers.length() > 0) System.out.println(modifiers + " ");
            System.out.println(type.getName() + " " + name + ";");
        }
    }
}

把 Reflection.java 文件保存到文件夹下,打开终端 / 命令提示符,切换到文件所在目录执行如下命令:

bash 复制代码
javac Reflection.java

执行编译命令,文件夹里会出现Reflection.class文件

bash 复制代码
java Reflection java.lang.String

解析java.lang.String

反射的应用场景

反射的核心价值是赋予程序运行时的动态性,当程序在编译期无法确定要操作的具体类、方法或字段时,反射就能通过运行时解析类信息完成操作。因此,反射的应用场景几乎都围绕动态性需求展开。

几乎所有主流 Java 框架(Spring、MyBatis、Hibernate)都依赖反射实现核心功能:

Spring IOC:Spring 配置文件(XML)或注解中配置的类名,在运行时通过 Class.forName(类全限定名) 加载类,再通过 Constructor.newInstance() 创建 Bean 实例,最后通过反射调用 setter 方法完成依赖注入(DI);

MyBatis:通过 Mapper 接口的全类名,反射生成代理对象,动态执行 SQL,通过反射将 ResultSet 中的数据动态赋值给实体类的字段(无需为每个实体类写固定的赋值逻辑);

JUnit:通过反射识别 @Test 注解的方法并执行测试。

反射是动态代理的基础:

代理模式中,代理类在运行时通过反射调用目标类的方法,实现增强逻辑(如日志、事务)。

注解本身只是标记不具备功能,需通过反射获取类 / 方法 / 字段上的注解信息,才能执行注解对应的逻辑。

反射的优缺点与性能优化

反射的优点

  • 动态性:突破编译期静态绑定,实现运行时灵活适配
    • 核心逻辑:反射将类的解析、成员绑定从编译期延迟至运行时,无需在编码阶段硬编码信息,而是通过类的全限定名或对象引用动态操作类;
    • 典型场景:框架开发(如 Spring IOC 通过配置文件动态加载 Bean、JDBC 通过类名加载数据库驱动),无需修改源码即可切换实现类,大幅提升程序对不同场景的适配能力。
  • 灵活性:一套逻辑适配多类场景,降低重复开发成本
    • 核心逻辑:通过反射遍历类的元信息(字段、方法、构造器),可实现通用化处理逻辑,无需为每个类单独编写重复代码;
    • 典型场景:ORM 框架的对象映射(MyBatis 动态将 ResultSet 赋值给任意实体类)、工具类通用操作(如对字段日志打印),核心是利用反射统一解析元信息并执行处理。
  • 扩展性:支持动态加载外部组件,实现无侵入式功能扩展
    • 核心逻辑:无需将所有功能打包进主程序,可在运行时动态加载外部 JAR 包中的类,通过反射调用其接口方法,实现插件化、热部署等场景;
    • 典型场景:IDEA 插件、Dubbo 扩展点、中间件动态功能模块(如日志插件、加密插件),主程序通过反射适配外部组件,无需修改核心代码即可扩展功能。

反射的缺点与对应的性能优化

  • 性能损耗:运行时解析开销与编译优化缺失,高频场景需优化
    • 核心原因:反射需经过 "获取 Class 对象→解析成员元信息→权限检查→调用 invoke 方法" 等运行时步骤,绕过了编译期的方法内联、指令优化等底层优化逻辑,且每次反射调用都会重复执行元信息查找和权限校验,单次调用的耗时约为直接调用的 10-100 倍,在循环调用、高频业务处理等场景下,性能损耗会被显著放大,影响程序整体响应效率;
    • 优化方案:
      缓存反射对象:将解析后的 Class、Method、Field 对象缓存到线程安全的 Map 等容器中,首次获取后复用,避免重复解析元信息;
      减少权限检查开销:通过setAccessible(true)跳过访问权限校验,且在缓存反射对象时提前设置好访问权限,避免重复调用该方法触发安全检查;
      利用 JVM 底层优化:优先反射调用接口或父类的方法,JVM 对接口方法的反射调用有专门优化,执行效率高于直接调用具体类方法。
  • 破坏封装:突破访问控制修饰符,违背面向对象设计原则
    • 核心影响:封装的核心价值是隐藏类的内部实现细节、通过公共接口保证数据完整性,而 setAccessible(true) 可直接访问 private 字段和方法,绕开业务逻辑校验;直接修改私有字段可能导致对象状态不一致;调用私有方法可能破坏类的内部依赖关系,引发未知异常;
    • 防护方案:
      最小权限原则:仅在确有必要时才使用setAccessible(true),且优先通过类提供的公共接口操作成员,避免直接操作私有成员;
      额外校验逻辑:若需通过反射修改字段,在反射调用外层增加参数合法性校验,弥补封装被破坏后的逻辑缺失。
  • 可读性与可维护性差:代码冗余且逻辑隐式化,调试成本高
    • 具体表现:反射需通过多步 API 调用实现简单功能,代码繁琐度远超直接调用;依赖字符串硬编码标识类名、方法名,无编译期语法检查;运行时异常需手动捕获,排查问题时需追溯反射调用链路,定位效率低;
  • 编译期类型安全缺失:无法提前校验成员有效性,运行时风险后置
    • 核心问题:反射依赖字符串标识类成员,编译期无法检查成员名称拼写(NoSuchMethodException)、参数类型匹配(IllegalArgumentException)、访问权限等问题,所有错误需在程序运行时才能暴露。
  • 安全风险:敏感逻辑易被恶意利用,需多重防护
    • 具体场景:攻击者可通过反射调用程序中的敏感方法(如删除数据、修改配置)、修改私有配置字段(如数据库连接密码)、注入恶意代码(如通过反射执行本地命令);
    • 防护措施:启用安全管理器(SecurityManager)限制反射权限;对敏感类 / 方法进行代码混淆,隐藏全限定名;避免在对外接口中使用反射操作敏感逻辑;通过注解或配置明确允许反射访问的成员。

总结:

反射是 Java 动态编程的核心,它赋予程序在运行时操作类的能力,是框架、动态代理、注解等技术的基础。但反射也存在性能损耗和封装性问题,需在灵活性和安全性之间权衡。

相关推荐
计算机学姐4 分钟前
基于SpringBoot的在线骑行网站系统
java·vue.js·spring boot·后端·mysql·spring·tomcat
weixin_440730505 分钟前
04python编程笔记-01基础知识+02三种结构
java·笔记·python
Remember_9937 分钟前
【LeetCode精选算法】二分查找专题二
java·数据结构·算法·leetcode·哈希算法
凯子坚持 c7 分钟前
C++大模型SDK开发实录(三):流式交互协议SSE解析与httplib实现原理
开发语言·c++·交互
空空kkk13 分钟前
Java项目从单体到微服务的演变
java·运维·微服务
ghie909019 分钟前
基于MATLAB的多旋翼无人机多机编队仿真实现
开发语言·matlab·无人机
程农24 分钟前
java计算机毕业设计婚纱摄影网站(附源码、数据库)
java·数据库·课程设计
BlockChain88824 分钟前
Spring框架终极入门指南(12000字深度解析)
java·后端·python·spring
少控科技31 分钟前
QT新手日记026
开发语言·qt
就是有点傻33 分钟前
C#中如何和西门子通信
开发语言·c#