How to convert a string to a lambda expression?

后端 未结 2 1849
隐瞒了意图╮
隐瞒了意图╮ 2020-12-08 03:06

I was thinking a bit and came up with an interesting problem, suppose we have a configuration (input) file with:

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


        
相关标签:
2条回答
  • 2020-12-08 03:10

    Marko's comment on the question is correct. You can't read a bare Java lambda expression out of a file, since such an expression isn't defined without a target type provided by the context. For example, consider the following method declarations:

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

    Then in the following code,

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

    the two lambda expressions (x, y) -> x + y mean completely different things. For method1, the + operator is string concatenation, but for method2, it means integer addition.

    This is wandering a bit far afield from your question, but you can read and evaluate a lambda or function expression using a dynamic language. In Java 8 there is the Nashorn JavaScript engine. So instead of attempting to read an evaluate a Java lambda expression, you could read and evaluate a JavaScript function using Nashorn, called from Java.

    The following code takes a function in arg[0] and applies it to each subsequent, printing the results:

    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]));
            }
        }
    }
    

    For example, running the command

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

    gives the results

    52.0
    70.0
    142.0
    

    The wrapping of the function string inside of new java.util.function.Function is necessary in order to create an adapter between Nashorn's notion of a JavaScript function and Java's Function interface. (There might be a better way, but I'm not aware of one.) The cast of the return value of eval to Function<Object,Object> results in an unchecked cast warning, which is unavoidable, I think, since this is the boundary between JavaScript, a dynamically-typed language, and Java, which is statically-typed. Finally, no error checking is done. I'm sure this will blow up in a variety of nasty ways if certain assumptions are violated, such as the first argument not actually representing a JavaScript function.

    Still, you might find this technique useful if you have a need to evaluate expressions or functions read from a file.

    0 讨论(0)
  • 2020-12-08 03:23

    I believe that using Nashorn JavaScript engine mentioned in Stuart's answer is the best choice in most cases. However if, for some reason, it's desired to stay within the Java world I have recently created the LambdaFromString library that converts a String code to lambda at runtime.

    When using that library the code doing what is specified in the question looks like this:

        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
    

    The only thing that differs is that the type of lambda has to be known and passed to the library so that the compiler knows what "+" means in this context.

    0 讨论(0)
提交回复
热议问题