假设有以下接口:
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