鍍金池/ 教程/ Java/ google Guava 包的 reflection 解析
不可變集合
排序: Guava 強(qiáng)大的”流暢風(fēng)格比較器”
強(qiáng)大的集合工具類:java.util.Collections 中未包含的集合工具
新集合類型
常見 Object 方法
I/O
前置條件
字符串處理:分割,連接,填充
散列
原生類型
數(shù)學(xué)運(yùn)算
使用和避免 null
Throwables:簡化異常和錯(cuò)誤的傳播與檢查
google Guava 包的 ListenableFuture 解析
事件總線
緩存
函數(shù)式編程
區(qū)間
集合擴(kuò)展工具類
Google-Guava Concurrent 包里的 Service 框架淺析
google Guava 包的 reflection 解析

google Guava 包的 reflection 解析

譯者:萬天慧(武祖)

由于類型擦除,你不能夠在運(yùn)行時(shí)傳遞泛型類對(duì)象——你可能想強(qiáng)制轉(zhuǎn)換它們,并假裝這些對(duì)象是有泛型的,但實(shí)際上它們沒有。

舉個(gè)例子:


    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>

Guava 提供了 TypeToken, 它使用了基于反射的技巧甚至讓你在運(yùn)行時(shí)都能夠巧妙的操作和查詢泛型類型。想象一下 TypeToken 是創(chuàng)建,操作,查詢泛型類型(以及,隱含的類)對(duì)象的方法。

Guice 用戶特別注意:TypeToken 與類 GuiceTypeLiteral 很相似,但是有一個(gè)點(diǎn)特別不同:它能夠支持非具體化的類型,例如 T,List,甚至是 List<? extends Number>;TypeLiteral 則不能支持。TypeToken 也能支持序列化并且提供了很多額外的工具方法。

背景:類型擦除與反射

Java 不能在運(yùn)行時(shí)保留對(duì)象的泛型類型信息。如果你在運(yùn)行時(shí)有一個(gè) ArrayList 對(duì)象,你不能夠判定這個(gè)對(duì)象是有泛型類型 ArrayList 的 —— 并且通過不安全的原始類型,你可以將這個(gè)對(duì)象強(qiáng)制轉(zhuǎn)換成 ArrayList

但是,反射允許你去檢測(cè)方法和類的泛型類型。如果你實(shí)現(xiàn)了一個(gè)返回 List 的方法,并且你用反射獲得了這個(gè)方法的返回類型,你會(huì)獲得代表 List 的 ParameterizedType。

TypeToken 類使用這種變通的方法以最小的語法開銷去支持泛型類型的操作。

介紹

獲取一個(gè)基本的、原始類的 TypeToken 非常簡單:


    TypeToken<String> stringTok = TypeToken.of(String.class);
    TypeToken<Integer> intTok = TypeToken.of(Integer.class);

為獲得一個(gè)含有泛型的類型的 TypeToken —— 當(dāng)你知道在編譯時(shí)的泛型參數(shù)類型 —— 你使用一個(gè)空的匿名內(nèi)部類:


    TypeToken<List<String>> stringListTok = new TypeToken<List<String>>() {};

或者你想故意指向一個(gè)通配符類型:


    TypeToken<Map<?, ?>> wildMapTok = new TypeToken<Map<?, ?>>() {};

TypeToken 提供了一種方法來動(dòng)態(tài)的解決泛型類型參數(shù),如下所示:


    static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
        return new TypeToken<Map<K, V>>() {}
            .where(new TypeParameter<K>() {}, keyToken)
            .where(new TypeParameter<V>() {}, valueToken);
    }
    ...
    TypeToken<Map<String, BigInteger>> mapToken = mapToken(
        TypeToken.of(String.class),
        TypeToken.of(BigInteger.class)
    );
    TypeToken<Map<Integer, Queue<String>>> complexToken = mapToken(
       TypeToken.of(Integer.class),
       new TypeToken<Queue<String>>() {}
    );

注意如果 mapToken 只是返回了 new TypeToken>(),它實(shí)際上不能把具體化的類型分配到 K 和 V 上面,舉個(gè)例子


    class Util {
    static <K, V> TypeToken<Map<K, V>> incorrectMapToken() {
    return new TypeToken<Map<K, V>>() {};
    }
    }
    System.out.println(Util.<String, BigInteger>incorrectMapToken());
    // just prints out "java.util.Map<K, V>"

或者,你可以通過一個(gè)子類(通常是匿名)來捕獲一個(gè)泛型類型并且這個(gè)子類也可以用來替換知道參數(shù)類型的上下文類。


    abstract class IKnowMyType<T> {
    TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
    ...
    new IKnowMyType<String>() {}.type; // returns a correct TypeToken<String>

使用這種技術(shù),你可以,例如,獲得知道他們的元素類型的類。

查詢

TypeToken 支持很多種類能支持的查詢,但是也會(huì)把通用的查詢約束考慮在內(nèi)。

支持的查詢操作包括:

方法 描述
getType() 獲得包裝的 java.lang.reflect.Type.
getRawType() 返回大家熟知的運(yùn)行時(shí)類
getSubtype(Class<?>) 返回那些有特定原始類的子類型。舉個(gè)例子,如果這有一個(gè) Iterable 并且參數(shù)是 List.class,那么返回將是 List。
getSupertype(Class<?>) 產(chǎn)生這個(gè)類型的超類,這個(gè)超類是指定的原始類型。舉個(gè)例子,如果這是一個(gè) Set 并且參數(shù)是Iterable.class,結(jié)果將會(huì)是 Iterable。
isAssignableFrom(type) 如果這個(gè)類型是 assignable from 指定的類型,并且考慮泛型參數(shù),返回 true。List<? extends Number>是 assignable from List,但 List 沒有.
getTypes() 返回一個(gè) Set,包含了這個(gè)所有接口,子類和類是這個(gè)類型的類。返回的 Set 同樣提供了 classes()和 interfaces()方法允許你只瀏覽超類和接口類。
isArray() 檢查某個(gè)類型是不是數(shù)組,甚至是<? extends A[]>。
getComponentType() 返回組件類型數(shù)組。

resolveType

resolveType 是一個(gè)可以用來“替代”context token(譯者:不知道怎么翻譯,只好去 stackoverflow 去問了)中的類型參數(shù)的一個(gè)強(qiáng)大而復(fù)雜的查詢操作。例如,


    TypeToken<Function<Integer, String>> funToken = new TypeToken<Function<Integer, String>>() {};

    TypeToken<?> funResultToken = funToken.resolveType(Function.class.getTypeParameters()[1]));
    // returns a TypeToken<String>

TypeToken 將 Java 提供的 TypeVariables 和 context token 中的類型變量統(tǒng)一起來。這可以被用來一般性地推斷出在一個(gè)類型相關(guān)方法的返回類型:


    TypeToken<Map<String, Integer>> mapToken = new TypeToken<Map<String, Integer>>() {};
    TypeToken<?> entrySetToken = mapToken.resolveType(Map.class.getMethod("entrySet").getGenericReturnType());
    // returns a TypeToken<Set<Map.Entry<String, Integer>>>

Invokable

Guava 的 Invokable 是對(duì) java.lang.reflect.Method 和 java.lang.reflect.Constructor 的流式包裝。它簡化了常見的反射代碼的使用。一些使用例子:

方法是否是 public 的?

JDK:


    Modifier.isPublic(method.getModifiers())

Invokable:


    invokable.isPublic()

方法是否是 package private?

JDK:


     !(Modifier.isPrivate(method.getModifiers()) || Modifier.isPublic(method.getModifiers()))

Invokable:


    invokable.isPackagePrivate()

方法是否能夠被子類重寫?

JDK:


    !(Modifier.isFinal(method.getModifiers())
    || Modifiers.isPrivate(method.getModifiers())
    || Modifiers.isStatic(method.getModifiers())
    || Modifiers.isFinal(method.getDeclaringClass().getModifiers()))

Invokable:


    invokable.isOverridable()

方法的第一個(gè)參數(shù)是否被定義了注解@Nullable?

JDK:


    for (Annotation annotation : method.getParameterAnnotations[0]) {
      if (annotation instanceof Nullable) {
        return true;
      }
    }
    return false;

Invokable:


    invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)

構(gòu)造函數(shù)和工廠方法如何共享同樣的代碼?

你是否很想重復(fù)自己,因?yàn)槟愕姆瓷浯a需要以相同的方式工作在構(gòu)造函數(shù)和工廠方法中?

Invokable 提供了一個(gè)抽象的概念。下面的代碼適合任何一種方法或構(gòu)造函數(shù):


    invokable.isPublic();
    invokable.getParameters();
    invokable.invoke(object, args);

List 的 List.get(int)返回類型是什么?

Invokable 提供了與眾不同的類型解決方案:


    Invokable<List<String>, ?> invokable = new TypeToken<List<String>>(){}.method(getMethod);
    invokable.getReturnType(); // String.class

Dynamic Proxies

newProxy()

實(shí)用方法 Reflection.newProxy(Class, InvocationHandler)是一種更安全,更方便的 API,它只有一個(gè)單一的接口類型需要被代理來創(chuàng)建 Java 動(dòng)態(tài)代理時(shí)

JDK:


    Foo foo = (Foo) Proxy.newProxyInstance(
    Foo.class.getClassLoader(),
    new Class<?>[] {Foo.class},
    invocationHandler);

Guava:


    Foo foo = Reflection.newProxy(Foo.class, invocationHandler);

AbstractInvocationHandler

有時(shí)候你可能想動(dòng)態(tài)代理能夠更直觀的支持 equals(),hashCode()和 toString(),那就是:

  1. 一個(gè)代理實(shí)例 equal 另外一個(gè)代理實(shí)例,只要他們有同樣的接口類型和 equal 的 invocation handlers。
  2. 一個(gè)代理實(shí)例的 toString()會(huì)被代理到 invocation handler 的 toString(),這樣更容易自定義。

AbstractInvocationHandler 實(shí)現(xiàn)了以上邏輯。

除此之外,AbstractInvocationHandler 確保傳遞給 handleInvocation(Object, Method, Object[]))的參數(shù)數(shù)組永遠(yuǎn)不會(huì)空,從而減少了空指針異常的機(jī)會(huì)。

ClassPath

嚴(yán)格來講,Java 沒有平臺(tái)無關(guān)的方式來瀏覽類和類資源。不過一定的包或者工程下,還是能夠?qū)崿F(xiàn)的,比方說,去檢查某個(gè)特定的工程的慣例或者某種一直遵從的約束。

ClassPath 是一種實(shí)用工具,它提供盡最大努力的類路徑掃描。用法很簡單:


    ClassPath classpath = ClassPath.from(classloader); // scans the class path used by classloader
    for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClasses("com.mycomp.mypackage")) {
      ...
    }

在上面的例子中,ClassInfo 是被加載類的句柄。它允許程序員去檢查類的名字和包的名字,讓類直到需要的時(shí)候才被加載。

值得注意的是,ClassPath 是一個(gè)盡力而為的工具。它只掃描jar文件中或者某個(gè)文件目錄下的 class 文件。也不能掃描非 URLClassLoader 的自定義 class loader 管理的 class,所以不要將它用于關(guān)鍵任務(wù)生產(chǎn)任務(wù)。

Class Loading

工具方法 Reflection.initialize(Class…)能夠確保特定的類被初始化——執(zhí)行任何靜態(tài)初始化。

使用這種方法的是一個(gè)代碼異味,因?yàn)殪o態(tài)傷害系統(tǒng)的可維護(hù)性和可測(cè)試性。在有些情況下,你別無選擇,而與傳統(tǒng)的框架,操作間,這一方法有助于保持代碼不那么丑。