Определите, является ли выражение лямбда неактивным или состоящим в Java

Есть ли функция, которая принимает ссылку на выражение лямбда и возвращает логическое выражение, является ли выражение лямбда апатридом или нет? Как можно определить состояние лямбда-выражения?

Ответы

Ответ 1

Ну, выражение лямбда - это всего лишь экземпляр специального анонимного класса, который имеет только один метод. Анонимные классы могут " захватывать " переменные, находящиеся в окружении. Если ваше определение класса с состоянием - это тот, который содержит изменчивые вещи в своих полях (в противном случае это в значительной степени просто константа), тогда вам повезло, потому что то, как захват, похоже, реализован. Вот небольшой эксперимент:

import java.lang.reflect.Field;
import java.util.function.Function;

public class Test {
    public static void main(String[] args) {
        final StringBuilder captured = new StringBuilder("foo");
        final String inlined = "bar";
        Function<String, String> lambda = x -> {
            captured.append(x);
            captured.append(inlined);

            return captured.toString();
        };

        for (Field field : lambda.getClass().getDeclaredFields())
            System.out.println(field);
    }
}

Результат выглядит примерно так:

private final java.lang.StringBuilder Test$$Lambda$1/424058530.arg$1

Ссылка StringBuilder превратилась в поле анонимного класса лямбдаfinal String inlined константа final String inlined была встроена для повышения эффективности, но это не относится к точке). Поэтому эта функция должна выполняться в большинстве случаев:

public static boolean hasState(Function<?,?> lambda) {
    return lambda.getClass().getDeclaredFields().length > 0;
}

EDIT: как указано в @Federico, это поведение, специфичное для реализации, и может не работать в некоторых экзотических средах или будущих версиях JVM Oracle/OpenJDK.

Ответ 2

Нет, это вообще не возможно. Предлагаемый подход к проверке того, принадлежит ли лямбда классу с полем, является следующим лучшим, но наличие поля не совпадает с состоянием.

class Stateless {
    int result = 0;
    public int getResult() { return result; }
}

Можно доказать состояние, обнаружив две входные последовательности, для которых данная входная комбинация возвращает другой результат. Однако невозможно доказать, что такая входная последовательность не существует (любая входная последовательность может приводить к другому результату, если она была добавлена другим вызовом).

(Даже если вы проверите значения полей, найденных с помощью отражения, они могут измениться, не влияя на результат лямбда, поэтому на самом деле не делают это с точки зрения состояния).

Вот короткий компилятивный пример, показывающий как ложные положительные, так и отрицательные, опровергая понятие:

public class StatefulLambda {
    static AtomicInteger counter = new AtomicInteger();

    public static void main(String[] args) {
        // false negative: will return different result each call
        System.out.println(hasState(i -> counter.incrementAndGet()));

        // false positive: will always return the same result
        Object object = new Object() {
            final int i = 0;
        };
        System.out.println(hasState(i -> object.toString()));
    }

    private static boolean hasState(Function<?,?> lambda) {
        return lambda.getClass().getDeclaredFields().length > 0;
    }
}

Ответ 3

Вот простая и глупая идея. Просто проверьте, есть ли у вас лямбда поля.

Например, рассмотрите следующие состояния лямбда.

  List<Integer> serialStorage = new ArrayList<>();
  Function<? super Integer, ? extends Integer> statefulLambda =
      e -> { serialStorage.add(e); return e; };

Это statefulLambda имеет частное конечное внутреннее поле arg$1 которое, очевидно, ссылается на serialStorage. Так

  statefulLambda.getClass().getDeclaredFields().length > 0

может использоваться в качестве индикатора, что лямбда является состоятельной.

Однако я понятия не имею, будет ли это вообще работать.

Ответ 4

Я бы сказал, что невозможно написать функцию, которая может определить, является ли лямбда апатридом или нет:

Если вы ищете пример метода filter Stream API, javadoc заявляет, что этот параметр должен быть "предикатом [...] без сохранения состояния", а также ссылки на определение API без состояния.

Если бы был способ определить, был ли параметр filter (или любого другого) неактивным или нет, класс Stream включил бы возможность выбросить исключение IllegalArgumentException в том случае, если этот параметр был включен в lambda. Поскольку это не было реализовано, и только предупреждение было добавлено в javadocs, можно сделать вывод, что нет способа написать функцию, которая может определить, является ли лямбда-лямбда безстоящей.


Редактировать (после прочтения комментариев от Эрика): Существует множество ситуаций, когда команда внедрения делает выбор реализации не реализованным для конкретной функции; мы обычно не можем заключить из тех вариантов, которые эта функция невозможна для кого-то другого. В этом специальном случае я считаю его неправдоподобным, что дизайнеры Java 8 не нашли бы и не сделали бы это, если бы было (недорогое) решение.