java:用Guava的TypeToken优雅处理通配符类型(WildcardType): ? extends Number
在日常开发中我们经常会遇到泛型和通配符类型(WildcardType),比如当我们需要处理List<? extends Number>
这样的类型时,如何优雅地创建这样的类型表示?本文将重点介绍如何通过Guava的TypeToken来实现通配符类型的构造,并结合开源项目中的实践进行深入解读。
一、通配符类型的痛点
在Java的泛型系统中,通配符类型(如<? extends T>
和<? super T>
)能提供更灵活的类型约束。但在反射场景中,我们无法直接通过new
操作符或Class对象创建这种类型,这在需要动态生成类型的场合非常棘手。
考虑如下场景:
// 如何用代码表示List<? extends Number>类型?
Type listType = new ParameterizedType() {
public Type[] getActualTypeArguments() {
return new Type[] { /* 如何表示? extends Number */ };
}
// ...
}
我们希望能有一个工具来优雅地构造这样的通配符类型。
二、WildcardTyper工具类解析
以下是一个专为通配符类型设计的工具类,来自真实项目代码:
public abstract class WildcardTyper<T> {
public final WildcardType wildcardType;
protected WildcardTyper(boolean subType) {
TypeToken<?> token = subType
? new TypeToken<Class<? extends T>>() {}
: new TypeToken<Class<? super T>>() {};
this.wildcardType = (WildcardType) ((ParameterizedType) token.getType())
.getActualTypeArguments()[0];
}
// 子类型快捷方式
public abstract static class SubOf<T> extends WildcardTyper<T> {
public SubOf() { super(true); }
}
// 超类型快捷方式
public abstract static class SuperOf<T> extends WildcardTyper<T> {
public SuperOf() { super(false); }
}
}
核心原理:
- TypeToken魔法:利用Guava的TypeToken捕获泛型参数类型信息
- 参数类型提取:构造如
Class<? extends T>
的参数化类型,从中提取通配符类型 - 子类化技巧:通过继承时的具体类型声明保留泛型参数信息
三、实战使用示例
示例1:创建基础通配符类型
// ? extends Number
WildcardType extendsNumber = new WildcardTyper.SubOf<Number>() {}.wildcardType;
// ? super Integer
WildcardType superInteger = new WildcardTyper.SuperOf<Integer>() {}.wildcardType;
示例2:复杂容器类型构建
// List<? extends Number>
Type listOfNumbers = new ParameterizedTypeImpl(
List.class,
new Type[] { new WildcardTyper.SubOf<Number>() {}.wildcardType }
);
// Map<String, ? super Date>
Type mapWithSuper = new ParameterizedTypeImpl(
Map.class,
new Type[] { String.class, new WildcardTyper.SuperOf<Date>() {}.wildcardType }
);
示例3:类型系统验证
@Test
public void testTypeCompatibility() {
// 验证Float可以赋值给? extends Number
WildcardType numType = new WildcardTyper.SubOf<Number>() {}.wildcardType;
assertTrue(TypeToken.of(numType).isSupertypeOf(Float.class));
// 验证List<? extends Number>能接受ArrayList<Double>
ParameterizedType listType = new ParameterizedTypeImpl(
List.class, new Type[] { numType });
assertTrue(TypeToken.of(listType).isSupertypeOf(ArrayList.class));
}
四、高级技巧:类型转换中的通配符处理
在BaseTypeTransformer的代码中,我们看到了这样的高级应用:
// 定义从Date到子类型的转换器
transTable.put(Date.class,
new WildcardTyper.SubOf<Date>() {}.wildcardType,
new Date2SubTransformer()
);
// 转换器实现
class Date2SubTransformer extends BaseFunction<Date, Date> {
public Date doApply(Date input) {
return (Date) outputType.getRawType()
.getConstructor(long.class).newInstance(input.getTime());
}
}
工作原理:
- 通过
SubOf<Date>
声明目标类型为? extends Date
- 运行时动态确定实际类型(如
java.sql.Date
) - 反射调用具体的构造函数实例化
BaseTypeTransformer的完整代码:
common-base2/src/main/java/com/gitee/l0km/com4j/basex/BaseTypeTransformer.java · 10km/common-java - 码云 - 开源中国
五、为何不用静态工厂方法?
观察WildcardTyper的设计,可能有读者会问:为什么不像这样提供静态方法?
// 期望的API(但这行不通!)
WildcardType type = WildcardTyper.createWildcard(Number.class, true);
根本原因在于类型擦除:静态方法无法捕获泛型参数的具体类型信息。而通过抽象类的继承模式:
- 保留了完整的泛型类型信息
- 通过
getClass().getGenericSuperclass()
获取类型参数 - 使TypeToken能准确推断出通配符的边界
六、适用场景分析
- 泛型反射操作:在需要解析或生成泛型类型的反射场景
- 类型转换系统:构建灵活的类型转换体系
- 序列化/反序列化:处理带有通配符的复杂泛型类型
- DI容器实现:解析依赖注入时的泛型限定
七、总结与最佳实践
最佳实践建议:
- 尽量使用子类模式:如
new SubOf<Number>() {}
保持类型安全 - 结合TypeToken使用:借助Guava的强大类型推断能力
- 注意类型边界:明确理解
extends
和super
的行为差异 - 防御式编程:在反射访问时做好类型校验
性能考虑:TypeToken的内部缓存机制保证了重复使用的性能,但在高频场景建议缓存生成的WildcardType实例。
通过本文的代码示例和原理分析,相信读者已经掌握了使用TypeToken优雅处理通配符类型的诀窍。这个技巧在处理复杂泛型系统时显示出极大的威力,值得加入每个Java开发者的工具箱。
八、WildcardTyper 完整代码
package com.gitee.l0km.com4j.basex.reflection;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.WildcardType;
import com.google.common.reflect.TypeToken;
/**
* 用于生成通配符类型(WildcardType)的抽象工具类,支持上界和下界通配符类型<br>
* 示例:
* <pre>
* WildcardTyper<Number> superWildcardTyper = new WildcardTyper<Number>(false) {};
* superWildcardTyper.wildcardType ==> ? super Number
* WildcardTyper<Number> extendsWildcardTyper = new WildcardTyper<Number>(true) {};
* extendsWildcardTyper.wildcardType ==> ? extends Number
* </pre>
* @param <T> 通配符类型中的边界类型参数,决定通配符类型的上下界基础类型
* @author guyadong
* @since 4.4.0
*/
public abstract class WildcardTyper<T> {
/** 生成的通配符类型实例,如:? super Number */
public final WildcardType wildcardType;
/** 延迟初始化的TypeToken实例,线程安全 */
private volatile TypeToken<?> token;
/**
* 构造方法,根据subType参数生成对应的通配符类型
*
* @param subType 通配符类型方向标识:
* - true:生成上界通配符类型(? extends T)
* - false:生成下界通配符类型(? super T)
*/
@SuppressWarnings("serial")
protected WildcardTyper(boolean subType){
TypeToken<?> _token;
// 根据subType选择创建不同通配符类型的TypeToken
if(subType) {
// 创建上界通配符类型TypeToken(Class<? extends T>)
_token = new TypeToken<Class<? extends T>>(getClass()) {};
}else {
// 创建下界通配符类型TypeToken(Class<? super T>)
_token = new TypeToken<Class<? super T>>(getClass()) {};
}
// 从ParameterizedType中提取实际的通配符类型参数
this.wildcardType = (WildcardType) ((ParameterizedType) _token.getType()).getActualTypeArguments()[0];
}
/**
* 获取与wildcardType对应的TypeToken实例
*
* @return 延迟初始化的TypeToken对象,保证线程安全的单例模式
*/
public TypeToken<?> getToken() {
TypeToken<?> _token = this.token;
if(null == _token){
// 使用双重检查锁定保证线程安全
synchronized(wildcardType){
_token = this.token;
if(null == _token){
this.token = _token = TypeToken.of(wildcardType);
}
}
}
return _token;
}
/**
* 用于生成下界通配符类型 (? super T) 的抽象基类
*/
public abstract static class SuperOf<T> extends WildcardTyper<T> {
protected SuperOf() {
super(false); // 固定调用父类下界构造
}
}
/**
* 用于生成上界通配符类型 (? extends T) 的抽象基类
*/
public abstract static class SubOf<T> extends WildcardTyper<T> {
protected SubOf() {
super(true); // 固定调用父类上界构造
}
}
}
BaseTypeTransformer完整代码:common-base2/src/main/java/com/gitee/l0km/com4j/basex/BaseTypeTransformer.java · 10km/common-java - 码云 - 开源中国
WildcardTyperT的单元测试代码:common-base2/src/test/java/com/gitee/l0km/com4j/basex/reflection/WildcardTyperTest.java · 10km/common-java - 码云 - 开源中国
九、参考资料
-
Guava官方文档
Guava GitHub仓库
TypeToken用法详解 -
Java泛型官方教程
Oracle Java泛型教程
Java类型擦除机制 -
反射与通配符类型
Java WildcardType API文档
ParameterizedType深入解析 -
实例项目参考
BaseTypeTransformer完整源码
WildcardTyper实现细节 -
扩展阅读
Java类型系统科普
泛型类型安全最佳实践
希望这些资料能帮助你深入理解Java泛型和反射的奥秘!如果有其他技术问题,欢迎随时交流探讨。 💡