Справочник по методу — передача функции в метод с аргументом-потребителем

Я изучаю ссылки на методы из Java 8, и мне трудно понять, почему это работает?

class Holder {
    private String holded;

    public Holder(String holded) {
        this.holded = holded;
    }

    public String getHolded() {
        return holded;
    }
}

private void run() {
    Function<Holder, String> getHolded = Holder::getHolded;

    consume(Holder::getHolded); //This is correct...
    consume(getHolded);         //...but this is not
}

private void consume(Consumer<Holder> consumer) {
    consumer.accept(null);
}

Как вы можете видеть в методе run - Holder::getHolded возвращает несвязанную ссылку на метод, которую вы можете вызвать, передав объект типа Holder в качестве аргумента. Вот так: getHolded.apply(holder)

Но почему эта несвязанная ссылка на метод приводится к Consumer, когда он вызывается напрямую в качестве аргумента метода, и не делает этого, когда я явно передаю Function?


person Tomasz Bielaszewski    schedule 16.05.2018    source источник
comment
Обратите внимание, что вы также должны иметь возможность consumer(getHolded::apply).   -  person Joshua Taylor    schedule 16.05.2018
comment
Кстати, «держать» надо «держать».   -  person Holger    schedule 17.05.2018
comment
@Holger :) ты везде! Я даже не заметил, красиво!   -  person Eugene    schedule 17.05.2018
comment
consumer(getHolded::apply) будет соответствовать тому же правилу совместимости void, что и представленное consume(Holder::getHolded);) это правило упоминается @Eugene ниже   -  person Tomasz Bielaszewski    schedule 18.05.2018


Ответы (2)


Здесь две вещи: лямбда-выражения являются поливыражениями — они выводятся компилятором с использованием их контекста (например, дженерики).

Когда вы объявляете consume(Holder::getHolded);, компилятор (согласно так называемому специальному правилу совместимости с пустотой) выводит его как Consumer<Holder>.

И это может показаться неочевидным, но подумайте об упрощенном примере. Как правило, более чем нормально вызывать метод и отбрасывать его возвращаемый тип, верно? Например:

List<Integer> list = new ArrayList<>();
list.add(1);

Даже если list.add(1) возвращает логическое значение, нас это не волнует.

Таким образом, ваш пример, который работает, можно упростить до:

consume(x -> {
        x.getHolded(); // ignore the result here
        return;
});

Итак, это как возможные, так и действительные объявления:

Consumer<Holder> consumer = Holder::getHolded;
Function<Holder, String> function = Holder::getHolded;

Но в этом случае мы явно сообщаем, какой тип является Holder::getHolded, это не делает вывод компилятора, поэтому consume(getHolded); терпит неудачу, а Consumer != Function в конце концов.

person Eugene    schedule 16.05.2018
comment
Спасибо! Это действительно помогло мне понять весь этот шаткий, шаткий материал, который здесь происходит. Особенно специальное правило совместимости void - person Tomasz Bielaszewski; 18.05.2018
comment
@TomaszBielaszewski просто заметьте, что не все вписывается в это правило, если я правильно помню, есть только 4 типа, вызов метода, вызов конструктора, увеличение/уменьшение, и я забыл последний :) поищите в JLS, если хотите... - person Eugene; 18.05.2018

Java 8 представила 4 важных «формы функций» в пакете java.util.function.

  • Потребитель -> принимает ссылку на метод (или лямбда-выражение), которая принимает один аргумент, но ничего не возвращает
  • Поставщик -> принимает ссылку на метод (или лямбда-выражение), которая не принимает аргументов и возвращает объект.
  • Функция -> принимает ссылку на метод (или лямбда-выражение), которая принимает один аргумент и возвращает объект.
  • Predicate -> принимает ссылку на метод (или лямбда-выражение), которая принимает один аргумент и возвращает логическое значение.

Прочтите документацию по Java. для более подробной информации.

Чтобы ответить на ваш вопрос о том, почему первый работает, а второй выдает ошибку, прочитайте следующее:

Второе утверждение

consume(getHolded);

не работает, потому что тип аргумента getHolded равен Function<Holder, String>, тогда как метод consume ожидает аргумент типа Consumer<Holder>. Поскольку между Function и Consumer нет отношения родитель-потомок, требуется явное приведение, без которого компилятор правильно ошибается.

Первое утверждение

consume(Holder::getHolded);

работает, потому что метод getHolded объявлен как public String getHolded(), что означает, что он не принимает никаких аргументов и возвращает String. Согласно новому правилу void compatibility, типы void выводятся как класс, содержащий указанный метод. Рассмотрим следующее утверждение:

Consumer<Holder> consumer = Holder::getHolded;

Это правильное утверждение, хотя метод getHolded не принимает никаких аргументов. Это позволяет облегчить вывод типов пустот. Еще один пример - тот, который вы упомянули сами:

Function<Holder, String> getHolded = Holder::getHolded;

Это также правильное утверждение, если вы сказали, что объект функции getHolded является Function, который возвращает String и принимает тип Holder, даже если назначенная ссылка на метод не принимает никаких аргументов.

person VHS    schedule 16.05.2018