Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method Reference - passing Function to method with Consumer argument

I'm learning about Method References from Java 8 and I have difficulties understanding why does this work?

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

As you can see in run method - Holder::getHolded returns unbound method reference which you can invoke by passing object of type Holder as an argument. Like this: getHolded.apply(holder)

But why it casts this unbound method reference to Consumer when it is invoked directly as an method argument, and it does not doing it when I'm passing Function explicitly?

like image 918
Tomasz Bielaszewski Avatar asked Jan 28 '23 02:01

Tomasz Bielaszewski


2 Answers

Two things here, lambda expressions are poly expressions - they are inferred by the compiler using their context (like generics for example).

When you declare consume(Holder::getHolded);, compiler (under the so-called special void compatibility rule) will infer it to Consumer<Holder>.

And this might not look obvious, but think of a simplified example. It is generally more than ok do call a method and discard it's return type, right? For example:

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

Even if list.add(1) returns a boolean, we don't care about it.

Thus your example that works can be simplified to:

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

So these are both possible and valid declarations:

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

But in this case we are explicitly telling what type is Holder::getHolded,, it's not the compiler inferring, thus consume(getHolded); fails, a Consumer != Function after all.

like image 139
Eugene Avatar answered May 16 '23 04:05

Eugene


Java 8 introduced 4 important "function shapes" in the package java.util.function.

  • Consumer -> accepts a method reference (or a lambda expression) that takes one argument but doesn't return anything
  • Supplier -> accepts a method reference (or a lambda expression) that takes no argument and returns an object.
  • Function -> accepts a method reference (or a lambda expression) that takes one argument and returns an object.
  • Predicate -> accepts a method reference (or a lambda expression) that takes one argument and returns a boolean.

Read the Java docs for more detail.

To answer your question on why the first one works but the second one errors out, read following:

The second statement

consume(getHolded);

doesn't work because the type of the argument getHolded is Function<Holder, String> whereas the consume method expects an argument of type Consumer<Holder>. Since there is no parent-child relationship between Function and Consumer, it requires an explicit cast without which the compiler rightly errors out.

The first statement

consume(Holder::getHolded);

works because the method getHolded is declared as public String getHolded() meaning that it doesn't take any argument and returns a String. As per the new void compatibility rule, void types are inferred as the class containing the referenced method. Consider the following statement:

Consumer<Holder> consumer = Holder::getHolded;

This is a valid statement even though the method getHolded doesn't accept any arguments. This is allowed to facilitate inferring void types. Yet another example is the one you have mentioned yourself:

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

This is also a valid statement where you have said that the function object getHolded is a Function that returns String and accepts a type Holder even though the assigned method reference doesn't take any argument.

like image 30
VHS Avatar answered May 16 '23 04:05

VHS