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

Invokablejava.lang.reflect.Methodjava.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项目,可以直接导入并运行,无需额外设置。


« 上一篇: Reactor Core介绍
» 下一篇: Mockito 中的Java特性