如何通过反射从静态方法中获取谓词?

时间:2018-12-04 23:18:33

标签: java reflection predicate

给定一个由静态谓词方法组成的类,我想通过反射访问它们并将它们转换为Predicate<Object>类型。

public class MyPredicates {
    public static boolean isNull(Object obj) {
        return obj == null;
    }

    public static boolean hasNegativeHashcode(Object obj) {
        return obj.hashCode() < 0;
    }
}

通常,我将编写以下代码来获取谓词:

Predicate<Object> isNull = MyPredicates::isNull;

但是,我不知道如何使用反射来做到这一点。我的意图是为这些方法创建一个注释,并通过反射将其获取,从而为我的框架创建可用谓词的列表。

我想到了三种可能的方法,对我来说似乎都不对。

  1. 我可以像Method一样保留它,调用invoke()并将返回的Object转换为boolean。但是,这将很难阅读,这意味着我无法在运行时检查类型。
  2. 我可以将反射调用包装到Predicate,但这会涉及额外的开销。
  3. 我可以让用户分别注册每个方法(添加/删除许多方法时很难维护)。

无论如何,我担心直接使用反射会增加开销并减慢程序速度。

所以,我的问题是:

  1. 我可以直接通过反射获得Predicate吗?
  2. 如果没有,那么在拥有可用API的同时(例如通过涉及Predicate)访问这种方法而又不增加过多开销的合适方法是什么?

1 个答案:

答案 0 :(得分:0)

首先,重要的是要了解JVM如何处理方法引用。有一个很棒的explanation of that in another question。看一下lambda translation document-特别是方法参考捕获部分。

list.filter(String::isEmpty)
     

被翻译为

list.filter(indy(MH(metaFactory), MH(invokeVirtual Predicate.apply), MH(invokeVirtual String.isEmpty))()))

这意味着Predicate在运行时不存在,除非您在代码中编写了它。通过反射API可能有一种方便的方法,但是据我所知,还没有。您可以使用dynamic proxies来写类似的东西;但是,我认为这会给代码增加不必要的复杂性。

这里是实现所需行为的4种不同方法的基准,包括问题中提到的那些方法:

Benchmark                                     Mode  Cnt    Score    Error   Units
MyBenchmark.testNormal                       thrpt   30  362.782 ± 13.521  ops/us
MyBenchmark.testReflectionInvoke             thrpt   30   60.440 ±  1.394  ops/us
MyBenchmark.testReflectionWrappedPredicate   thrpt   30   62.948 ±  0.846  ops/us
MyBenchmark.testReflectionReturnedPredicate  thrpt   30  381.780 ±  5.265  ops/us
  1. testNormal 通过::运算符访问该谓词
  2. testReflectionInvoke 直接使用invoke()方法
  3. testReflectionWrappedPredicate invoke()方法包装到Precicate
  4. testReflectionReturnedPredicate 使用返回Predicate的方法,因此反射仅被调用一次

出于本示例的目的,对谓词进行了缓存,因为我们没有测试获取谓词的速度,而是假设只会执行一次。

从结果中可以看到,反射比正常访问谓词慢6倍。因此,我认为最干净/最易维护的方法是使方法的参数不返回Predicate ,而不是返回boolean类型。

public static Predicate<Object> isNull() {
    return obj -> obj == null;
}

使用3个fork,4个热身迭代和10个迭代来完成基准测试。

如果您想自己运行基准测试(使用JMH框架进行基准测试),这是我用于测试的代码:

public class MyBenchmark {
    public static Predicate<Object> normal = MyBenchmark::isNull;
    public static Method reflectionInvoke;
    public static Predicate<Object> reflectionWrappedPredicate;
    public static Predicate<Object> reflectionReturnedPredicate;

    static {
        try {
            Method m1 = MyBenchmark.class.getMethod("isNull", Object.class);
            reflectionInvoke = m1;
            reflectionWrappedPredicate = a -> {
                try {
                    return (boolean)m1.invoke(null, a);
                }
                catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    return false;
                }
            };

            Method m2 = MyBenchmark.class.getMethod("isNull");
            reflectionReturnedPredicate = (Predicate)m2.invoke(null);
        }
        catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
            ex.printStackTrace();
        };
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testNormal() {
        Predicate<Object> p = normal;
        return p.test(this) | p.test(null);
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testReflectionInvoke() {
        try {
            Method m = reflectionInvoke;
            return (boolean)m.invoke(null, this) | (boolean)m.invoke(null, (Object)null);
        }
        catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException ex) {
            ex.printStackTrace();
            return false;
        }
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testReflectionWrappedPredicate() {
        Predicate<Object> p = reflectionWrappedPredicate;
        return p.test(this) | p.test(null);
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testReflectionReturnedPredicate() {
        Predicate<Object> p = reflectionReturnedPredicate;
        return p.test(this) | p.test(null);
    }

    public static boolean isNull(Object obj) {
        return obj == null;
    }

    public static Predicate<Object> isNull() {
        return obj -> obj == null;
    }
}