1. 概述
在这篇文章中,我们将探讨Guava的反射API——它无疑比标准Java反射API功能更为强大。
我们将使用Guava来在运行时捕获泛型类型,并充分利用Invokable
类。
2. 在运行时捕获泛型类型
在Java中,泛型是通过类型擦除实现的。这意味着泛型类型信息只在编译时可用,而在运行时则不再可见。
例如,List<String>
,关于泛型类型的信息会在运行时被擦除。因此,不安全在运行时传递泛型Class
对象,因为可能会将具有不同泛型类型的两个列表赋值给同一个引用:
List<String> stringList = Lists.newArrayList();
List<Integer> intList = Lists.newArrayList();
boolean result = stringList.getClass()
.isAssignableFrom(intList.getClass());
assertTrue(result);
由于类型擦除,isAssignableFrom()
方法无法知道列表的实际泛型类型。它实际上只是比较两个没有实际类型信息的List
。
通过使用标准Java反射API,我们可以检测方法和类的泛型类型。如果我们有一个返回List<String>
的方法,我们可以通过反射获取该方法的返回类型——一个表示List<String>
的ParameterizedType
。
TypeToken
类使用这个技巧来允许对泛型类型进行操作。我们可以使用TypeToken
类来捕获泛型列表的实际类型,并检查它们是否真的可以由同一个引用引用:
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));
只有integerListToken
可以赋值给numberTypeToken
类型的引用,因为Integer
类继承自Number
类。
3. 使用TypeToken
捕获复杂类型
假设我们要创建一个泛型参数化类,并希望在运行时获取泛型类型的信息。我们可以创建一个类,其中包含一个TypeToken
字段来捕获这些信息:
abstract class ParametrizedClass<T> {
TypeToken<T> type = new TypeToken<T>(getClass()) {};
}
然后,在创建此类的实例时,运行时会获得泛型类型:
ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
assertEquals(parametrizedClass.type, TypeToken.of(String.class));
我们还可以创建一个具有多个泛型类型的复杂类型TypeToken
,并在运行时获取这些类型的每个信息:
TypeToken<Function<Integer, String>> funToken
= new TypeToken<Function<Integer, String>>() {};
TypeToken<?> funResultToken = funToken
.resolveType(Function.class.getTypeParameters()[1]);
assertEquals(funResultToken, TypeToken.of(String.class));
我们得到了Function
的实际返回类型,即String
。我们甚至可以获取映射中的条目的类型:
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>>>() {});
这里我们使用Java标准库的反射方法getMethod()
来获取方法的返回类型。
4. Invokable
Invokable
是java.lang.reflect.Method
和java.lang.reflect.Constructor
的流畅封装器。它在标准Java反射API之上提供了一个更简单的API。假设我们有一个类,它有两个公共方法,其中一个为final:
class CustomClass {
public void somePublicMethod() {}
public final void notOverridablePublicMethod() {}
}
现在让我们使用Guava API和Java标准反射API来检查somePublicMethod()
:
Method method = CustomClass.class.getMethod("somePublicMethod");
Invokable<CustomClass, ?> invokable
= new TypeToken<CustomClass>() {}
.method(method);
boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
boolean isPublicGuava = invokable.isPublic();
assertTrue(isPublicStandradJava);
assertTrue(isPublicGuava);
这两种方式之间的差异不大,但在Java中检查方法是否可重写是一项相当复杂的任务。幸运的是,Invokable
类的isOverridable()
方法使得这变得更容易:
Method method = CustomClass.class.getMethod("notOverridablePublicMethod");
Invokable<CustomClass, ?> invokable
= new TypeToken<CustomClass>() {}.method(method);
boolean isOverridableStandardJava = (!(Modifier.isFinal(method.getModifiers())
|| Modifier.isPrivate(method.getModifiers())
|| Modifier.isStatic(method.getModifiers())
|| Modifier.isFinal(method.getDeclaringClass().getModifiers())));
boolean isOverridableFinalGauava = invokable.isOverridable();
assertFalse(isOverridableStandardJava);
assertFalse(isOverridableFinalGauava);
我们看到,即使是这样简单的操作,使用标准反射API也需要进行很多检查。Invokable
类在其背后隐藏了简单易用且简洁的API。
5. 总结
在这篇文章中,我们研究了Guava的反射API,并将其与标准Java进行了比较。我们了解了如何在运行时捕获泛型类型,以及Invokable
类如何为使用反射的代码提供优雅易用的API。
所有这些示例和代码片段的实现可以在GitHub项目中找到——这是一个Maven项目,可以直接导入并运行,无需额外设置。