Guava官方文档中文版(十五)-反射

文章介绍了Java中的类型擦除问题以及Guava库提供的TypeToken类如何在运行时处理和查询泛型类型。TypeToken允许在反射中保留泛型信息,支持对泛型列表、通配符类型的操作,并提供了动态解析和查询类型的能力。此外,还提到了Guava的Invokable接口简化反射代码,以及动态代理和ClassPath类的使用。
摘要由CSDN通过智能技术生成

反射

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类似于GuiceTypeLiteral类,但是有一个重要的不同:它支持non-reified类型,例如T,List<T>,甚至List<? extend Number>;而TypeLiteral没有。TypeToken也可以序列化并且提供多种多样额外的工具方法。

背景:类型擦除和反射

在运行时,Java不会保留对象的泛型类型信息。如果在运行时,你有ArrayList<String>对象,你不能确定他有泛型类型ArrayList<String>– 并且对于不安全的原生类型,你可以将它转换为ArrayList<Object>

但是,反射允许你检测方法和类的泛型类型。如果你实现一个返回List<String>的方法,并且你使用反射来获取该方法的返回类型,你可以得到ParameterizedType表示List<String>

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<Map<K, V>>(),它实际上不能具体化分配给KV的类型,
例如:

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支持许多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。“Supertype”根据Java泛型引入的类型参数的规则定义。
getTypes()返回此类型所属的或者其子类型的所有类和结果的集合。返回的Set也提供方法classes()interfaces()来让你展示父类和父接口。
isArray()检查此类型是否是已知数组,例如int[]或者甚至是<? extends A[]>
getComponentType()返回数组的组件类型
resolveType(解析类型)

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提供的TypeVariable与来自context令牌的那些类型变量的值统一起来。这通常用来推断类型上方法的返回类型:

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的Invokableava.lang.reflect.Methodjava.lang.reflect.Constructor的流式封装器。它简化了普通的反射代码。以下是一些用法示例:

方法是公共的吗?

JDK:

Modifier.isPublic(method.getModifiers())

Invokable

invokable.isPublic()
方法是包私有的吗?

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

动态代理

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方法,即:如果他们实现相同的接口类型并有相同的调用处理器,A代理实例等于另外一个代理实例。A代理的toString()委托给调用处理器的toString(),以便于更容易自定义。

AbstractInvocationHandler实现了该逻辑。

除此之外,AbstractInvocationHandler确保传入到handleInvocation(Object, Method, Object[])参数数组从不为null,这样NullPointerException的机会较少。

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文件或者文件系统目录中中它只扫描类,两者都不能扫描通过自定义类加载器(不是URLClasLoader)管理的类。所以不要将其用于关键任务的生产任务。

类加载

工具方法Reflection.initialize(Class...)确保指定的类被初始化 – 例如,任何静态初始化已经执行。

此方法的使用是一种代码气味,因为静态状态会损害系统可维护性和可测试性。在你没有其他选择地与遗留框架进行互操作时,此方法帮助你代码不那么难看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值