由于类型擦除,你不能在运行时传递泛形Class对象。你可以投射它们并假装它们是通用的,但实际上并非如此。
例如:
ArrayList<String> stringList = Lists.newArrayList();
ArrayList<Integer> intList = Lists.newArrayList();
// 返回true,即使 ArrayList 不能从 ArrayList 分配
System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));
Guava提供了TypeToken,它使用基于反射的技巧允许你操作和查询泛形类型,即便是在运行期。将TypeToken当作一种尊重泛形的方式来创建、操纵、查询Type对象(或隐式的Class)。
Java在运行期不能保存对象的泛型信息。如果在运行期你有一个ArrayList对象,你无法确定它有泛型类型ArrayList,你能够使用不安全的原始类型,将其转换为ArrayList。
可是,反射允许你检测方法和类的泛型类型。如果你实现一个返回List 的方法,你能够使用泛型去获取方法的方法都返回类型,你能得到一个ParameterizedType,其代表List。
TypeToken 类使用此解决方法来允许以最小的语法开销操作泛型类型。
获取基本的TypeToken:
TypeToken<String> stringToken = TypeToken.of(String.class);
TypeToken<Integer> intToken = TypeToken.of(Integer.class);
获取具有泛型类型的TypeToken,当你在编译时知道泛型类型的参数,使用一个空的匿名内部类:
// 使用空的匿名内部类创建
TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
或者指向通配符类型:
// 使用匿名内部类,指向通配符类型
TypeToken<Map<?,?>> mapToken = new TypeToken<Map<?, ?>>() {};
TypeToken提供了一个方法动态获取泛型类型的参数:
TypeToken<Map<String, Integer>> mapToken02 = new TypeToken<Map<String, Integer>>() {}
.where(new TypeParameter<String>() {}, TypeToken.of(String.class))
.where(new TypeParameter<Integer>() {}, TypeToken.of(Integer.class));
提取一个公共方法:
private static <K,V> TypeToken<Map<K, V>> mapToToken(TypeToken<K> kTypeToken, TypeToken<V> vTypeToken) {
return new TypeToken<Map<K, V>>() {}
.where(new TypeParameter<K>() {}, kTypeToken)
.where(new TypeParameter<V>() {}, vTypeToken);
}
使用上面的方法:
TypeToken<Map<String, Integer>> mapTypeToken03 =
mapToToken(TypeToken.of(String.class), TypeToken.of(Integer.class));
TypeToken<Map<Integer, BigInteger>> mapTypeToken04 =
mapToToken(TypeToken.of(Integer.class), TypeToken.of(BigInteger.class));
TypeToken<Map<Integer, Queue<String>>> mapTypeToken05 =
mapToToken(TypeToken.of(Integer.class), new TypeToken<Queue<String>>() {});
请注意,如果 mapToken 刚刚返回 new TypeToken
class Util {
static <K,V> TypeToken<Map<K, V>> incorrectMapToken() {
return new TypeToken<Map<K, V>>() {};
}
}
// 打印:java.util.Map
System.out.println(Util.<String, BigInteger>incorrectMapToken());
// 打印: java.util.Map
System.out.println(mapToToken(TypeToken.of(String.class), TypeToken.of(BigInteger.class)));
或者,您可以使用(通常是匿名的)子类捕获泛型类型,并针对知道类型参数是什么的上下文类解析它:
abstract class IKnowMyType<T> {
TypeToken<T> type = new TypeToken<T>(getClass()){};
}
TypeToken<String> type = new IKnowMyType<String>(){}.type;
// java.lang.String
System.out.println(type);
使用这种技术,您可以获得知道其元素类型的类。
TypeToken 支持 Class 支持的许多查询,但适当考虑了通用约束。
支持的查询操作包括:
| 方法 | 描述 |
|---|---|
getType() | 返回java.lang.reflect.Type的包装类型 |
getRawType() | 返回运行时类型 |
getSubtype(Class) | 返回具有指定原始类的 this 的某个子类型。 例如,如果这是 Iterable 并且参数是 List.class,则结果将为 List。 |
getSupertype(Class) | 将指定的原始类生成为该类型的超类型。 例如,如果这是 Set 并且参数是 Iterable.class,则结果将是 Iterable。 |
isSupertypeOf(type) | 如果此类型是给定类型的超类型,则返回 true。 “超类型”是根据 Java 泛型引入的类型参数规则定义的。 |
getTypes() | 返回此类型是或其子类型的所有类和接口的集合。 返回的 Set 还提供方法 classes() 和 interfaces() 让您只查看超类和超接口。 |
isArray() | 检查此类型是否已知为数组,例如 int[] 甚至 extends A[]>。 |
getComponentType() | 返回数组组件类型 |
resolveType 是一个强大但复杂的查询操作,可用于从上下文标记中“替换”类型参数。 例如:
TypeToken<Function<String, Integer>> functionTypeToken = new TypeToken<Function<String, Integer>>() {};
TypeToken<?> typeToken = functionTypeToken.resolveType(Function.class.getTypeParameters()[0]);
// java.lang.String
System.out.println(typeToken);
TypeToken 将 Java 提供的 TypeVariables 与来自“上下文”标记的那些类型变量的值统一起来。 这可用于一般地推断类型上方法的返回类型:
try {
TypeToken<Map<String, Integer>> mapTypeToken = new TypeToken<Map<String, Integer>>() {};
TypeToken<?> entrySet =
mapTypeToken.resolveType(Map.class.getMethod("entrySet").getGenericReturnType());
// java.util.Set>
System.out.println(entrySet);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
Guava的Invokable是java.lang.reflect.Method和java.lang.reflect.Constructor的包装器。使用下面任何代码可以简化常用的反射代码:
try {
// Invokable 的判断方法
Invokable<?, Object> invokable = Invokable.from(List.class.getMethod("size"));
boolean aPublic = invokable.isPublic();
System.out.println(aPublic);
// JDK 的判断方法
boolean aPublic1 = Modifier.isPublic(List.class.getMethod("size").getModifiers());
System.out.println(aPublic1);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
// Invokable 的判断方法
invokable.isOverridable();
// JDK 的判断方法
boolean f = !(Modifier.isFinal(method.getModifiers())
|| Modifier.isPrivate(method.getModifiers())
|| Modifier.isStatic(method.getModifiers())
|| Modifier.isFinal(method.getModifiers()));
@Nullable注解// JDK 的判断方法
boolean hasNullable = false;
for (Annotation annotation : method.getParameterAnnotations()[0]) {
if (annotation instanceof Nullable) {
hasNullable = true;
break;
}
}
System.out.println(hasNullable);
// Invokable 的判断方法
boolean annotationPresent = invokable.getParameters().get(0).isAnnotationPresent(Nullable.class);
您是否想重复自己,因为您的反射代码需要以相同的方式为构造函数和工厂方法工作。
Invokable 提供了一种抽象。 以下代码适用于方法或构造函数:
invokable.isPublic();
invokable.getParameters();
invokable.invoke(object, args);
List,它的List.get(int)返回什么类型?Invokable 提供开箱即用的类型解析:
TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
Invokable<List<String>, Object> invokable1 = stringListToken.method(List.class.getMethod("size"));
TypeToken<?> returnType = invokable1.getReturnType(); // int
System.out.println(returnType);
实用方法 Reflection.newProxy(Class, InvocationHandler) 是一种更安全、更方便的 API,用于在仅代理单个接口类型时创建 Java 动态代理。
public class FooInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
// JDK 创建动态代理:
Foo foo1 = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler());
// Reflection 创建动态代理
Foo foo = Reflection.newProxy(Foo.class, new FooInvocationHandler());
有时您可能希望您的动态代理以直观的方式支持 equals()、hashCode() 和 toString(),即: * 如果它们用于相同的接口类型并且具有相同的调用,则代理实例等于另一个代理实例 处理程序。 * 代理的 toString() 委托给调用处理程序的 toString() 以便于定制。
AbstractInvocationHandler 实现了这样的魔法:
public class FooInvocationHandler02 extends AbstractInvocationHandler {
@CheckForNull
@Override
protected Object handleInvocation(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
return null;
}
}
此外,AbstractInvocationHandler 确保传递给 handleInvocation(Object, Method, Object[]) 的参数数组永远不会为空,因此发生 NullPointerException 的可能性较小。
严格的说,Java 没有独立于平台的方式来浏览类或类路径资源。然而,有时希望能够遍历某个包或项目下的所有类,例如,检查是否遵循了某个项目约定或约束。
ClassPath 是一个提供尽力而为的类路径扫描的实用程序。 用法很简单:
try {
ClassPath classPath = ClassPath.from(ReflectionDemo.class.getClassLoader());
ImmutableSet<ClassPath.ClassInfo> topLevelClasses = classPath.getTopLevelClasses("com.hef.guava.reflection");
for (ClassPath.ClassInfo topLevelClass : topLevelClasses) {
System.out.println(topLevelClass);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
在上面的示例中,ClassInfo 是要加载的类的句柄。 它允许程序员检查类名或包名,并且只在需要时才加载类。
值得注意的是,ClassPath 是一个尽力而为的实用程序。 它只扫描 jar 文件或文件系统目录下的类。 它也不能扫描由不是 URLClassLoader 的自定义类加载器管理的类。 所以不要将它用于关键任务生产任务。
实用方法 Reflection.initialize(Class…) 确保初始化指定的类——例如,执行任何静态初始化。
使用这种方法是一种代码味道,因为静态会损害系统的可维护性和可测试性。 如果您在与遗留框架互操作时别无选择,此方法有助于保持代码不那么难看。