java反射与泛型的简单知识和应用
常见描述
ParameterizedType 叫做参数化类型, 是 java.lang.reflect 包里的一个接口,表示一种 已经传入具体类型参数的泛型类型
List<String>、Map<String, Integer>
这种 带了尖括号并且明确了泛型实参的类型。
原始类型(Raw Type)泛型擦除后的类型,是 Java 泛型向后兼容的一种表现
例如泛型类型 List<T>
,它的原始类型就是 List(没有泛型参数)
注意:Java 里找不到一个叫 RawType 的类,因为 “原始类型(Raw Type)” 只是 Java 语言规范(JLS)里的一个概念,并不是一个实际存在的 Java 类。
TypeVariable(类型变量)就是泛型声明里的占位符 T, E, K, V 这种。
GenericArrayType(泛型数组类型)元素是泛型的数组,例如 T[]、List<String>[]
WildcardType(通配符类型)含有 ? 的类型,比如 List<?>、List<? extends Number>、List<? super String>。
pubic static <T> T getName()
这里的<T>
叫做 类型参数声明(Type Parameter Declaration),也可以简称为类型形参。
Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
这里的 actualTypeArgument 叫 实际类型参数(Actual Type Argument)
在 Repository<User>
中,User 就是实际类型参数,它替换了泛型声明里的 T
1.反射 父类 的泛型
public class Base<T> {}
public class Child extends Base<String> {}
Type genericSuperclass = Child.class.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) genericSuperclass;Type[] actualTypeArguments = pt.getActualTypeArguments();for (Type type : actualTypeArguments) {System.out.println(type); // 输出: class java.lang.String}
}
方法:Class.getGenericSuperclass()
返回类型:Type(一般是 ParameterizedType)
应用场景:ORM 框架(如 MyBatis、Hibernate)经常用它获取实体类绑定的泛型类型。
1.1应用示例
背景
ORM 框架(比如 MyBatis、Hibernate)通常会有一个通用的基类 DAO/Repository,用来做增删改查。
public abstract class BaseDao<T> {public void save(T entity) {System.out.println("保存:" + entity);}
}
这个 T 是个泛型,占位符,代表你要操作的实体类型。
每个具体的 DAO 只要继承 BaseDao 并指定具体类型就行:
public class UserDao extends BaseDao<User> { }
public class OrderDao extends BaseDao<Order> { }
问题
而框架在运行时需要知道具体的泛型类型 T,这样才能:
自动生成 SQL 语句
做字段映射(数据库列名 ↔ Java 属性)
自动创建表(有些 ORM 支持)
例如:
UserDao → 它的泛型参数是 User
OrderDao → 它的泛型参数是 Order
如何获取
Java 在运行时可以通过 getGenericSuperclass() 拿到父类声明的泛型信息:
Class<?> clazz = UserDao.class;
Type genericSuperclass = clazz.getGenericSuperclass();if (genericSuperclass instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) genericSuperclass;Type actualType = pt.getActualTypeArguments()[0];System.out.println(actualType); // 输出 class User
}
即:
UserDao extends BaseDao<User> 运行时通过反射拿到BaseDao<User>
泛型化参数为User.class
在在 Hibernate/MyBatis 这种框架里:
BaseDao<T>
是公共的 CRUD 逻辑
子类指定了泛型(UserDao extends BaseDao<User>
)
框架启动时反射父类泛型 → 拿到 User.class
再用 User.class 做:
映射数据库表名(user)
映射字段(name → name)
生成 SQL 语句(INSERT INTO user …)
那么可以假设框架便知道这里
T = User
表名 = user
字段 = id, name, age…
反射父类的泛型在 ORM 里,就是让框架“自动知道”你要操作的实体类型,而不用你每次都手动告诉它。
2.反射 接口 的泛型
public interface MyInterface<T> {}
public class MyClass implements MyInterface<Integer> {}
Type[] genericInterfaces = MyClass.class.getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {if (genericInterface instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) genericInterface;for (Type type : pt.getActualTypeArguments()) {System.out.println(type); // 输出: class java.lang.Integer}}
}
方法:Class.getGenericInterfaces()
返回类型:Type[](每个元素可能是 ParameterizedType)
应用场景:比如获取某个类实现的泛型 DAO 接口的实体类型。
反射 属性 上的泛型
public class User {private List<String> names;
}
Field field = User.class.getDeclaredField("names");
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) genericType;for (Type type : pt.getActualTypeArguments()) {System.out.println(type); // 输出: class java.lang.String}
}
方法:Field.getGenericType()
返回类型:Type(一般是 ParameterizedType)
应用场景:序列化框架(如 Gson、Jackson)反射字段时获取集合元素的类型。
像 Gson、Jackson 这种 JSON 序列化框架,需要把 Java 对象和 JSON 字符串互相转换,如果属性是一个集合(List<T>、Map<K,V>
等),框架必须知道 集合元素的泛型类型 才能正确序列化 / 反序列化。
反序列化时
当 JSON 中出现 users: [ {name:“张三”}, {name:“李四”} ] 时,框架需要知道 users 是 List,才能循环创建 User 对象,而不是 Object。
序列化时
框架可以通过泛型类型决定如何将集合中的元素写成 JSON,比如 User 对象会被转换成 {“name”:“xxx”}。
反射 方法参数 上的泛型
public class Service {public void processData(List<Integer> data) {}
}
Method method = Service.class.getMethod("processData", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type paramType : genericParameterTypes) {if (paramType instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) paramType;for (Type type : pt.getActualTypeArguments()) {System.out.println(type); // 输出: class java.lang.Integer}}
}
方法:Method.getGenericParameterTypes()
返回类型:Type[]
应用场景:反射方法调用时,动态推断参数泛型类型。
反射 方法返回值 的泛型
public class Service {public List<Double> getValues() { return null; }
}
Method method = Service.class.getMethod("getValues");
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) returnType;for (Type type : pt.getActualTypeArguments()) {System.out.println(type); // 输出: class java.lang.Double}
}
方法:Method.getGenericReturnType()
返回类型:Type
应用场景:动态 API 调用时获取返回集合元素的类型。
反射 构造方法参数上的泛型
public class MyClass {public MyClass(List<Long> list) {}
}
Constructor<?> constructor = MyClass.class.getConstructor(List.class);
Type[] genericParameterTypes = constructor.getGenericParameterTypes();
for (Type paramType : genericParameterTypes) {if (paramType instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) paramType;for (Type type : pt.getActualTypeArguments()) {System.out.println(type); // 输出: class java.lang.Long}}
}
方法:Constructor.getGenericParameterTypes()
返回类型:Type[]
应用场景:依赖注入(DI)框架用来分析构造参数的泛型类型
依赖注入(DI)框架用来分析构造参数的泛型类型
所谓赖注入(DI)框架用来分析构造参数的泛型类型” 这个说法,其实是在描述 DI 容器在自动装配(autowiring)时,会用反射去解析构造方法的参数类型,包括它的泛型信息。
1.背景
依赖注入框架(比如 Spring、Guice、Dagger)在创建对象时,通常会调用构造方法(Constructor Injection)或者 Setter/Field 注入。
如果一个构造方法的参数是泛型类型,比如:
public class Service {private final Repository<User> userRepository;public Service(Repository<User> userRepository) {this.userRepository = userRepository;}
}
框架在实例化 Service 时,需要知道:
1.你要注入的不是任意 Repository
2.而是 Repository<User>
这种带泛型参数的类型
2. 为什么要分析泛型类型
因为:
可能有多个 Repository 的实现,比如 Repository<User>
、Repository<Order>
如果不看泛型,只知道是 Repository,容器没法判断用哪个
而通过分析泛型参数,DI 框架可以精确找到要注入的 Bean
3.框架是怎么分析的
Java 运行时通过 java.lang.reflect 包来获取泛型信息。
对于构造方法,步骤大致是:
// getConstructors() 返回一个 Constructor 对象数组
// 里面包含了类的所有 public 构造方法(不包括 private、protected、
// package-private 构造器)
// 如果一个类有多个 public 构造器,这个数组会有多个元素,顺序并不保证固定(通常是声明顺序,但规范中不要求)
// [0] 代表取数组中第一个构造器,如果类有多个构造方法,显然不合适了
// 更安全做法:Constructor<Service> constructor = Service.class.getConstructor(Repository.class);Constructor<?> constructor = Service.class.getConstructors()[0];// 获取构造参数的泛型类型
Type[] genericParameterTypes = constructor.getGenericParameterTypes();for (Type type : genericParameterTypes) {if (type instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) type;System.out.println("原始类型: " + pt.getRawType());System.out.println("泛型参数: " + Arrays.toString(pt.getActualTypeArguments()));}
}
输出:
原始类型: interface Repository
泛型参数: [class User]
这样,DI 框架就知道:
原始类型是 Repository
泛型参数是 User
4.现实中的应用
在 Spring 里,如果你有:
@Component
class UserRepository implements Repository<User> {}@Component
class Service {public Service(Repository<User> repo) { ... }
}
Spring 会:
扫描到 Service 构造器
解析参数类型为 Repository<User>
在 Bean 容器里找到符合条件的 Bean(UserRepository)
自动注入
示例:迷你Spring
这个例子主要演示两个重点:
怎么用 ParameterizedType 读取构造参数的泛型信息
根据泛型类型找到对应的依赖对象
import java.lang.reflect.*;
import java.util.*;// ======== 模拟 Repository 接口 ========
interface Repository<T> {void save(T entity);
}// ======== 用户实体 ========
class User {String name;public User(String name) { this.name = name; }
}// ======== Repository 实现类 ========
class UserRepository implements Repository<User> {@Overridepublic void save(User entity) {System.out.println("保存用户:" + entity.name);}
}// ======== Service 类,依赖 Repository<User> ========
class UserService {private final Repository<User> userRepository;// 通过构造方法注入public UserService(Repository<User> userRepository) {this.userRepository = userRepository;}public void register(String name) {userRepository.save(new User(name));}
}// ======== 迷你 DI 容器 ========
class MiniSpring {private final Map<Class<?>, Object> beanContainer = new HashMap<>();// 注册一个 Beanpublic <T> void registerBean(Class<T> clazz, T instance) {beanContainer.put(clazz, instance);}// 根据类创建实例,自动解析构造器泛型依赖public <T> T createBean(Class<T> clazz) throws Exception {Constructor<?> constructor = clazz.getConstructors()[0];Type[] genericParameterTypes = constructor.getGenericParameterTypes();Object[] args = new Object[genericParameterTypes.length];for (int i = 0; i < genericParameterTypes.length; i++) {Type type = genericParameterTypes[i];if (type instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) type;// 原始类型(Repository)Class<?> rawType = (Class<?>) pt.getRawType();// 泛型参数类型(User)Class<?> actualType = (Class<?>) pt.getActualTypeArguments()[0];// 根据泛型匹配容器中符合条件的 Beanfor (Object bean : beanContainer.values()) {if (rawType.isAssignableFrom(bean.getClass())) {// 进一步判断 bean 是否实现了对应泛型Type[] interfaces = bean.getClass().getGenericInterfaces();for (Type iface : interfaces) {if (iface instanceof ParameterizedType) {ParameterizedType ipt = (ParameterizedType) iface;if (ipt.getRawType().equals(rawType) &&ipt.getActualTypeArguments()[0].equals(actualType)) {args[i] = bean;}}}}}}}return (T) constructor.newInstance(args);}
}// ======== 测试运行 ========
public class Main {public static void main(String[] args) throws Exception {MiniSpring container = new MiniSpring();// 注册一个 Repository<User>container.registerBean(UserRepository.class, new UserRepository());// 自动解析泛型并注入UserService userService = container.createBean(UserService.class);userService.register("张三");}
}
这段代码:
// 1.创建容器,存放各种Bean对象
MiniSpring miniSpring = new MiniSpring();
// 2.向容器注入一个Bean,Key是UserRepository.class类型,值是new UserRepository() 实例对象
miniSpring.registerBean(UserRepository.class,new UserRepository());
// 3.创建并自动注入依赖
//找到 UserService 的构造方法(它的构造参数是 Repository<User>)
// 从容器里查找是否有匹配的 Bean,此时发现 UserRepository 实现了 //Repository<User>于是把它当作构造参数传进去
//反射调用构造器,创建一个 已经注入好依赖的 UserService
UserService userService = miniSpring.createBean(UserService.class);
// 调用 UserService 的方法时,它会用注入好的 UserRepository 保存用户:
userService.register("张三");
梳理createBean方法流程:
1.通过UserService userService = container.createBean(UserService.class);调用creatBean方法,此时方法里面的clazz就是UserService.class
2.传入UserService.class类,使用其构造方法,创建实例,然后自动注入bean对象
由于UserService.class只有一个构造方法,故取第一个即可。即指向了public UserService(Repository<User> userRepository){...}
3.获取构造方法的参数上的泛型,由于构造方法中只有一个参数,所以获取的泛型就是Repository<User>
,它属于ParameterizedType,继承自Type接口
4.循环遍历从构造方法中得到的参数上的泛型
5.判断是否是带泛型的参数: if(type instanceof ParamterizedType)
6.取原始类型和泛型参数:
即 Class<?> rawType = (Class<?>) pt.getRawType()和
Class<?> actuaType = (Class<?>) pt.getActualTypeArguments()[0]
得到:
rawType = Repository.class
actualType = User.class
7.从Bean容器中逐个查找符合的Bean
容器中有 beanContainer = { UserRepository.class -> new UserRepository() }
8.先判断原始类型是否兼容:if (rawType.isAssignableFrom(bean.getClass()))
由于rawType = Repository.class,而bean.getClass() = UserRepository.class
因为 UserRepository implements Repository<User>
,所以条件成立
A.isAssignableFrom(B) 判断的是: 类型 B 的对象,能不能赋值给类型 A 的变量。换句话说:如果 B 是 A 的同类、子类,或者实现了 A 这个接口 → true
否则 → false
9.判断泛型是否匹配:
Type[] interfaces = bean.getClass().getGenericInterfaces();
UserRepository 实现的接口是 Repository<User>
这是 ParameterizedType,所以可以继续判断:
if (ipt.getRawType().equals(rawType) && ipt.getActualTypeArguments()[0].equals(actualType))
这里的
ipt.getRawType() = Repository.class
ipt.getActualTypeArguments()[0] = User.class
跟我们从构造方法的参数得到的我们需要 Repository<User>
完全一致
10.注入到构造参数:args[0] = bean;
11.调用构造方法:return (T) constructor.newInstance(args);
原本constructor.newInstance返回Object,转型为T
等价于:new UserService(new UserRepository());
自此userService 就完成了自动注入。
总结
第一步:向容器注入Bean,提供给使用
第二步:查找类的所有构造参数泛型【只考虑所有参数都是有泛型的】,找到它的原始类型和泛型参数类型
第三步:对Bean容器中的所有bean对象,逐个分析他们的类型,如果能和参数的原始类型匹配上,则继续查看其是否实现了对应泛型
第四步:匹配bean对象的类型所实现的接口类型和从构造方法得到的原始类型是否一样:
Type[] interfaces = bean.getClass().getGenericInterfaces();//类的实现接口
ipt.getRawType().equals(rawType)
以及其实现接口的泛型参数类是否和从构造方法得到的参数的泛型参数类型一样
ipt.getActualTypeArguments()[0].equals(actualTypeArgument)
第五步:如果一样则该bean作为构造器的一个参数
QA
关于静态方法和方法的泛型类型参数问题:
有两个 T,但它们其实是同一个泛型类型参数
,不是两个不同的东西。
第一个 <T>
这是 泛型类型参数的声明。
放在方法返回类型前面,表示这个方法是泛型方法
,方法内部和返回值都可以使用这个类型参数 T。
它告诉编译器:“这个方法支持一个叫做 T 的泛型类型,在调用时由传入参数推断出来。”
第二个 T
这是返回类型,使用了上面声明的泛型参数 T。
表示该方法返回的对象类型和传入的 clazz 的类型是一致的。
我自己学习时候有误解,以为只有静态方法需要在返回类型前面加上<T>
,实际上任何泛型方法都需要在方法签名前面显示声明类型参数。
只要方法自己引入了泛型类型参数(不依赖于类级别的泛型声明),就要写 <T>
:
这里的 <T>
表示这个方法引入了新的泛型参数 T,不管它是静态的还是实例方法,都得写。
为什么有时候不写 <T>
?
如果类本身已经声明了泛型参数,就可以直接用:
class Box<T> {public T get() { ... } // 不需要额外写 <T>,因为类已经有了
}
但如果方法里的 T 不是类的泛型参数,就必须在方法上显式声明 <T>
。
tips:静态方法的特殊性
静态方法不能用类的泛型参数(因为静态方法在类加载时就存在,而类的泛型是实例相关的),
所以静态方法想用泛型必须自己声明:
可以看到下面的类已经有泛型参数,但是静态方法还是提示没有,要么改为非静态方法,要么在返回值类型前面加入泛型
现在可以了:
所以为什么我自己会感觉泛型方法大多出现在静态方法上,但其实这是因为静态方法没得偷懒。