反射
反射(Reflection)让程序在运行期检视类的结构、动态创建对象、调用方法、读写字段。它是 Spring/Jackson/JUnit 等框架的「魔法」之源。
代价:反射牺牲了编译期类型安全,性能略低(现代 JVM 已经优化得不错),可读性也更弱。能不用就不用;框架层是它的主战场。
1. 三种获取 Class 对象的方式
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
// 1. 类字面量
Class<String> c1 = String.class;
// 2. 对象.getClass()
Class<?> c2 = "hello".getClass();
// 3. Class.forName(动态加载)
Class<?> c3 = Class.forName("java.util.ArrayList");
System.out.println("c1 == c2 ? " + (c1 == c2));
System.out.println("c1 classLoader = " + c1.getClassLoader());
System.out.println("c3 simpleName = " + c3.getSimpleName());
System.out.println("c3 isInterface? = " + c3.isInterface());
}
}💡基本类型与 void 的 Class
int.class→Class<Integer>的「类型对象」void.class→Class<Void>int[].class≠String[].class→ 都是数组 Class
2. 检视类的结构
import java.lang.reflect.*;
import static java.lang.reflect.Modifier.*;
public class Main {
static class User {
private final long id;
public String name;
protected int age;
static int count;
private String[] tags;
public User(long id) { this.id = id; }
public User(long id, String name) { this.id = id; this.name = name; }
public long getId() { return id; }
public void setAge(int age) { this.age = age; }
private void secret() {}
}
public static void main(String[] args) {
Class<?> c = User.class;
System.out.println("== 字段 ==");
for (Field f : c.getDeclaredFields()) {
System.out.printf(" %s %s %s%n",
Modifier.toString(f.getModifiers()),
f.getType().getSimpleName(),
f.getName());
}
System.out.println("== 公有方法(不含 Object) ==");
for (Method m : c.getMethods()) {
if (m.getDeclaringClass() == Object.class) continue;
System.out.printf(" %s %s%n", m.getReturnType().getSimpleName(), m.getName());
}
System.out.println("== 构造器 ==");
for (Constructor<?> ctor : c.getDeclaredConstructors()) {
System.out.printf(" ctor(%d 参数)%n", ctor.getParameterCount());
}
}
}ℹ️getXxx vs getDeclaredXxx
getFields()/getMethods():只看公有 + 继承来的getDeclaredFields()/getDeclaredMethods():看本类声明的所有(含 private),不含继承
3. 动态创建对象与读写字段
import java.lang.reflect.*;
public class Main {
static class User {
public String name;
private int age;
public User() {}
public User(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public int getAge() { return age; }
@Override public String toString() {
return "User(name=" + name + ", age=" + age + ")";
}
}
public static void main(String[] args) throws Exception {
Class<?> c = User.class;
// 1. 用无参构造器 newInstance
Object u1 = c.getDeclaredConstructor().newInstance();
System.out.println("u1 = " + u1);
// 2. 用有参构造器
Object u2 = c.getDeclaredConstructor(String.class).newInstance("Alice");
System.out.println("u2 = " + u2);
// 3. 改公有字段
Field nameF = c.getField("name");
nameF.set(u2, "Bob");
System.out.println("after set name = " + u2);
// 4. 改私有字段(需 setAccessible)
Field ageF = c.getDeclaredField("age");
ageF.setAccessible(true);
ageF.set(u2, 28);
System.out.println("after set age = " + u2);
// 5. 调方法
Method hello = c.getMethod("setAge", int.class);
hello.invoke(u2, 99);
System.out.println("age = " + ((User) u2).getAge());
}
}⚠️Java 17+ 的访问警告
setAccessible(true) 在标准模块(java.*)上会抛 InaccessibleObjectException。
业务类在自己模块里通常没问题;想调 JDK 内部需要 --add-opens。
4. 动态代理 Proxy
JDK 自带的动态代理:在运行期为接口生成实现类,把所有方法调用转给 InvocationHandler。
import java.lang.reflect.*;
import java.util.*;
public class Main {
interface Hello {
String say(String name);
}
static class HelloImpl implements Hello {
public String say(String name) { return "Hello, " + name; }
}
public static void main(String[] args) {
Hello real = new HelloImpl();
Hello proxy = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(),
new Class<?>[] { Hello.class },
(proxyObj, method, methodArgs) -> {
long t0 = System.nanoTime();
Object result = method.invoke(real, methodArgs);
long ns = System.nanoTime() - t0;
System.out.printf("[proxy] %s took %d ns%n", method.getName(), ns);
return result;
}
);
System.out.println(proxy.say("Alice"));
System.out.println(proxy.say("Bob"));
}
}ℹ️代理能做什么
动态代理是 AOP 的基础——在不修改业务代码的前提下,横向插入日志、事务、权限、监控等切面。 Spring AOP 默认就用 JDK 动态代理(前提是目标实现接口)。
5. 实战:迷你 JSON 序列化器
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
public class Main {
static class User {
public String name = "Alice";
public int age = 30;
public boolean admin = true;
public String[] tags = { "vip", "beta" };
public List<String> skills = List.of("Java", "Kotlin");
}
static String toJson(Object obj) throws Exception {
Class<?> c = obj.getClass();
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())) continue;
f.setAccessible(true);
Object v = f.get(obj);
if (!first) sb.append(",");
first = false;
sb.append("\"").append(f.getName()).append("\":");
sb.append(valueToJson(v));
}
return sb.append("}").toString();
}
static String valueToJson(Object v) {
if (v == null) return "null";
if (v instanceof String) return "\"" + v + "\"";
if (v instanceof Number || v instanceof Boolean) return v.toString();
if (v instanceof Object[]) {
return Arrays.stream((Object[]) v).map(Main::valueToJson)
.collect(Collectors.joining(",", "[", "]"));
}
if (v instanceof Collection<?>) {
return ((Collection<?>) v).stream().map(Main::valueToJson)
.collect(Collectors.joining(",", "[", "]"));
}
return "\"" + v + "\"";
}
public static void main(String[] args) throws Exception {
System.out.println(toJson(new User()));
}
}🎯 练习
// 任务:实现一个工具方法 copyProperties(src, dest),
// 把 src 中所有 String/int 公有字段拷贝到 dest 的同名字段。
// 期望输出:dest = User(name=Alice, age=30, role=admin)
import java.lang.reflect.*;
public class Main {
static class User {
public String name;
public int age;
public String role;
@Override public String toString() {
return "User(name=" + name + ", age=" + age + ", role=" + role + ")";
}
}
static class UserDTO {
public String name;
public int age;
public String role;
public String extra; // 目标里多出来的字段,保持原样
@Override public String toString() {
return "User(name=" + name + ", age=" + age + ", role=" + role + ")";
}
}
public static void main(String[] args) throws Exception {
User src = new User(); src.name = "Alice"; src.age = 30; src.role = "admin";
UserDTO dest = new UserDTO();
// 补全:调用你的 copyProperties
System.out.println("dest = " + dest);
}
}小结
- ✅ 三种
Class获取:.class/getClass()/Class.forName() - ✅
getXxx看 public+继承;getDeclaredXxx看本类所有 - ✅
Constructor.newInstance/Method.invoke/Field.set动态操作 - ✅
setAccessible(true)突破 private 限制(模块化下需--add-opens) - ✅
Proxy.newProxyInstance+InvocationHandler= JDK 动态代理 - ✅ 反射是框架(Spring/Jackson/Hibernate)的底层支撑
下一章 JVM 与 GC 基础:理解 Java 程序运行时。