反射
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
类,但是有一个重要的不同:它支持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>>()
,它实际上不能具体化分配给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
支持许多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的Invokable
是ava.lang.reflect.Method
和java.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...)
确保指定的类被初始化 – 例如,任何静态初始化已经执行。
此方法的使用是一种代码气味,因为静态状态会损害系统可维护性和可测试性。在你没有其他选择地与遗留框架进行互操作时,此方法帮助你代码不那么难看。