2023-08-17
原文作者:LifeIsForSharing 原文地址:https://solang.blog.csdn.net/article/details/105101316

反射:用于Java反射功能的Guava工具。

1.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用户注意:TypeTokenGuiceTypeLiteral类相似,但有一个重要区别:它支持非具体化类型,例如TList<T>甚至List<? extends Number>;而TypeLiteral则不支持。TypeToken也是可序列化的,并提供了许多附加的工具方法。

1.1背景:类型擦除和反射

Java在运行时不保留对象的泛型类型信息。如果在运行时有ArrayList<String>对象,则无法确定其具有泛型类型ArrayList<String>,并且可以使用不安全的原始类型将其强制转换为ArrayList<Object>

但是,反射使你可以检测方法和类的泛型类型。如果你实现一个返回List<String>的方法,并使用反射获取该方法的返回类型,则将返回代表List<String>ParameterizedType

TypeToken类使用这种变通办法来允许以最小的语法开销来操作泛型类型。

1.2介绍

获得基本的、原始类的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>

使用这种技术,例如,你可以获取知道其元素类型的类。

1.3查询

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。“超类型”是根据Java泛型引入的类型参数的规则定义的。
getTypes() 返回是此类型或是其子类型的所有类和接口的集合。返回的Set还提供方法classes()和interfaces(),让你仅查看超类和超接口。
isArray() 检查此类型是否已知为数组,例如int[]或甚至<?extendsA[]>。
getComponentType() 返回数组组件类型。

1.3.1resolveType

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提供的TypeVariables与“上下文”标记中的那些类型变量的值统一起来。这可以用于一般地推断类型上的方法的返回类型:

    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>>>

2.Invokable

Guava的Invokablejava.lang.reflect.Methodjava.lang.reflect.Constructor的流利包装。它使用这两种方法简化了常见的反射代码。一些用法示例如下:

2.1方法是公共的吗?

JDK:

    Modifier.isPublic(method.getModifiers())

Invokable

    invokable.isPublic()

2.2方法包是私有的吗?

JDK:

    !(Modifier.isPrivate(method.getModifiers()) || Modifier.isPublic(method.getModifiers()))

Invokable

    invokable.isPackagePrivate()

2.3方法可以被子类重写吗?

JDK:

    !(Modifier.isFinal(method.getModifiers())
        || Modifiers.isPrivate(method.getModifiers())
        || Modifiers.isStatic(method.getModifiers())
        || Modifiers.isFinal(method.getDeclaringClass().getModifiers()))

Invokable

    invokable.isOverridable()

2.4方法的第一个参数是否用@Nullable注解?

JDK:

    for (Annotation annotation : method.getParameterAnnotations()[0]) {
      if (annotation instanceof Nullable) {
        return true;
      }
    }
    return false;

Invokable

    invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)

2.5构造函数和工厂方法如何共享相同的代码?

因为反射代码需要以相同的方式同时为构造函数和工厂方法工作,你是否愿意重复自己的代码?

Invokable提供了一个抽象。以下代码适用于方法或构造函数:

    invokable.isPublic();
    invokable.getParameters();
    invokable.invoke(object, args);

2.6List<String>List.get(int)的返回类型是什么?

Invokable提供了开箱即用的类型解析:

    Invokable<List<String>, ?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
    invokable.getReturnType(); // String.class

3.动态代理

3.1newProxy()

工具方法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);

3.2AbstractInvocationHandler

有时,你可能希望动态代理以直观的方式支持equals()、hashCode()和toString(),即:**如果代理实例具有相同的接口类型并且具有相同的调用处理程序,则该代理实例等于另一个代理实例。**代理的toString()委托给调用处理程序的toString(),以便于自定义。

AbstractInvocationHandler实现了这个逻辑。

另外,AbstractInvocationHandler确保传递给handleInvocation(Object, Method, Object[])的参数数组永远不会为null,从而减少了NullPointerException的机会。

4.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文件中或文件系统目录下的类。它也不能扫描由非URLClassLoader的自定义类加载器管理的类。因此,不要把它用于关键任务的生产任务。

5.Class Loading

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

由于静态会损害系统的可维护性和可测试性,因此使用此方法会产生代码坏味道。你在与遗留框架进行互操作时别无选择的情况下,此方法有助于减少代码的丑陋程度。

本文参考:
ReflectionExplained
guava-tests-reflect

阅读全文