Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java8: Is there a way to get an instance method reference from a class method reference?

Tags:

java

java-8

If I have, for instance, an interface like this:

public interface FooBar<A, B> {
    B foo(A a);

    B bar(A a);
}

Is there a way to take a class-level method reference like FooBar::bar and get an instance method reference?

ie. if I have

FooBar myFooBarInstance;
BiFunction<FooBar<A, B>, A, B> barFunction = FooBar::bar;

is there any simple way to get a Function<A,B> instance that would match what I'd get if I defined

Function<A, B> myBarFunction = myFooBarInstance::bar;
like image 973
Steve K Avatar asked Mar 11 '16 02:03

Steve K


2 Answers

What you want to do is called “partial function application” in the function programming world or just “binding a value to a parameter” in less functional terms. There’s no built-in method for that, but it’s easy to write your own:

public static <T,U,R> Function<U,R> bind(BiFunction<T,U,R> f, T t) {
    return u -> f.apply(t, u);
}

Then you can use it in your case:

FooBar<X,Y> instance=…;
BiFunction<FooBar<X,Y>,X,Y> barFunction=FooBar::bar;
Function<X,Y> myBarFunction=bind(barFunction, instance);

or just

// equivalent to myBarFunction=instance::bar
Function<X,Y> myBarFunction=bind(FooBar::bar, instance);

Note that the utility method is tight to the functional interfaces you are using, i.e. Function and BiFunction, not to the method references. It works for any BiFunction, be it implemented as method reference, lambda expression or ordinary class. But it will be useful only if you specifically need a Function instance, not for an arbitrary functional interface taking one parameter. You can convert a Function to another single-argument function type using ::apply but using bind(bifunc, value)::apply doesn’t offer any benefit over x -> bifunc.apply(value, u) at the place where the instance is required.

So if you have to convert BiFunctions to Functions via binding a first argument very often, the utility method might be useful. Otherwise, just use a lambda expression in the context where the actual target type is present. Of course, you can write a similar method for other interfaces as well, but this will again only be useful if you need it for that specific interface often.

Regarding functions taking more parameters, since the Java API doesn’t provide such functional interfaces, you will have to define the appropriate interface yourself, which offers the option of providing the binding function directly in the interface as default methods, e.g.

interface MyFunc3<A,B,C,R> {
    R apply(A a, B b, C c);
    default BiFunction<B,C,R> bind(A a) {
        return (b, c) -> apply(a, b, c);
    }
}
interface MyFunc4<A,B,C,D,R> {
    R apply(A a, B b, C c, D d);
    default MyFunc3<B,C,D,R> bind(A a) {
        return (b, c, d) -> apply(a, b, c, d);
    }
}

then, if you have a function like

MyFunc4<U, W, X, Y, Z> func = …;

you can do something like

MyFunc3<W, X, Y, Z> f3 = func.bind(u);

or

BiFunction<X, Y, Z> f2 = func.bind(u).bind(w);
like image 114
Holger Avatar answered Sep 27 '22 22:09

Holger


I realized that the answer's pretty simple once I thought about it. I can do this:

Function<A, B> instanceFunction = (a)->barFunction.apply(myFooBar, a);

It gets really ugly if you have methods that take more than a couple parameters, but it works...

like image 33
Steve K Avatar answered Sep 27 '22 22:09

Steve K