4

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?

3 Answers3

5

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.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Thanks! This really helped me understanding the whole wibbly wobbly stuff which is going on here. Especially the __special void compatibility rule__ – Tomasz Bielaszewski May 18 '18 at 09:01
  • 1
    @TomaszBielaszewski just notice that not everything fits into that rule, if I remember correctly there are only 4 types, method invocation, constructor invocation, increment/decrement and I forgot the last one :) search the JLS if you want... – Eugene May 18 '18 at 09:02
4

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.

VHS
  • 9,534
  • 3
  • 19
  • 43
0

Sharing just a summary of the four types of Method References under the hood:

  1. Reference to a static method:

    Type::staticMethod ===>>> x -> Type.staticMethod(x)

  2. Reference to an instance method of a particular object:

    instance::instanceMethod ===>>> x -> instance.instanceMethod(x)

  3. Reference to an Instance Method of an Arbitrary Object of a Particular Type:

    Type::instanceMethod ===>>> x -> x.instanceMethod() OR (x, y) -> x.instanceMethod(y)

  4. Reference to a constructor:

    Type::new ===> x -> new Type(x)

Jonas Freire
  • 165
  • 3
  • 4