# 13-反射
**譯者:[萬天慧](http://weibo.com/beneo)(武祖)**
由于類型擦除,你不能夠在運行時傳遞泛型類對象——你可能想強制轉換它們,并假裝這些對象是有泛型的,但實際上它們沒有。
舉個例子:
```
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](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/TypeToken.html), 它使用了基于反射的技巧甚至讓你在運行時都能夠巧妙的操作和查詢泛型類型。想象一下TypeToken是創建,操作,查詢泛型類型(以及,隱含的類)對象的方法。
Guice用戶特別注意:TypeToken與類[Guice](http://code.google.com/p/google-guice/)的[TypeLiteral](http://google-guice.googlecode.com/git/javadoc/com/google/inject/TypeLiteral.html)很相似,但是有一個點特別不同:它能夠支持非具體化的類型,例如T,List<T>,甚至是List<? extends Number>;TypeLiteral則不能支持。TypeToken也能支持序列化并且提供了很多額外的工具方法。
## 背景:類型擦除與反射
Java不能在運行時保留對象的泛型類型信息。如果你在運行時有一個ArrayList<String>對象,你不能夠判定這個對象是有泛型類型ArrayList<String>的 —— 并且通過不安全的原始類型,你可以將這個對象強制轉換成ArrayList<Object>。
但是,反射允許你去檢測方法和類的泛型類型。如果你實現了一個返回List的方法,并且你用反射獲得了這個方法的返回類型,你會獲得代表List<String>的[ParameterizedType](http://docs.oracle.com/javase/6/docs/api/java/lang/reflect/ParameterizedType.html)。
TypeToken類使用這種變通的方法以最小的語法開銷去支持泛型類型的操作。
## 介紹
獲取一個基本的、原始類的TypeToken非常簡單:
```
TypeToken<String> stringTok = TypeToken.of(String.class);
TypeToken<Integer> intTok = TypeToken.of(Integer.class);
```
為獲得一個含有泛型的類型的TypeToken —— 當你知道在編譯時的泛型參數類型 —— 你使用一個空的匿名內部類:
```
TypeToken<List<String>> stringListTok = new TypeToken<List<String>>() {};
```
或者你想故意指向一個通配符類型:
```
TypeToken<Map<?, ?>> wildMapTok = new TypeToken<Map<?, ?>>() {};
```
TypeToken提供了一種方法來動態的解決泛型類型參數,如下所示:
```
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>(),它實際上不能把具體化的類型分配到K和V上面,舉個例子
```
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>"
```
或者,你可以通過一個子類(通常是匿名)來捕獲一個泛型類型并且這個子類也可以用來替換知道參數類型的上下文類。
```
abstract class IKnowMyType<T> {
TypeToken<T> type = new TypeToken<T>(getClass()) {};
}
...
new IKnowMyType<String>() {}.type; // returns a correct TypeToken<String>
```
使用這種技術,你可以,例如,獲得知道他們的元素類型的類。
## 查詢
TypeToken支持很多種類能支持的查詢,但是也會把通用的查詢約束考慮在內。
支持的查詢操作包括:
| 方法 | 描述 |
|:--- |:--- |
| getType() | 獲得包裝的java.lang.reflect.Type. |
| getRawType() | 返回大家熟知的運行時類 |
| getSubtype(Class<?>) | 返回那些有特定原始類的子類型。舉個例子,如果這有一個Iterable并且參數是List.class,那么返回將是List。 |
| getSupertype(Class<?>) | 產生這個類型的超類,這個超類是指定的原始類型。舉個例子,如果這是一個Set并且參數是Iterable.class,結果將會是Iterable。 |
| isAssignableFrom(type) | 如果這個類型是 assignable from 指定的類型,并且考慮泛型參數,返回true。List<? extends Number>是assignable from List,但List沒有. |
| getTypes() | 返回一個Set,包含了這個所有接口,子類和類是這個類型的類。返回的Set同樣提供了classes()和interfaces()方法允許你只瀏覽超類和接口類。 |
| isArray() | 檢查某個類型是不是數組,甚至是<? extends A[]>。 |
| getComponentType() | 返回組件類型數組。 |
## resolveType
resolveType是一個可以用來“替代”context token(譯者:不知道怎么翻譯,只好去stackoverflow去問了)中的類型參數的一個強大而復雜的查詢操作。例如,
```
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中的類型變量統一起來。這可以被用來一般性地推斷出在一個類型相關方法的返回類型:
```
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是對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()
```
#### 方法的第一個參數是否被定義了注解@Nullable?
JDK:
```
for (Annotation annotation : method.getParameterAnnotations[0]) {
if (annotation instanceof Nullable) {
return true;
}
}
return false;
```
Invokable:
```
invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)
```
#### 構造函數和工廠方法如何共享同樣的代碼?
你是否很想重復自己,因為你的反射代碼需要以相同的方式工作在構造函數和工廠方法中?
Invokable提供了一個抽象的概念。下面的代碼適合任何一種方法或構造函數:
```
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()
實用方法Reflection.newProxy(Class, InvocationHandler)是一種更安全,更方便的API,它只有一個單一的接口類型需要被代理來創建Java動態代理時
JDK:
```
Foo foo = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class<?>[] {Foo.class},
invocationHandler);
```
Guava:
```
Foo foo = Reflection.newProxy(Foo.class, invocationHandler);
```
#### AbstractInvocationHandler
有時候你可能想動態代理能夠更直觀的支持equals(),hashCode()和toString(),那就是:
1. 一個代理實例equal另外一個代理實例,只要他們有同樣的接口類型和equal的invocation handlers。
2. 一個代理實例的toString()會被代理到invocation handler的toString(),這樣更容易自定義。
[AbstractInvocationHandler](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/AbstractInvocationHandler.html)實現了以上邏輯。
除此之外,AbstractInvocationHandler確保傳遞給[handleInvocation(Object, Method, Object[])](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/re…ion%28java.lang.Object,%20java.lang.reflect.Method,%20java.lang.Object%5B%5D%29)的參數數組永遠不會空,從而減少了空指針異常的機會。
## ClassPath
嚴格來講,Java沒有平臺無關的方式來瀏覽類和類資源。不過一定的包或者工程下,還是能夠實現的,比方說,去檢查某個特定的工程的慣例或者某種一直遵從的約束。
[ClassPath](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/ClassPath.html)是一種實用工具,它提供盡最大努力的類路徑掃描。用法很簡單:
```
ClassPath classpath = ClassPath.from(classloader); // scans the class path used by classloader
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClasses("com.mycomp.mypackage")) {
...
}
```
在上面的例子中,[ClassInfo](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/reflect/ClassPath.ClassInfo.html)是被加載類的句柄。它允許程序員去檢查類的名字和包的名字,讓類直到需要的時候才被加載。
值得注意的是,ClassPath是一個盡力而為的工具。它只掃描jar文件中或者某個文件目錄下的class文件。也不能掃描非URLClassLoader的自定義class loader管理的class,所以不要將它用于關鍵任務生產任務。
## Class Loading
工具方法Reflection.initialize(Class…)能夠確保特定的類被初始化——執行任何靜態初始化。
使用這種方法的是一個代碼異味,因為靜態傷害系統的可維護性和可測試性。在有些情況下,你別無選擇,而與傳統的框架,操作間,這一方法有助于保持代碼不那么丑。
- Google Guava官方教程(中文版)
- 1-基本工具
- 1.1-使用和避免null
- 1.2-前置條件
- 1.3-常見Object方法
- 1.4-排序: Guava強大的”流暢風格比較器”
- 1.5-Throwables:簡化異常和錯誤的傳播與檢查
- 2-集合
- 2.1-不可變集合
- 2.2-新集合類型
- 2.3-強大的集合工具類:java.util.Collections中未包含的集合工具
- 2.4-集合擴展工具類
- 3-緩存
- 4-函數式編程
- 5-并發
- 5.1-google Guava包的ListenableFuture解析
- 5.2-Google-Guava Concurrent包里的Service框架淺析
- 6-字符串處理:分割,連接,填充
- 7-原生類型
- 9-I/O
- 10-散列
- 11-事件總線
- 12-數學運算
- 13-反射