Java中獲取lambda表示式的泛型型別
假設有以下介面:
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