2024/05/27

Guava Reflection

TypeToken

因為 java 有 type erasure 特性,無法在 runtime 得知 generic Class 物件

TypeToken 使用了 workaround 方法,可處理 generict types,就是能夠在 runtime 檢測 Generic Type

    @Test
    public void typeToken() {
        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));
        // returns true, even though ArrayList<String> is not assignable from ArrayList<Integer>

        TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
        TypeToken<List<Integer>> integerListToken = new TypeToken<List<Integer>>() {};
        TypeToken<List<? extends Number>> numberTypeToken = new TypeToken<List<? extends Number>>() {};
        assertFalse(stringListToken.isSubtypeOf(integerListToken));
        assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
        assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
    }

利用 TypeToken 在 runtime 取得 complex type 資訊

    abstract class ParametrizedClass<T> {
        TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
    @Test public void typeToken2() throws NoSuchMethodException {
        //// 產生一個 class 裡面儲存了 TypeToken 欄位
        ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
        assertEquals(parametrizedClass.type, TypeToken.of(String.class));

        //// 也能產生 TypeToken of a complex type,裡面有多個 generic type
        // runtime 還是能取得 type 資訊
        TypeToken<Function<Integer, String>> funToken
                = new TypeToken<Function<Integer, String>>() {};
        TypeToken<?> funResultToken = funToken
                .resolveType(Function.class.getTypeParameters()[1]);
        assertEquals(funResultToken, TypeToken.of(String.class));

        /// 也能在 Map 裡面得知 entry 的 data type
        TypeToken<Map<String, Integer>> mapToken
                = new TypeToken<Map<String, Integer>>() {};
        TypeToken<?> entrySetToken = mapToken
                .resolveType(Map.class.getMethod("entrySet")
                        .getGenericReturnType());
        assertEquals(
                entrySetToken,
                new TypeToken<Set<Map.Entry<String, Integer>>>() {});
    }

Invokable

Invokable 是 java.lang.reflec.Method 及 java.lang.reflect.Constructor 的 fluent wrapper,提供簡化後的 API

    @Test
    public void invokable() throws NoSuchMethodException {
        Method method = CustomClass.class.getMethod("somePublicMethod");
        Invokable<CustomClass, ?> invokable
                = new TypeToken<CustomClass>() {}
                .method(method);
        // 分別透過 Guava 及 標準的 JAVA reflection API 檢測是否有 somePublicMethod 這個 method
        boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
        boolean isPublicGuava = invokable.isPublic();

        assertTrue(isPublicStandradJava);
        assertTrue(isPublicGuava);

        ////////
        // checking if a method is overridable
        Method method2 = CustomClass.class.getMethod("notOverridablePublicMethod");
        // 用 Guava TypeToken 比較簡單
        Invokable<CustomClass, ?> invokable2
                = new TypeToken<CustomClass>() {}.method(method2);

        // 用標準 java 的方法
        boolean isOverridableStandardJava = (!(Modifier.isFinal(method2.getModifiers())
                || Modifier.isPrivate(method2.getModifiers())
                || Modifier.isStatic(method2.getModifiers())
                || Modifier.isFinal(method2.getDeclaringClass().getModifiers())));
        boolean isOverridableFinalGauava = invokable2.isOverridable();

        assertFalse(isOverridableStandardJava);
        assertFalse(isOverridableFinalGauava);
    }

References

Guide to Guava's Reflection Utilities | Baeldung

ReflectionExplained · google/guava Wiki · GitHub

沒有留言:

張貼留言