在 Java 的世界里反射Reflection是一把双刃剑。它赋予了程序在运行时动态获取类的信息、创建对象、调用方法、访问字段的能力让 Java 从编译时静态语言获得了动态语言的灵活性。许多框架如 Spring、MyBatis、Hibernate都离不开反射。但反射也带来了性能损耗和安全隐患。本文将带你全面了解 Java 反射的核心概念、使用方式、应用场景以及注意事项。一、什么是反射反射是指程序在运行期可以访问、检测和修改它自身状态或行为的一种能力。通过反射我们可以获取任意类的名称、包信息、父类、接口等。动态创建对象实例。获取并调用任意方法包括私有方法。访问和修改字段的值包括私有字段。操作数组。Java 反射 API 主要位于java.lang.reflect包中。二、反射的核心类类/接口作用Class代表一个类或接口反射的入口Constructor代表构造方法Method代表普通方法Field代表成员变量字段Array提供动态创建和访问数组的静态方法Modifier提供解析访问修饰符的工具方法Parameter代表方法参数JDK 8三、获取 Class 对象要使用反射首先需要获取目标类的Class对象。有以下三种方式3.1 通过类名.classClass? clazz1 String.class; Class? clazz2 int.class; // 基本类型也有 Class 对象3.2 通过对象的 getClass() 方法String str hello; Class? clazz str.getClass();3.3 通过 Class.forName() 全限定类名Class? clazz Class.forName(java.util.ArrayList);注意这种方式会触发类的静态初始化块如果类还未加载。四、通过反射创建对象4.1 使用无参构造器Class? clazz Class.forName(java.util.ArrayList); Object obj clazz.newInstance(); // 已过时但依然可用推荐使用Constructor来创建Constructor? constructor clazz.getConstructor(); Object obj constructor.newInstance();4.2 使用有参构造器Class? clazz Class.forName(java.lang.String); // 获取参数类型为 String 的构造器 Constructor? constructor clazz.getConstructor(String.class); Object obj constructor.newInstance(hello);五、通过反射调用方法5.1 调用公共方法public class Person { public void sayHello(String name) { System.out.println(Hello, name); } } // 反射调用 Class? clazz Person.class; Object person clazz.newInstance(); Method method clazz.getMethod(sayHello, String.class); method.invoke(person, World);5.2 调用私有方法public class Calculator { private int add(int a, int b) { return a b; } } // 反射调用私有方法 Calculator calc new Calculator(); Method method Calculator.class.getDeclaredMethod(add, int.class, int.class); method.setAccessible(true); // 压制 Java 访问检查 int result (int) method.invoke(calc, 3, 5); System.out.println(result); // 8六、通过反射访问字段6.1 访问公共字段public class User { public String name; } User user new User(); Field field User.class.getField(name); field.set(user, Alice); System.out.println(field.get(user)); // Alice6.2 访问私有字段public class Student { private int age; } Student student new Student(); Field field Student.class.getDeclaredField(age); field.setAccessible(true); field.set(student, 18); System.out.println(field.get(student)); // 18七、操作数组Array类提供了一组静态方法可以动态创建和访问数组。// 创建一个 int 数组长度为 5 Object array Array.newInstance(int.class, 5); // 设置索引 0 处的值为 10 Array.set(array, 0, 10); // 获取索引 0 处的值 int value Array.getInt(array, 0); System.out.println(value); // 10八、反射的应用场景8.1 框架开发Spring 的 IoC 容器通过反射创建 Bean 实例并注入依赖MyBatis 通过反射将数据库查询结果映射到 Java 对象JUnit 通过反射执行测试方法。8.2 动态代理JDK 动态代理基于接口生成代理类底层使用了反射来调用目标方法。8.3 注解处理在运行时读取注解信息通常结合反射实现。例如Spring MVC 的RequestMapping注解解析。8.4 工具类与调试开发通用工具如对象深拷贝、JSON 序列化时反射可以避免针对每种类型编写重复代码。九、反射的性能问题与优化反射由于涉及动态解析、安全检查即使setAccessible也还是有一定开销性能远低于直接调用。在性能敏感的场景下可以采取以下优化措施缓存反射对象Method、Field、Constructor对象可以缓存避免重复获取。关闭访问检查setAccessible(true)可以略微提升速度。使用MethodHandleJDK 7提供比反射更快的动态调用方式。避免频繁调用反射如果反射调用是热点代码考虑使用代码生成或缓存代理。性能对比示例简单测试// 直接调用 for (int i 0; i 1_000_000; i) { obj.method(); } // 反射调用未缓存 for (int i 0; i 1_000_000; i) { Method m obj.getClass().getMethod(method); m.invoke(obj); } // 反射调用缓存 Method 对象 Method m obj.getClass().getMethod(method); for (int i 0; i 1_000_000; i) { m.invoke(obj); }实际测试中缓存后的反射调用大约比直接调用慢 3-5 倍而未缓存的反射调用则慢得多。十、反射的安全性问题破坏封装性通过setAccessible(true)可以访问私有成员打破了类的封装原则可能导致不可预知的行为。安全限制在安全管理器SecurityManager启用的情况下某些反射操作可能被禁止。框架使用场景通常只在框架内部使用反射业务代码应尽量避免直接使用反射以保持代码的清晰和可维护性。十一、总结Java 反射机制是一种强大的工具它使程序具备了动态性是框架和基础库不可或缺的部分。然而反射也是一把双刃剑优点提供了极高的灵活性支持动态代理、依赖注入、注解处理等高级特性。缺点性能损耗、破坏封装、可能引发安全问题、代码可读性降低。在实际开发中建议只在确实需要动态处理时才使用反射并遵循以下原则缓存反射对象以减少性能开销。尽量使用MethodHandle替代反射如果性能是关键。避免在业务代码中随意使用反射保持代码的简洁和可维护性。理解反射的原理不仅有助于写出更优雅的代码也能帮助你更深入地理解 Spring、MyBatis 等主流框架的实现。希望本文能为你揭开反射的神秘面纱让你在 Java 编程之路上走得更远。
Java笔记——反射
在 Java 的世界里反射Reflection是一把双刃剑。它赋予了程序在运行时动态获取类的信息、创建对象、调用方法、访问字段的能力让 Java 从编译时静态语言获得了动态语言的灵活性。许多框架如 Spring、MyBatis、Hibernate都离不开反射。但反射也带来了性能损耗和安全隐患。本文将带你全面了解 Java 反射的核心概念、使用方式、应用场景以及注意事项。一、什么是反射反射是指程序在运行期可以访问、检测和修改它自身状态或行为的一种能力。通过反射我们可以获取任意类的名称、包信息、父类、接口等。动态创建对象实例。获取并调用任意方法包括私有方法。访问和修改字段的值包括私有字段。操作数组。Java 反射 API 主要位于java.lang.reflect包中。二、反射的核心类类/接口作用Class代表一个类或接口反射的入口Constructor代表构造方法Method代表普通方法Field代表成员变量字段Array提供动态创建和访问数组的静态方法Modifier提供解析访问修饰符的工具方法Parameter代表方法参数JDK 8三、获取 Class 对象要使用反射首先需要获取目标类的Class对象。有以下三种方式3.1 通过类名.classClass? clazz1 String.class; Class? clazz2 int.class; // 基本类型也有 Class 对象3.2 通过对象的 getClass() 方法String str hello; Class? clazz str.getClass();3.3 通过 Class.forName() 全限定类名Class? clazz Class.forName(java.util.ArrayList);注意这种方式会触发类的静态初始化块如果类还未加载。四、通过反射创建对象4.1 使用无参构造器Class? clazz Class.forName(java.util.ArrayList); Object obj clazz.newInstance(); // 已过时但依然可用推荐使用Constructor来创建Constructor? constructor clazz.getConstructor(); Object obj constructor.newInstance();4.2 使用有参构造器Class? clazz Class.forName(java.lang.String); // 获取参数类型为 String 的构造器 Constructor? constructor clazz.getConstructor(String.class); Object obj constructor.newInstance(hello);五、通过反射调用方法5.1 调用公共方法public class Person { public void sayHello(String name) { System.out.println(Hello, name); } } // 反射调用 Class? clazz Person.class; Object person clazz.newInstance(); Method method clazz.getMethod(sayHello, String.class); method.invoke(person, World);5.2 调用私有方法public class Calculator { private int add(int a, int b) { return a b; } } // 反射调用私有方法 Calculator calc new Calculator(); Method method Calculator.class.getDeclaredMethod(add, int.class, int.class); method.setAccessible(true); // 压制 Java 访问检查 int result (int) method.invoke(calc, 3, 5); System.out.println(result); // 8六、通过反射访问字段6.1 访问公共字段public class User { public String name; } User user new User(); Field field User.class.getField(name); field.set(user, Alice); System.out.println(field.get(user)); // Alice6.2 访问私有字段public class Student { private int age; } Student student new Student(); Field field Student.class.getDeclaredField(age); field.setAccessible(true); field.set(student, 18); System.out.println(field.get(student)); // 18七、操作数组Array类提供了一组静态方法可以动态创建和访问数组。// 创建一个 int 数组长度为 5 Object array Array.newInstance(int.class, 5); // 设置索引 0 处的值为 10 Array.set(array, 0, 10); // 获取索引 0 处的值 int value Array.getInt(array, 0); System.out.println(value); // 10八、反射的应用场景8.1 框架开发Spring 的 IoC 容器通过反射创建 Bean 实例并注入依赖MyBatis 通过反射将数据库查询结果映射到 Java 对象JUnit 通过反射执行测试方法。8.2 动态代理JDK 动态代理基于接口生成代理类底层使用了反射来调用目标方法。8.3 注解处理在运行时读取注解信息通常结合反射实现。例如Spring MVC 的RequestMapping注解解析。8.4 工具类与调试开发通用工具如对象深拷贝、JSON 序列化时反射可以避免针对每种类型编写重复代码。九、反射的性能问题与优化反射由于涉及动态解析、安全检查即使setAccessible也还是有一定开销性能远低于直接调用。在性能敏感的场景下可以采取以下优化措施缓存反射对象Method、Field、Constructor对象可以缓存避免重复获取。关闭访问检查setAccessible(true)可以略微提升速度。使用MethodHandleJDK 7提供比反射更快的动态调用方式。避免频繁调用反射如果反射调用是热点代码考虑使用代码生成或缓存代理。性能对比示例简单测试// 直接调用 for (int i 0; i 1_000_000; i) { obj.method(); } // 反射调用未缓存 for (int i 0; i 1_000_000; i) { Method m obj.getClass().getMethod(method); m.invoke(obj); } // 反射调用缓存 Method 对象 Method m obj.getClass().getMethod(method); for (int i 0; i 1_000_000; i) { m.invoke(obj); }实际测试中缓存后的反射调用大约比直接调用慢 3-5 倍而未缓存的反射调用则慢得多。十、反射的安全性问题破坏封装性通过setAccessible(true)可以访问私有成员打破了类的封装原则可能导致不可预知的行为。安全限制在安全管理器SecurityManager启用的情况下某些反射操作可能被禁止。框架使用场景通常只在框架内部使用反射业务代码应尽量避免直接使用反射以保持代码的清晰和可维护性。十一、总结Java 反射机制是一种强大的工具它使程序具备了动态性是框架和基础库不可或缺的部分。然而反射也是一把双刃剑优点提供了极高的灵活性支持动态代理、依赖注入、注解处理等高级特性。缺点性能损耗、破坏封装、可能引发安全问题、代码可读性降低。在实际开发中建议只在确实需要动态处理时才使用反射并遵循以下原则缓存反射对象以减少性能开销。尽量使用MethodHandle替代反射如果性能是关键。避免在业务代码中随意使用反射保持代码的简洁和可维护性。理解反射的原理不仅有助于写出更优雅的代码也能帮助你更深入地理解 Spring、MyBatis 等主流框架的实现。希望本文能为你揭开反射的神秘面纱让你在 Java 编程之路上走得更远。