Chris Smith
Chris Smith

Reputation: 3012

Construct a Function at runtime

Is it possible to create a java.util.function.Function from a String using reflection? I can't seem to figure out a way to do that as there are no constructors for Function.

So say I have this method:

public Integer hello() {
    return 5;
}

I want to obtain a Function reference for the hello() method. I need to pass the name of the method, in this case, "hello" and get the Function.

I can get the Function in this way also, Test::hello, but I want to do something like, Test::"hello"


@immibis mentioned to try this:

private Function<Test, Integer> getFunction(String s){
    return (Test o) -> s;
}                      ^

But this sadly does not work when calling it like this, getFunction("hello"). IntelliJ IDEA gives me this error message:

Bad return type in lambda expression: String cannot be converted to Integer

This is not a duplicate of this, I do not want a Method response, but instead a Function response.

Upvotes: 2

Views: 1372

Answers (2)

YoYo
YoYo

Reputation: 9405

Intuitively we already put a lot of dynamism into functions (methods) we commonly write: we use all sorts of conditional branching and looping. If you can some how constrain what can be done, you can build your function using those simple constructs.

However, it is unclear from your question exactly what dynamism you expect:

  1. Actual java coding
  2. evaluating simple expressions like 1+2*(4-8)
  3. or some other script like construct you want to parse and evaluate

For actual Java coding, I would suggest to implement some kind of abstraction using an API/SPI pair. An SPI is a Service Provider Interface, or an abstraction that allows others to provide ready made and compiled classes as an extension. I believe OSGI provides a standard way of doing this.

To evaluate expressions there are many 3rd party libraries available. I developed one, but wont mention, as there are many others available. This board does not have the purpose to put forward one tool over the order. You can also consider Nashorn, which is a JavaScript engine.

To actually allow scripting, I would suggest to stick with javascript and use Nashorn. Java allows for plugins and actually enables you to add-in additional scripting engines as part of JSR-223.

[UPDATE]

Based on your clarifications and your example, yes we will need to use some type of reflection.

In your case, you want to lazily decide to which class or instance you are going to apply the method. This constrains me to provide the solution as below, however I took it one step further by optimizing the implementation for one case: where the class of instances the functional objects will be applied or can be predetermined.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;

public class Play {
  public int hello() {
    return 5;
  }

  static public int byebye() {
    return -1;
  }

  public static class ExtendedPlay extends Play {
    @Override
    public int hello() {
      return 10;
    }
  }

  private static <T> Function<T,Integer> getFunction(Class<T> clazz,String method) throws NoSuchMethodException {
    Method m = clazz.getDeclaredMethod(method);
    return (o)->{
      try {
        return ((Integer)m.invoke(o));
      } catch (IllegalAccessException | InvocationTargetException ex) {
        // Just hope and pray this will be all ok!
      }
      return 0;
    };
  }

  private static <T> Function<Class<T>,Integer> getStaticFunction(Class<T> clazz,String method) throws NoSuchMethodException {
    Method m = clazz.getDeclaredMethod(method);
    return (o)->{
      try {
        return ((Integer)m.invoke(o));
      } catch (IllegalAccessException | InvocationTargetException ex) {
        // Just hope and pray this will be all ok!
      }
      return 0;
    };
  }

  private static Function<Object,Integer> getFunction(String method) {
    return (o)->{
      try {
        Method m;
        if (o instanceof Class) // For static methods
          m = ((Class)o).getDeclaredMethod(method);
        else // For instance methods
          m = o.getClass().getDeclaredMethod(method);
        return ((Integer)m.invoke(o));
      } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
        // Just hope and pray this will be all ok!
      }
      return 0;
    };
  }

  public static void main(String args[]) throws NoSuchMethodException {
    // Little quicker because class type and Method instance can be resolved before multiple executions.
    // Method is cached and has better compile-time type checking, but requires extra paramter.
    Function<Play,Integer> f1 = getFunction(Play.class,"hello");
    Function<Class<Play>,Integer> f2 = getStaticFunction(Play.class,"byebye");

    // Little slower, because method instance has to be resolved for each subsequent call
    // of the dereferenced Function Object. Slower but use is simpler: one less parameter, and works for 
    // both static and instance methods.
    Function<Object,Integer> f3 = getFunction("hello");

    System.out.println("Value1 is: "+f1.apply(new ExtendedPlay()));
    System.out.println("Value2 is: "+f2.apply(Play.class));
    System.out.println("Value3 is: "+f3.apply(new Play()));
  }
}

Note that I made the solution in such a way that it would work on both static and instance methods.

Upvotes: 4

fps
fps

Reputation: 34460

@JoD.'s answer is correct. Here I'm taking another approach to solve the problem, without using reflection:

public class Test {

    private final int number;

    public Test(int number) {
        this.number = number;
    }

    public int increment() {
        return this.number + 1;
    }

    public int decrement() {
        return this.number - 1;
    }

    public static void main(String[] args) {

        // Define references to methods    
        Function<Test, Integer> incr = Test::increment;
        Function<Test, Integer> decr = Test::decrement;

        // Store method references in a map    
        Map<String, Function<Test, Integer>> map = new HashMap<>();
        map.put("incr", incr);
        map.put("decr", decr);

        // Define invocation: based on a string, select method reference to use
        Function<String, Function<Test, Integer>> invocation = k -> map.get(k);

        // Now the test
        Test test1 = new Test(10);

        int incrOnTest1 = invocation.apply("incr").apply(test1);
        int decrOnTest1 = invocation.apply("decr").apply(test1);

        System.out.println(incrOnTest1); // 11
        System.out.println(decrOnTest1); // 9

        Test test2 = new Test(50);

        int incrOnTest2 = invocation.apply("incr").apply(test2);
        int decrOnTest2 = invocation.apply("decr").apply(test2);

        System.out.println(incrOnTest2); // 51
        System.out.println(decrOnTest2); // 49
    }
}

The idea is to declare references to methods as functions, and store them in a map, keyed by some string. Then, a special invocation function is defined, which receives a string and queries the map to return the corresponding method reference. Finally, the returned function is applied with the desired object intance.

Upvotes: 2

Related Questions