Guava的反射工具

Guava的反射工具

一、TypeToken

由于类型擦除,你不能在运行时传递泛形Class对象。你可以投射它们并假装它们是通用的,但实际上并非如此。

例如:

        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        // 返回true,即使 ArrayList<String> 不能从 ArrayList<Integer> 分配
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));

Guava提供了TypeToken,它使用基于反射的技巧允许你操作和查询泛形类型,即便是在运行期。将TypeToken当作一种尊重泛形的方式来创建、操纵、查询Type对象(或隐式的Class)。

1.1 背景:类型擦除和反射

Java在运行期不能保存对象的泛型信息。如果在运行期你有一个ArrayList<String>对象,你无法确定它有泛型类型ArrayList<String>,你能够使用不安全的原始类型,将其转换为ArrayList<Object>

可是,反射允许你检测方法和类的泛型类型。如果你实现一个返回List<String> 的方法,你能够使用泛型去获取方法的方法都返回类型,你能得到一个ParameterizedType,其代表List<String>

TypeToken 类使用此解决方法来允许以最小的语法开销操作泛型类型。

1.2 介绍

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

class Util {
    static <K,V> TypeToken<Map<K, V>> incorrectMapToken() {
        return new TypeToken<Map<K, V>>() {};
    }
}
        // 打印:java.util.Map<K, V>
        System.out.println(Util.<String, BigInteger>incorrectMapToken());
        // 打印: java.util.Map<java.lang.String, java.math.BigInteger>
        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);

使用这种技术,您可以获得知道其元素类型的类。

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[] 甚至 <? 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<java.util.Map$Entry<java.lang.String, java.lang.Integer>>
            System.out.println(entrySet);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

二、Invokable

Guava的Invokable是java.lang.reflect.Methodjava.lang.reflect.Constructor的包装器。使用下面任何代码可以简化常用的反射代码:

2.1 判断方法是否是公共的

        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);
        }

2.2 方法是否能被子类覆盖

// Invokable 的判断方法
invokable.isOverridable();

// JDK 的判断方法
boolean f = !(Modifier.isFinal(method.getModifiers())
                    || Modifier.isPrivate(method.getModifiers())
                    || Modifier.isStatic(method.getModifiers())
                    || Modifier.isFinal(method.getModifiers()));

2.3 判断方法的第一个参数是否标注了@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);

2.4 如何在构造函数和工厂方法中共享相同的代码

您是否想重复自己,因为您的反射代码需要以相同的方式为构造函数和工厂方法工作。

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

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

2.5 对于List<String>,它的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);

三、动态代理

3.1 newProxy()

实用方法 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());

3.2 AbstractInvocationHandler

有时您可能希望您的动态代理以直观的方式支持 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 的可能性较小。

四、ClassPath

严格的说,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 的自定义类加载器管理的类。 所以不要将它用于关键任务生产任务。

五、Class Loading

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

使用这种方法是一种代码味道,因为静态会损害系统的可维护性和可测试性。 如果您在与遗留框架互操作时别无选择,此方法有助于保持代码不那么难看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值