Подтвердить что ты не робот

Как преобразовать строку в выражение лямбда?

Я немного подумал и придумал интересную проблему, предположим, что у нас есть файл конфигурации (ввода):

x -> x + 1
x -> x * 2
x -> x * x
x -> -x

И кроме того, у нас есть список Integer s:

List<Integer> list = new ArrayList<>();
list.addAll(Arrays.toList(1, 2, 3, 4, 5));

Есть ли способ конвертировать String (x -> x + 1 и т.д.) в Object, которые представляют собой лямбда-выражение? Которая затем может быть использована как:

Object lambda = getLambdaFromString("x -> x + 1");
if (lambda.getClass().equals(IntFunction.class) {
    list.stream().forEach()
        .mapToInt(x -> x)
        .map(x -> ((IntFunction)lambda).applyAsInt(x))
        .forEach(System.out::println);
}

Как бы я написал такой метод getLambdaFromString?

  • Есть ли что-то, что я мог бы использовать из JDK/JRE?
  • Нужно ли мне написать все это самостоятельно?
  • Можно ли сузить Object lambda на что-то другое, которое захватывает только lambdas?
4b9b3361

Ответ 1

Марко прокомментировал вопрос правильно. Вы не можете прочитать явное выражение lambda Java из файла, поскольку такое выражение не определено без целевого типа, предоставляемого контекстом. Например, рассмотрите следующие объявления методов:

void method1(BiFunction<String,String,String> f) { ... }
void method2(BiFunction<Integer,Integer,Integer> f) { ... }

Затем в следующем коде

method1((x, y) -> x + y);
method2((x, y) -> x + y);

два лямбда-выражения (x, y) -> x + y означают совершенно разные вещи. Для метода1 оператор + представляет собой конкатенацию строк, но для метода2 это означает добавление целых чисел.

Это блуждает немного далеко от вашего вопроса, но вы можете читать и оценивать выражение лямбда или функции с использованием динамического языка. В Java 8 есть JavaScript-движок Nashorn. Поэтому вместо того, чтобы пытаться прочитать выражение Java lambda, вы можете прочитать и оценить функцию JavaScript с помощью Nashorn, вызванного с Java.

Следующий код принимает функцию в arg [0] и применяет ее к каждому последующему, распечатывая результаты:

import java.util.function.Function;
import javax.script.*;

public class ScriptFunction {
    public static void main(String[] args) throws Exception {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        @SuppressWarnings("unchecked")
        Function<Object,Object> f = (Function<Object,Object>)engine.eval(
            String.format("new java.util.function.Function(%s)", args[0]));
        for (int i = 1; i < args.length; i++) {
            System.out.println(f.apply(args[i]));
        }
    }
}

Например, запуск команды

java ScriptFunction 'function(x) 3 * x + 1' 17 23 47

дает результаты

52.0
70.0
142.0

Обертка строки функции внутри new java.util.function.Function необходима для создания адаптера между понятием Nashorn функции JavaScript и интерфейсом Java Function. (Может быть, лучший способ, но я не знаю об этом.) Приведение возвращаемого значения от eval до Function<Object,Object> приводит к неконтролируемому предупреждению о бросании, которое, как мне кажется, неизбежно, поскольку это границы между JavaScript, динамически типизированным языком и Java, который статически типизирован. Наконец, проверка ошибок не выполняется. Я уверен, что это взорвется множеством неприятных способов, если некоторые допущения будут нарушены, например, первый аргумент, фактически не представляющий функцию JavaScript.

Тем не менее, вы можете найти этот метод полезным, если вам нужно оценить выражения или функции, считанные из файла.

Ответ 2

Я считаю, что использование JavaScript-движка Nashorn, упомянутое в ответе Стюарта, является лучшим выбором в большинстве случаев. Однако, если по какой-то причине он хочет остаться в мире Java, я недавно создал библиотеку LambdaFromString, которая преобразует строковый код в лямбда во время выполнения.

При использовании этой библиотеки код, выполняющий то, что задано в вопросе, выглядит следующим образом:

    List<Integer> list = new ArrayList<>();
    list.addAll(Arrays.asList(1, 2, 3, 4, 5));

    LambdaFactory lambdaFactory = LambdaFactory.get();
    Function<Integer, Integer> lambda = lambdaFactory
            .createLambda("x -> x + 1", new TypeReference<Function<Integer, Integer>>() {});
    list.stream().map(lambda).forEach(System.out::println); //prints 2 to 6

Единственное, что отличается, - это то, что тип лямбда должен быть известен и передан в библиотеку, чтобы компилятор знал, что означает "+" в этом контексте.