Краткий способ составления ссылок на методы Java?

Учитывая некоторые функции метода Java 8:

class Foo { Bar getBar() {} }
class Bar { Baz getBaz() {} }

Композиция из двух аксессоров выглядит так:

Function<Foo, Bar> getBarFromFoo = Foo::getBar;
Function<Bar, Baz> getBazFromBar = Bar::getBaz;
Function<Foo, Baz> getBazFromFoo = getBarFromFoo.andThen(getBazFromBar);

Есть ли более лаконичный способ? Кажется, это работает

((Function<Foo, Bar>) Foo::getBar).andThen(Bar::getBaz)

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

(Foo::getBar::getBaz было бы неплохо, но увы...)


person Gene    schedule 18.08.2017    source источник
comment
Почему бы не сделать foo -> foo.getBar()::getBaz? Похоже, вы слишком усложняете. Есть что-то, что мне не хватает?   -  person Dioxin    schedule 18.08.2017
comment
@VinceEmigh, ты имел в виду foo -> foo.getBar().getBaz()? иначе смысла нет   -  person Andrew Tobilko    schedule 18.08.2017
comment
@VinceEmigh, предложенный вами способ недостаточно гибкий. Гораздо лучше подготовить простые преобразователи (a->b, b->c), составляя их с помощью простых операций, таких как compose, andThen Function, во время выполнения, а не создавать все возможные случаи во время компиляции.   -  person Andrew Tobilko    schedule 18.08.2017
comment
@AndrewTobilko Вы можете использовать каррирование, если вас беспокоит гибкость: foo -> bar -> bar::getBaz   -  person Dioxin    schedule 18.08.2017
comment
@VinceEmigh, вы не можете использовать foo -> bar -> bar::getBaz для получения серии карт (здесь, чтобы получить Baz в качестве вывода). Он может вернуть что-то вроде Function<Foo, Function<Bar, Function<Bar, Baz>>>, что не имеет смысла   -  person Andrew Tobilko    schedule 18.08.2017


Ответы (5)


Определим функциональный интерфейс:

@FunctionalInterface
interface MyFunctionalInterface {
    Bar getBar(Foo f);
}

Мы можем немного упростить ссылку на метод Foo::getBar,

(Foo foo) -> foo.getBar();

что означает "получить Foo и вернуть Bar". Для этого описания подходят многие методы (например, наш интерфейс с getBar и Funtion<Foo, Bar> с его apply):

MyFunctionalInterface f1 = (Foo foo) -> foo.getBar();
Function<Foo, Bar> f2 = (Foo foo) -> foo.getBar();

Это ответ на вопрос, зачем нужен приведение.


Чтобы утвердительно ответить на вопрос, есть ли более лаконичный способ, мы должны установить контекст. Контекст однозначно дает нам Function для продолжения работы:

class Functions {
    public static <I, O> Function<I, O> of(Function<I, O> function) {
        return function;
    }
}

Functions.of(Foo::getBar).andThen(Bar::getBaz);
person Andrew Tobilko    schedule 18.08.2017

В Java нет специального способа составления функций, кроме andThen().

Вам нужно выполнить приведение, потому что Foo::getBar неоднозначно. ** Он может соответствовать каждому интерфейсу с похожей сигнатурой метода.

К сожалению, ((Function<Foo, Bar>) Foo::getBar).andThen(Bar::getBaz) — лучшее, что вы можете сделать.

person Grzegorz Piwowarek    schedule 18.08.2017

Может быть, просто использовать лямбда-выражение?

x -> x.getBar().getBaz()

Нет другого способа составить функции, кроме того, что вы уже предложили из-за неоднозначности типа. Это даже не намного дольше, чем Foo::getBar::getBaz

person Palle    schedule 18.08.2017

Это лучшее, что вы получите. Если вы думаете, что это сработает:

Foo::getBar::getBaz

Я не буду. Это связано с тем, что Foo::getBar является поли-выражением — это зависит от используемого контекста — это может быть, например, Function, но также может быть и Predicate; так что это потенциально может применяться ко многим вещам, поэтому актерский состав там просто необходим.

Вы можете скрыть это за методом, который будет выполнять цепочку и andThen, но проблема все еще существует.

ИЗМЕНИТЬ

См. пример здесь:

public static void cool(Predicate<Integer> predicate) {

}

public static void cool(Function<Integer, String> function) {

}

и выражение cool(i -> "Test"); не скомпилируется

person Eugene    schedule 18.08.2017
comment
Спасибо. Я знаю, что это не сработает. Просто желаю... Инферсер типов должен знать, что это не предикат, потому что он не возвращает логическое значение. Я не мастер вывода типов в Java, но кажется логичным, что, поскольку Foo::getBar удовлетворяет любому функциональному интерфейсу формы Foo->Bar, а getBaz удовлетворяет Bar->Baz, то кажется разумным, что Foo::getBar::getBaz является поливыражением, которое удовлетворяет любому функциональному интерфейсу вида Foo-›Baz. - person Gene; 19.08.2017
comment
@Gene Predicate и Function были лишь одним примером, подтверждающим, что это действительно поливыражения (см. редактирование) ... И type inference для лямбда-выражений и ссылок на методы не так очевидны; вот почему, вероятно, эта цепочка вообще не была реализована - это далеко не тривиально - person Eugene; 19.08.2017
comment
На самом деле я написал механизм вывода типов. Я знаю, что это не тривиально. Это не значит, что это невозможно. - person Gene; 22.08.2017
comment
@Gene Я не говорю, что это невозможно, кстати - это может быть улучшено в какой-то будущей версии idk ... просто на данный момент лично я (и я определенно не подходящий человек для этого) не вижу реальный способ сделать это безопасно. - person Eugene; 22.08.2017

С Guava вы можете использовать Функции .составить:

import com.google.common.base.Functions;

...

Functions.compose(Bar::getBaz, Foo::getBar);
person ZhekaKozlov    schedule 17.11.2019