user3316027
user3316027

Reputation: 23

Variable method reference in java 8

I am trying to create a method reference with the variable, which holds method name for some method from an object:

SomeClass obj = new SomeClass();
String methodName = "someMethod";

I am looking for way to create exactly obj::someMethod, but using variable methodName for this. Is it possible?

I know how to create functional interface instance from methodName and obj:

() -> {
    try {
        return obj.getClass().getMethod(methodName).invoke(obj);
    } catch (NoSuchMethodException | IllegalAccessException e) {
        return null;
    }
};

but I am wondering is this can be done in more shorthand way.

Upvotes: 2

Views: 121

Answers (2)

Holger
Holger

Reputation: 298113

If you strive for brevity rather than performance, there are Expression and Statement since Java 1.4.

Object obj="example";
String methodName="substring";
Object[] arg={ 2, 5 };
try {
    Object result=new Expression(obj, methodName, arg).getValue();
    new Statement(System.out, "println", new Object[]{ result }).execute();
} catch (Exception ex) {
    Logger.getLogger(YourClass.class.getName()).log(Level.SEVERE, null, ex);
}

But if you want to use them in the context of the standard function interfaces, which don’t allow checked exceptions, the exception handling will dominate the source code.


You can bind a reflectively acquired method to a functional interface even under Java 7:

Object obj="example";
String methodName="substring";
Object[] arg={ 2, 5 };
Supplier<String> s;
Consumer<String> c;
try {
    MethodHandle mh=MethodHandles.insertArguments(
        MethodHandles.lookup().bind(obj, methodName,
            MethodType.methodType(String.class, int.class, int.class)),
        0, arg);
    s = MethodHandleProxies.asInterfaceInstance(Supplier.class, mh);
    mh=MethodHandles.lookup().bind(System.out, "println",
        MethodType.methodType(void.class, String.class));
    c = MethodHandleProxies.asInterfaceInstance(Consumer.class, mh);
} catch(NoSuchMethodException | IllegalAccessException ex) {
    Logger.getLogger(YourClass.class.getName()).log(Level.SEVERE, null, ex);
    return;
}
String result=s.get();
c.accept(result);

This is not shorter, but avoids performing the reflective lookup in each function evaluation.


Potentially more efficient is to use the LambdaMetafactory introduced in Java 8, which is the back-end of the lambda expressions and method references at runtime.

Object obj="example";
String methodName="substring";
Object[] arg={ 2, 5 };
Supplier<String> s;
Consumer<String> c;
try {
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh=lookup.findVirtual(String.class, methodName,
        MethodType.methodType(String.class, int.class, int.class));
    s = (Supplier<String>)LambdaMetafactory.metafactory(lookup, "get",
        mh.type().changeReturnType(Supplier.class),
        MethodType.methodType(Object.class), mh, MethodType.methodType(String.class))
        .getTarget().bindTo(obj).invokeWithArguments(arg);
    mh=MethodHandles.lookup().findVirtual(PrintStream.class, "println",
        MethodType.methodType(void.class, String.class));
    c = (Consumer<String>)LambdaMetafactory.metafactory(lookup, "accept",
        MethodType.methodType(Consumer.class, PrintStream.class),
        MethodType.methodType(void.class, Object.class), mh,
        MethodType.methodType(void.class, String.class))
        .getTarget().bindTo(System.out).invokeExact();
} catch(Throwable ex) {
    Logger.getLogger(YourClass.class.getName()).log(Level.SEVERE, null, ex);
    return;
}
String result=s.get();
c.accept(result);

This has a higher creation complexity, but the subsequent execution of the functions will have an efficiency on par with compile-time method references as there is no technical difference anymore.

Upvotes: 1

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272467

This is a very rare case - it's highly unlikely that the Java8 syntax sugar has been optimised for this. In particular, it would be impossible for the usual compile-time type checking to occur (remember that a method reference is just syntax sugar for an anonymous class that adheres to a particular type contract).

If this pattern is common in your codebase (hopefully it's not!), you could just move it to, say, a static utility method, and then do () -> Utils.invoke(obj, methodName).

Upvotes: 0

Related Questions