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