假设有以下接口:
public interface Factory<T> {
T create();
}
这是一个泛型接口,在实现Factory
的时候需要指定泛型参数:
public class StringFactory implements Factory<String> {
@Override
public String create() {
return "hello";
}
}
public class IntegerFactory implements Factory<Integer> {
@Override
public Integer create() {
return 123;
}
}
假如我们要获取一个Factory
实例的泛型参数,要怎么做呢?可以使用Java反射API提供的函数getGenericInterfaces
:
public class Main {
public static void main(String[] args) {
System.out.println(getFactoryTypeParameter(new StringFactory()));
System.out.println(getFactoryTypeParameter(new IntegerFactory()));
}
// 获取接口的泛型参数
private static Class<?> getFactoryTypeParameter(Factory<?> factory) {
return (Class<?>) ((ParameterizedType) factory.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
}
}
输出结果:
class java.lang.String
class java.lang.Integer
一切看起来很完美,但是,假如我们传递一个lambda表达式给getFactoryTypeParameter
函数会怎么样呢?
System.out.println(getFactoryTypeParameter(() -> 3.14));
很不幸,控制台出现了异常:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.reflect.ParameterizedType (java.lang.Class and java.lang.reflect.ParameterizedType are in module java.base of loader 'bootstrap')
at byx.test.genetic.Main.getFactoryTypeParameter(Main.java:14)
at byx.test.genetic.Main.main(Main.java:9)
在我们的印象中,lambda表达式就是匿名内部类的一种简化写法,我们尝试一下将lambda表达式还原成匿名内部类:
System.out.println(getFactoryTypeParameter(new Factory<Double>() {
@Override
public Double create() {
return 3.14;
}
}));
令人惊讶的是,此时getFactoryTypeParameter
又正常工作了,控制台输出:
class java.lang.Double
从以上可以看出,匿名内部类和lambda表达式还是有本质上的区别的,因为getGenericInterfaces
函数对lambda表达式无效,而对匿名内部类有效。这里不深究造成这种差异的具体原因,而是只给出解决方案。
首先新建一个TypeRef
类:
public abstract class TypeRef<T> {
protected TypeRef(Factory<T> factory) {}
public Class<?> getGenericType() {
return (Class<?>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
TypeRef
是一个抽象类,而且带一个泛型参数T
,在它的构造方法中我们需要传入一个Factory
的实例,这个实例主要用来帮助TypeRef
确定T
的类型。
TypeRef
中还有一个getGenericType
方法,它使用了Java的getGenericSuperclass
API,这个函数可以用来获取抽象父类的泛型参数。
我们需要像下面这样使用TypeRef
:
public class Main {
public static void main(String[] args) {
System.out.println(getFactoryTypeParameter(new TypeRef<>(() -> 3.14){}));
}
// 获取接口的泛型参数(使用TypeRef)
private static <T> Class<?> getFactoryTypeParameter(TypeRef<T> typeRef) {
return typeRef.getGenericType();
}
}
new TypeRef<>(() -> 3.14){}
这行代码实际上原地创建了一个TypeRef
的实现类,编译器可以根据类型推导得出其泛型参数为Double
,因此可以间接利用TypeRef
来得到lambda表达式的泛型参数,这就是改进后的getFactoryTypeParameter
的主要逻辑。
运行以上代码,正确地输出了结果:
class java.lang.Double
当然,改进后的getFactoryTypeParameter
对于之前的StringFactory
和IntegerFactory
也适用:
System.out.println(getFactoryTypeParameter(new TypeRef<>(new StringFactory()){}));
System.out.println(getFactoryTypeParameter(new TypeRef<>(new IntegerFactory()){}));
输出如下:
class java.lang.String
class java.lang.Integer