• Guava的反射工具


    Guava的反射工具

    一、TypeToken

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

    例如:

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

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

    1.1 背景:类型擦除和反射

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

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

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

    1.2 介绍

    获取基本的TypeToken

            TypeToken<String> stringToken = TypeToken.of(String.class);
            TypeToken<Integer> intToken = TypeToken.of(Integer.class);
    
    • 1
    • 2

    获取具有泛型类型的TypeToken,当你在编译时知道泛型类型的参数,使用一个空的匿名内部类:

    // 使用空的匿名内部类创建
    TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
    
    • 1
    • 2

    或者指向通配符类型:

    // 使用匿名内部类,指向通配符类型
    TypeToken<Map<?,?>> mapToken = new TypeToken<Map<?, ?>>() {};
    
    • 1
    • 2

    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));
    
    • 1
    • 2
    • 3

    提取一个公共方法:

        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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用上面的方法:

            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>>() {});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    请注意,如果 mapToken 刚刚返回 new TypeToken>(),它实际上并不能具体化分配给 K 和 V 的类型,例如:

    class Util {
        static <K,V> TypeToken<Map<K, V>> incorrectMapToken() {
            return new TypeToken<Map<K, V>>() {};
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
            // 打印: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)));
    
    • 1
    • 2
    • 3
    • 4

    或者,您可以使用(通常是匿名的)子类捕获泛型类型,并针对知道类型参数是什么的上下文类解析它:

    abstract class IKnowMyType<T> {
        TypeToken<T> type = new TypeToken<T>(getClass()){};
    }
    
    • 1
    • 2
    • 3
            TypeToken<String> type = new IKnowMyType<String>(){}.type;
            // java.lang.String
            System.out.println(type);
    
    • 1
    • 2
    • 3

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

    1.3 查询

    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[] 甚至
    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);
    
    • 1
    • 2
    • 3
    • 4

    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);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二、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);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.2 方法是否能被子类覆盖

    // Invokable 的判断方法
    invokable.isOverridable();
    
    // JDK 的判断方法
    boolean f = !(Modifier.isFinal(method.getModifiers())
                        || Modifier.isPrivate(method.getModifiers())
                        || Modifier.isStatic(method.getModifiers())
                        || Modifier.isFinal(method.getModifiers()));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

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

    invokable.isPublic();
    invokable.getParameters();
    invokable.invoke(object, args);
    
    • 1
    • 2
    • 3

    2.5 对于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);
    
    • 1
    • 2
    • 3
    • 4

    三、动态代理

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    // JDK 创建动态代理:
            Foo foo1 = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), 
                    new Class[]{Foo.class}, 
                    new FooInvocationHandler());
    // Reflection 创建动态代理
    Foo foo = Reflection.newProxy(Foo.class, new FooInvocationHandler());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此外,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);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在上面的示例中,ClassInfo 是要加载的类的句柄。 它允许程序员检查类名或包名,并且只在需要时才加载类。

    值得注意的是,ClassPath 是一个尽力而为的实用程序。 它只扫描 jar 文件或文件系统目录下的类。 它也不能扫描由不是 URLClassLoader 的自定义类加载器管理的类。 所以不要将它用于关键任务生产任务。

    五、Class Loading

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

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

  • 相关阅读:
    大数据智能交通未来会是怎样的交通状况?
    spring框架概述及快速入门
    深度学习深陷困境
    请求一下子太多了,数据库危
    【JavaEE】 spring boot的配置文件详解
    vue3 + ts项目(无vite)报错记录
    java Map集合基本概念
    大数据学习之一文学会Spark【Spark知识点总结】
    基于SSM的食用菌菌棒溯源系统
    PHP将数据集转换成树状结构
  • 原文地址:https://blog.csdn.net/hefrankeleyn/article/details/126211858