1. 程式人生 > 其它 >Java中獲取lambda表示式的泛型型別

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的getGenericSuperclassAPI,這個函式可以用來獲取抽象父類的泛型引數。

我們需要像下面這樣使用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對於之前的StringFactoryIntegerFactory也適用:

System.out.println(getFactoryTypeParameter(new TypeRef<>(new StringFactory()){}));
System.out.println(getFactoryTypeParameter(new TypeRef<>(new IntegerFactory()){}));

輸出如下:

class java.lang.String
class java.lang.Integer