文章目錄
反射:用於Java反射功能的Guava工具。
1.TypeToken
由於類型擦除,你不能在運行時傳遞泛型的Class
對象——你可能可以強制轉換它們並假裝它們是泛型的,但實際上不是。
例如:
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
,它使用基於反射的技巧使你即使在運行時也可以操作和查詢泛型類型。將TypeToken
視爲一種以尊重泛型的方式創建、操作和查詢Type
(以及隱式Class
)對象的方法。
Guice用戶注意:TypeToken
與Guice的TypeLiteral
類相似,但有一個重要區別:它支持非具體化類型,例如T
、List<T>
甚至List<? extends Number>
;而TypeLiteral
則不支持。TypeToken
也是可序列化的,並提供了許多附加的工具方法。
1.1背景:類型擦除和反射
Java在運行時不保留對象的泛型類型信息。如果在運行時有ArrayList<String>
對象,則無法確定其具有泛型類型ArrayList<String>
,並且可以使用不安全的原始類型將其強制轉換爲ArrayList<Object>
。
但是,反射使你可以檢測方法和類的泛型類型。如果你實現一個返回List<String>
的方法,並使用反射獲取該方法的返回類型,則將返回代表List<String>
的ParameterizedType
。
TypeToken
類使用這種變通辦法來允許以最小的語法開銷來操作泛型類型。
1.2介紹
獲得基本的、原始類的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<Map<K, V>>()
,則它實際上不能具體化分配給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>
使用這種技術,例如,你可以獲取知道其元素類型的類。
1.3查詢
TypeToken
支持Class
支持的許多查詢,但適當地將泛型約束考慮在內。
支持的查詢操作包括:
方法 | 描述 |
---|---|
getType() |
返回包裝的java.lang.reflect.Type 。 |
getRawType() |
返回最知名的運行時類。 |
getSubtype(Class<?>) |
返回具有指定原始類的this 的某些子類型。例如,如果這是Iterable<String> 並且參數是List.class ,則結果將是List<String> 。 |
getSupertype(Class<?>) |
將指定的原始類泛化爲這個類型的超類型。例如,如果這是Set<String> 並且參數是Iterable.class ,則結果將是Iterable<String> 。 |
isSupertypeOf(type) |
如果這個類型是給定類型的超類型,則返回true。 “超類型”是根據Java泛型引入的類型參數的規則定義的。 |
getTypes() |
返回是此類型或是其子類型的所有類和接口的集合。返回的Set 還提供方法classes() 和interfaces() ,讓你僅查看超類和超接口。 |
isArray() |
檢查此類型是否已知爲數組,例如int[] 或甚至<? extends A[]> 。 |
getComponentType() |
返回數組組件類型。 |
1.3.1resolveType
resolveType
是一個功能強大但複雜的查詢操作,可用於“替換”上下文標記中的類型參數。例如,
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
與“上下文”標記中的那些類型變量的值統一起來。這可以用於一般地推斷類型上的方法的返回類型:
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>>>
2.Invokable
Guava的Invokable
是java.lang.reflect.Method
和java.lang.reflect.Constructor
的流利包裝。它使用這兩種方法簡化了常見的反射代碼。一些用法示例如下:
2.1方法是公共的嗎?
JDK:
Modifier.isPublic(method.getModifiers())
Invokable
:
invokable.isPublic()
2.2方法包是私有的嗎?
JDK:
!(Modifier.isPrivate(method.getModifiers()) || Modifier.isPublic(method.getModifiers()))
Invokable
:
invokable.isPackagePrivate()
2.3方法可以被子類重寫嗎?
JDK:
!(Modifier.isFinal(method.getModifiers())
|| Modifiers.isPrivate(method.getModifiers())
|| Modifiers.isStatic(method.getModifiers())
|| Modifiers.isFinal(method.getDeclaringClass().getModifiers()))
Invokable
:
invokable.isOverridable()
2.4方法的第一個參數是否用@Nullable註解?
JDK:
for (Annotation annotation : method.getParameterAnnotations()[0]) {
if (annotation instanceof Nullable) {
return true;
}
}
return false;
Invokable
:
invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)
2.5構造函數和工廠方法如何共享相同的代碼?
因爲反射代碼需要以相同的方式同時爲構造函數和工廠方法工作,你是否願意重複自己的代碼?
Invokable
提供了一個抽象。以下代碼適用於方法或構造函數:
invokable.isPublic();
invokable.getParameters();
invokable.invoke(object, args);
2.6List<String>
的List.get(int)
的返回類型是什麼?
Invokable
提供了開箱即用的類型解析:
Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
invokable.getReturnType(); // String.class
3.動態代理
3.1newProxy()
工具方法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);
3.2AbstractInvocationHandler
有時,你可能希望動態代理以直觀的方式支持equals()、hashCode()和toString(),即:**如果代理實例具有相同的接口類型並且具有相同的調用處理程序,則該代理實例等於另一個代理實例。**代理的toString()委託給調用處理程序的toString(),以便於自定義。
AbstractInvocationHandler
實現了這個邏輯。
另外,AbstractInvocationHandler
確保傳遞給handleInvocation(Object, Method, Object[])
的參數數組永遠不會爲null,從而減少了NullPointerException
的機會。
4.ClassPath
嚴格來說,Java沒有與平臺無關的方式來瀏覽類或類路徑資源。但是,有時希望能夠遍歷某個包或項目下的所有類,例如,檢查是否遵循了某些項目約定或約束。
ClassPath
是一個提供盡力而爲的類路徑掃描的工具。用法很簡單:
ClassPath classpath = ClassPath.from(classloader); // scans the class path used by classloader
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClasses("com.mycomp.mypackage")) {
...
}
在上面的示例中,ClassInfo
是要加載的類的句柄。它允許程序員檢查類名或包名,並僅在必要時才加載該類。
值得注意的是,ClassPath
是一種盡力而爲的工具。它僅掃描jar文件中或文件系統目錄下的類。它也不能掃描由非URLClassLoader的自定義類加載器管理的類。因此,不要把它用於關鍵任務的生產任務。
5.Class Loading
工具方法Reflection.initialize(Class...)
確保已初始化指定的類——例如,執行任何靜態初始化。
由於靜態會損害系統的可維護性和可測試性,因此使用此方法會產生代碼壞味道。你在與遺留框架進行互操作時別無選擇的情況下,此方法有助於減少代碼的醜陋程度。