Java8: есть ли способ получить ссылку на метод экземпляра из ссылки на метод класса?

если у меня есть, например, такой интерфейс:

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

    B bar(A a);
}

есть ли способ взять ссылку на метод уровня класса, например FooBar::bar и получить ссылку на метод экземпляра?

ie. если у меня есть

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

есть ли простой способ получить Function<A,B> экземпляр, который будет соответствовать тому, что я получу, если я определил

Function<A, B> myBarFunction = myFooBarInstance::bar;

2 ответов


то, что вы хотите сделать, называется "частичным приложением функций" в мире программирования функций или просто "привязкой значения к параметру" в менее функциональных терминах. Для этого нет встроенного метода, но легко написать свой собственный:

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

затем вы можете использовать его в вашем случае:

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

или просто

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

обратите внимание, что метод утилиты плотно прилегает к функциональным интерфейсам, которые вы используете, т. е. Function и BiFunction, а не метод ссылки. Он работает для любого BiFunction, будь то реализовано как ссылка на метод, лямбда-выражение или обычный класс. Но это будет полезно только в том случае, если вам конкретно нужно Function экземпляра, а не для произвольного функционального интерфейса принимает один параметр. Вы можете преобразовать Function к другому типу функции с одним аргументом, используя ::apply, но через bind(bifunc, value)::apply не предлагает никаких преимуществ над x -> bifunc.apply(value, u) в том месте, где требуется экземпляр.

Итак, если вам нужно конвертировать BiFunctions до Functions через привязку первого аргумента очень часто может быть полезен метод утилиты. В противном случае просто используйте лямбда-выражение в контексте, где присутствует фактический целевой тип. Конечно, вы можете написать аналогичный метод для других интерфейсов, но это снова будет полезно, только если вам это нужно для этого конкретного интерфейса часто.

что касается функций, принимающих больше параметров, так как Java API не предоставляет таких функциональных интерфейсов, вам придется определите соответствующий интерфейс самостоятельно, который предлагает возможность предоставления функции привязки непосредственно в интерфейсе как default методы, например,

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

тогда, если у вас есть функция, как

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

вы можете сделать что-то вроде

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

или

BiFunction<X, Y, Z> f2 = func.bind(u).bind(w);

Я понял, что ответ довольно прост, как только я подумал об этом. Я могу это сделать:

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

это становится действительно уродливым, если у вас есть методы, которые принимают больше, чем пару параметров, но это работает...