1. 什么是反射?
在 Java 的世界里,我们通常是在编译期 就确定了一个对象的类型,然后 new 出它的实例。但在很多高级框架(如 Spring、MyBatis)中,程序在编译时根本不知道要加载哪个类,只有在运行时才能动态地获取类信息并创建对象。
这种"在运行状态中,对于任意一个类,都能知道它的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性 " 的能力,就是 Java 的 反射机制。
简单来说,反射让 Java 变成了一种"准动态语言",赋予了代码极强的扩展性。
2. 核心 API:Class 对象
要理解反射,首先要理解 Class 对象。
在 Java 中,万物皆对象。类(Class)本身也是一个对象。当类加载器将一个 .class 文件加载到内存时,JVM 会为这个类创建一个 java.lang.Class 实例。
获取 Class 对象的三种方式:
Class.forName("全类名"):最常用,常用于配置文件中读取类名。类名.class:常用于编译期已知类的情况。对象.getClass():已有实例时使用。
3. 实战演练:反射能干什么?
光说不练假把式,我们通过代码来看看反射的四大金刚:构造方法、成员变量、成员方法。
假设我们有一个普通的实体类 User:
java
public class User {
private String name;
private int age;
public User() {}
private User(String name) {
this.name = name;
}
public void sayHello() {
System.out.println("Hello, I am " + name);
}
private void secretMethod() {
System.out.println("这是一个私有方法");
}
}
场景一:暴力反射(破解私有)
反射最强大的地方在于它可以无视访问修饰符(private)。
java
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("com.example.User");
// 2. 获取私有构造方法并创建对象
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 暴力访问,取消权限检查
Object user = constructor.newInstance("张三");
// 3. 调用私有方法
Method secretMethod = clazz.getDeclaredMethod("secretMethod");
secretMethod.setAccessible(true); // 暴力访问
secretMethod.invoke(user); // 输出:这是一个私有方法
// 4. 修改私有属性
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user, "李四"); // 把名字改成李四

注意: 在实际开发中,
setAccessible(true)是非常敏感的操作,它破坏了封装性,但在框架开发(如 ORM 映射)中却是必不可少的。
4. 灵魂拷问:反射的优缺点
在面试中,经常被问到"反射有什么缺点?",不要只回答"慢"。
优点:
- 解耦与扩展性: 这是反射最大的价值。Spring 的 IOC 容器就是利用反射,根据配置文件创建 Bean,实现了代码的零侵入。
- 通用性: 可以编写与具体类无关的通用代码(如 JSON 解析工具 FastJSON/Gson)。
缺点:
- 性能开销: 反射涉及动态解析类型,JVM 无法对其进行优化(如内联缓存),速度比直接调用慢。
- 安全限制: 反射需要绕过访问检查,如果运行在安全管理器(SecurityManager)环境下,可能会受限。
- 破坏泛型: 反射可以绕过泛型检查,向
ArrayList<Integer>中添加字符串。
5. 进阶:反射真的很慢吗?
这是一个常见的误区。
- 在 JDK 1.7 之前,反射确实非常慢。
- 但在现代 JDK 中,如果关闭了访问检查(即调用了
setAccessible(true)),JVM 会对其进行优化,性能损耗其实是可以接受的。 - 结论: 除非是在极高并发的热点代码路径上(如循环几百万次),否则反射的性能损耗通常不是瓶颈,开发效率和解耦的收益远大于性能损耗。
6. 总结
反射是 Java 框架的基石。作为业务开发,我们可能不需要天天写反射代码,但理解它有助于我们读懂 Spring、MyBatis 的源码,写出更优雅的代码。
以下是反射常用到的方法:
- Class.forName(String className):根据类名获取对应的 Class 对象。
- getClass():获取对象的运行时类型。
- getMethod(String name, Class<?>... parameterTypes):获取指定方法名和参数类型的方法。
- getField(String name):获取指定名称的字段。
- newInstance():使用默认的构造函数创建实例。
- newInstance(Object... initargs):使用指定参数类型和值的构造函数创建实例。
- invoke(Object obj, Object... args):调用指定对象的方法。
最简回答:Java 的反射机制是指在运行时动态地获取类的信息并操作类或对象的能力。通过反射,我们可以在编译时无法确定的情况下,通过类名获取类的实例、获取类的字段、方法、构造函数等信息,并且可以在运行时调用这些方法或访问这些字段。