Как я могу получить Предикат от статических методов через отражение? - PullRequest
0 голосов
/ 05 декабря 2018

Учитывая класс, состоящий из методов статических предикатов, я хочу получить к ним доступ через отражение и преобразовать их в тип 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 голосов
/ 05 декабря 2018

TL; DR: Возврат Predicate непосредственно из статического метода и сохранение его для будущего использования (возможно, на карте с именами методов в качестве ключей) для устранения узкого места в скорости отражательного доступа.

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

Прежде всего, важно понять, как JVM обрабатывает ссылки на методы.Это отличное объяснение в другом вопросе .Взгляните на лямбда-документ перевода - в частности, раздел захват ссылки на метод .

list.filter(String::isEmpty)

переводится как

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

Это означает, что Predicate не существует во время выполнения, если вы не напишите его в своем коде.Там может быть удобный способ получить его с помощью API отражения, но, насколько я знаю, нет.Вы можете написать что-то подобное, используя динамические прокси ;Тем не менее, я думаю, что это добавило бы ненужной сложности к коду.

Вот пример из 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 type вместо boolean.

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

Тест был выполнен с использованием 3 вилок, 4 итераций прогрева и 10 итераций.

Вот код, который я использовал для тестирования, если вы хотите запустить тест самостоятельно (для выполнениябенчмаркинг):

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