反射与泛型
本文按「反射」与「泛型」两块整理:反射侧重 Class / Method / Field / Constructor 的获取与调用,泛型侧重用法、擦除与桥接方法。
一、反射(Reflection)
Section titled “一、反射(Reflection)”1.1 引入:什么是反射
Section titled “1.1 引入:什么是反射”反射允许在运行时检查类、接口、方法、字段、构造器等,并动态调用方法、读写字段、创建实例,而无需在编译期写死类名。典型用途:框架(Spring 依赖注入、JUnit 发现测试方法)、序列化、动态代理等。
1.2 获取 Class 的三种方式
Section titled “1.2 获取 Class 的三种方式”// 1. 类名.class(编译期已知类型)Class<?> c1 = String.class;
// 2. 对象.getClass()String s = "hello";Class<?> c2 = s.getClass();
// 3. Class.forName("全限定类名")(仅类名字符串已知)Class<?> c3 = Class.forName("java.lang.String");- 同一类在同一个 ClassLoader 下只会有一个
Class实例,故c1 == c2 == c3为 true(同一加载器时)。
1.3 反射核心 API 与“声明” vs “公有”
Section titled “1.3 反射核心 API 与“声明” vs “公有””| API 前缀 | 范围说明 |
|---|---|
getDeclaredXxx | 本类声明的成员(含 private),不含继承的 |
getXxx | 本类 + 父类中的 public 成员 |
例如:getMethod(name, parameterTypes) 只拿 public 方法(含继承);getDeclaredMethod(...) 只拿当前类声明的方法(含 private),不包含父类。
1.4 构造器:创建实例
Section titled “1.4 构造器:创建实例”Class<?> clazz = Class.forName("com.example.User");
// 无参构造Constructor<?> noArg = clazz.getDeclaredConstructor();Object obj = noArg.newInstance();
// 带参构造Constructor<?> withArgs = clazz.getDeclaredConstructor(String.class, int.class);Object user = withArgs.newInstance("张三", 25);getDeclaredConstructor(Class<?>... parameterTypes)可拿到任意可见性的构造器;若需调用私有构造器,需先constructor.setAccessible(true)。newInstance(...)会传播构造器内部抛出的异常(JDK 9+ 推荐用clazz.getDeclaredConstructor().newInstance()替代已过时的Class.newInstance())。
1.5 方法:获取与调用(invoke)
Section titled “1.5 方法:获取与调用(invoke)”获取方法
// 本类声明的指定方法(含 private)Method m = clazz.getDeclaredMethod("methodName", String.class, int.class);
// 本类或父类 public 方法Method mPublic = clazz.getMethod("toString");Method.invoke 用法
// 实例方法:第一个参数为对象实例Object result = m.invoke(instance, "arg1", 2);
// 静态方法:第一个参数传 nullObject staticResult = staticMethod.invoke(null, args);- 若被调方法抛出异常,会包装为
InvocationTargetException,真实原因通过getCause()获取。 - 调用私有方法前需
method.setAccessible(true)(受模块系统限制时可能失败)。
Method 在 JDK 中的关键点
Method继承自Executable,内部持有方法名、参数类型、返回类型、修饰符等;实际执行通过MethodAccessor完成,首次调用会生成/使用 Native 或 Generated 的 accessor,后续 invoke 才真正走“调用逻辑”。
1.6 字段:读与写
Section titled “1.6 字段:读与写”Field field = clazz.getDeclaredField("fieldName");field.setAccessible(true); // 若为 private
// 读Object value = field.get(instance);
// 写field.set(instance, newValue);getDeclaredField(name)只查当前类声明字段;若取继承的 public 字段用getField(name)。- 静态字段:
get/set的“实例”参数可传null。
1.7 反射示例汇总(对应 PDF 中 ReflectionExample 思路)
Section titled “1.7 反射示例汇总(对应 PDF 中 ReflectionExample 思路)”public class ReflectionDemo { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("java.util.ArrayList");
// 构造实例 Object list = clazz.getDeclaredConstructor().newInstance();
// 调用 add(Object) Method add = clazz.getMethod("add", Object.class); add.invoke(list, "one"); add.invoke(list, "two");
// 调用 size() Method size = clazz.getMethod("size"); System.out.println(size.invoke(list)); // 2
// 读内部字段(仅演示,实际 elementData 等可能不可访问) Field sizeField = clazz.getDeclaredField("size"); sizeField.setAccessible(true); System.out.println(sizeField.get(list)); // 2 }}二、泛型(Generics)
Section titled “二、泛型(Generics)”2.1 用法概览
Section titled “2.1 用法概览”- 泛型类:
class Box<T> { private T value; ... } - 泛型接口:
interface Comparable<T> { int compareTo(T o); } - 泛型方法:
static <T> T getFirst(List<T> list) { return list.get(0); } - 有界类型参数:
<T extends Number>、<T extends Comparable<T>> - 通配符:
?、? extends Number、? super Integer
// 泛型类public class Box<T> { private T value; public void set(T v) { value = v; } public T get() { return value; }}
// 泛型方法public static <K, V> void put(Map<K, V> map, K key, V value) { map.put(key, value);}
// 通配符:只读void print(List<? extends Number> list) { ... }
// 通配符:只写(常见于 Consumer)void addNumbers(List<? super Integer> list) { list.add(1); }2.2 类型擦除(Type Erasure)
Section titled “2.2 类型擦除(Type Erasure)”泛型只在编译期生效,编译后字节码中泛型信息被擦除,JVM 看不到 T、List<String> 等,这是为兼容 1.5 之前无泛型代码的设计选择。
擦除规则简要
| 写法 | 擦除后 |
|---|---|
T 无界 | Object |
T extends Number | Number |
T extends A & B(多边界,A 为类) | 第一个边界类型(类) |
List<String> | List(原始类型) |
// 源码public class Holder<T> { private T value; public void set(T v) { value = v; } public T get() { return value; }}
// 擦除后(逻辑等价,便于理解)public class Holder { private Object value; public void set(Object v) { value = v; } public Object get() { return value; }}因此无法在运行时用反射直接拿到“泛型参数类型”(例如无法区分 List<String> 与 List<Integer>),只能拿到原始类型 List;若需保留类型信息,可借助「类型字面量」或显式传 Class<T>。
2.3 桥接方法(Bridge Method)
Section titled “2.3 桥接方法(Bridge Method)”子类继承/实现泛型父类时,擦除后父类方法签名会变成“原始类型”(如 Object),而子类重写的方法签名是具体类型(如 Integer),二者签名不一致,无法满足“重写 = 相同签名”的规则,多态会失效。编译器通过自动生成桥接方法解决:桥接方法签名与擦除后的父类一致,内部再转发到子类自己的重写方法。
示例(对应 PDF 中 BridgeMethodDemo 思路)
// 父类(擦除后 setData 参数为 Object)public class Node<T> { private T data; public void setData(T data) { this.data = data; } public T getData() { return data; }}
// 子类(擦除后仍保留 setData(Integer))public class IntNode extends Node<Integer> { private Integer data; @Override public void setData(Integer data) { this.data = data; } @Override public Integer getData() { return data; }}擦除后:
Node:setData(Object)、getData() -> ObjectIntNode:若只有setData(Integer),则与Node.setData(Object)签名不同,不构成重写。
编译器在 IntNode 中自动生成合成/桥接方法(源码中不可见,字节码中可见):
// 编译器生成的桥接方法(仅逻辑等价表示)public void setData(Object data) { setData((Integer) data); // 强转后调用本类 setData(Integer)}public Object getData() { return getData(); // 拆箱/装箱后委托给本类 getData() -> Integer}这样,通过 Node n = new IntNode(); n.setData(x) 调用时,会走到桥接方法,再转到子类的 setData(Integer),多态得以保持。
反射时注意:getDeclaredMethods() 会返回桥接方法,可通过 method.isBridge() 判断;若只关心“用户写的”方法,可过滤掉 isBridge() == true。
2.4 泛型与反射结合示例
Section titled “2.4 泛型与反射结合示例”// 泛型擦除后无法从 List 得到元素类型,只能拿到 List.classClass<?> listClass = List.class;
// 若需在运行时使用具体类型,通常通过参数传入 Class<T>public <T> T create(Class<T> clazz) throws Exception { return clazz.getDeclaredConstructor().newInstance();}String s = create(String.class);| 主题 | 要点 |
|---|---|
| 反射 | Class 三种获取方式;getDeclaredXxx 含本类私有,getXxx 含继承的 public;Constructor.newInstance、Method.invoke、Field.get/set;访问私有前需 setAccessible(true)。 |
| 泛型 | 泛型类/接口/方法、有界、通配符;编译后发生类型擦除,无界 → Object,有界 → 边界类型;子类重写泛型父类方法时由编译器生成桥接方法以保持多态,反射可见 isBridge()。 |
日期:2024-03-11。