譯者:萬天慧(武祖)
由于類型擦除,你不能夠在運(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 與類 Guice 的 TypeLiteral 很相似,但是有一個(gè)點(diǎn)特別不同:它能夠支持非具體化的類型,例如 T,List
Java 不能在運(yùn)行時(shí)保留對(duì)象的泛型類型信息。如果你在運(yùn)行時(shí)有一個(gè) ArrayList
但是,反射允許你去檢測(cè)方法和類的泛型類型。如果你實(shí)現(xiàn)了一個(gè)返回 List 的方法,并且你用反射獲得了這個(gè)方法的返回類型,你會(huì)獲得代表 List
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 是一個(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>>>
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
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(),那就是:
AbstractInvocationHandler 實(shí)現(xiàn)了以上邏輯。
除此之外,AbstractInvocationHandler 確保傳遞給 handleInvocation(Object, Method, Object[]))的參數(shù)數(shù)組永遠(yuǎn)不會(huì)空,從而減少了空指針異常的機(jī)會(huì)。
嚴(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ù)。
工具方法 Reflection.initialize(Class…)能夠確保特定的類被初始化——執(zhí)行任何靜態(tài)初始化。
使用這種方法的是一個(gè)代碼異味,因?yàn)殪o態(tài)傷害系統(tǒng)的可維護(hù)性和可測(cè)試性。在有些情況下,你別無選擇,而與傳統(tǒng)的框架,操作間,這一方法有助于保持代碼不那么丑。