2

Given a class consisting of static predicate methods, I want to access them via reflection and convert them to Predicate<Object> type.

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

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

Normally, I would write the following code to get the predicate:

Predicate<Object> isNull = MyPredicates::isNull;

However, I don't know how to do that using reflection. My intention is to create an annotation for these methods and get them via reflection to create a list of available predicates for my framework.

I thought of three possible approaches, none of which seems good to me.

  1. I could leave it like Method, call invoke() and cast the returned Object to boolean. This, however, would be hard to read and it would mean that I'd have no way of checking the type during runtime.
  2. I could wrap the reflection call to Predicate but this would involve additional overhead.
  3. I could make the user to register every method separately (hard to maintain when adding/removing many methods).

In any case, I fear that using reflection directly will add more overhead and slow down the program.

So, my questions are:

  1. Can I get the Predicate via reflection directly?
  2. If not, what would be an appropriate way of accessing such methods without adding too much overhead, while having a usable API (e.g. by involving Predicate)?
Genhis
  • 1,484
  • 3
  • 27
  • 29
  • I don’t understand why you need reflection. Won’t your annotation processor know what `@NotNull` (or whatever you choose to call it) does? – VGR Dec 04 '18 at 23:53
  • @VGR My point is not to make `@NotNull` annotation, but to get an array of `Predicate` which the user would create. For example, I could have 100 different predicate methods stored in the array for later use (not described here). – Genhis Dec 04 '18 at 23:57

1 Answers1

0

TL;DR: Return Predicate directly from a static method and store it for future use (possibly in a map with method names as keys) to eliminate speed bottleneck of reflective access.

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

First of all, it's important to understand how JVM handles method references. There is a great explanation of that in another question. Take a look at the lambda translation document - in particular, section Method reference capture.

list.filter(String::isEmpty)

is translated as

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

This means that Predicate doesn't exist during runtime unless you write it in your code. There could be a convenient way to get it via reflection API but as far as I know, there isn't. You may be able to write something similar using dynamic proxies; however, I think it would add unnecessary complexity to the code.

Here is a benchmark of 4 different methods to achieve the desired behaviour, including those mentioned in the question:

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 accesses the predicate via :: operator
  2. testReflectionInvoke uses the invoke() method directly
  3. testReflectionWrappedPredicate wraps the invoke() method to Precicate
  4. testReflectionReturnedPredicate uses a method which returns Predicate, so reflection is invoked only once

The predicates were cached for the purpose of this example, since we are not testing the speed of getting a predicate but assuming it'll be done only once.

As you can see from the results, reflection is 6 times slower than accessing predicates normally. Therefore, in my opinion, the cleanest/most maintainable way to do this is to have methods with no arguments which return Predicate type instead of boolean.

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

The benchmark has been done using 3 forks, 4 warm-up iterations and 10 iterations.

Here is the code I used for testing, if you want to run the benchmark yourself (uses JMH framework to do the benchmarking):

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;
    }
}
Genhis
  • 1,484
  • 3
  • 27
  • 29