1

I was expecting these to be simple drop-in replacements for each other, but they're not. Clearly I'm not understanding the notation.

Can anyone explain why does that happen?

playButton.setOnAction(e -> track.play());

Here, compiler is happy with play() having a signature of

void play()

but here

playButton.setOnAction(track::play);

it requires

void play(Event e)
4
  • 8
    The first one explicitly says "I'll happily accept an Event but I'm going to ignore it" (e is inferred to the type Event). The second one doesn't. Java could support your case and ditch the input argument automatically but there would probably be just as many, if not more, cases where that behaviour would mask a genuine mistake/bug. If you want to ignore an input, it is better to be explicit about that intention. Commented Jul 4, 2022 at 11:24
  • 5
    track::play is like a shortcut to e -> track.play(e), it is clearly not the same as e -> track.play(). Commented Jul 4, 2022 at 11:28
  • You should study functional interfaces to better understand these syntaxes. Both lambdas and method references are treated like functional interfaces implementations by the compiler. Commented Jul 4, 2022 at 11:31
  • 1
    Does this answer your question? :: (double colon) operator in Java 8 Commented Jul 4, 2022 at 12:38

1 Answer 1

1

Here is a quote from the Java language specification:

A method reference expression (§15.13) is potentially compatible with a functional interface type T if, where the arity of the function type of T is n, there exists at least one potentially applicable method when the method reference expression targets the function type with arity n (§15.13.1), and one of the following is true:

  • The method reference expression has the form ReferenceType :: [TypeArguments] Identifier and at least one potentially applicable method is either (i) static and supports arity n, or (ii) not static and supports arity n-1.

  • The method reference expression has some other form and at least one potentially applicable method is not static.

...

The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types.

Every method reference should conform to a function interface (which is an interface that declares a single abstract method, non-overriding methods from Object class). The compiler needs to verify whether the provided reference resolves to a single existing method that has required arity (number of parameters) and their types match the types of the method declared by the target functional interface.

Let's have a look at the prior Java 8 code (code-sample from JavaFX tutorial by created Oracle):

button2.setOnAction(new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
        label.setText("Accepted");
    }
});

That is the "shape" that needs to be filled with a code describing an action. Method handle() of the EventHandler interface expects an event as an argument. Whether it would be used or not, that's up to you, the key point is that the abstract method of the target interface expects this argument to be provided.

By using a lambda expression e -> track.play() you're explicitly telling to ignore it.

And when you're passing a method reference track::play, which should be classified as a reference to an instance method of a particular object (see), the compiler will try to resolve it to a method play(Event) and you're getting a compilation error because it fails to find one.

In this case, reference track::play is not an equivalent of lambda e -> track.play(), but () -> track.play(), which doesn't conform to the target functional interface EventHandler.

In case if you wonder, how a method reference which can be applicable to a non-static method of arity n-1 mentioned in the specification (see case ii) can look like, here is an example:

BiPredicate<String, String> startsWith = String::startsWith; // the same as (str1, str2) -> str1.strarsWith(str2);

System.out.println(startsWith.test("abc", "a"));    // => true
System.out.println(startsWith.test("fooBar", "a")); // => false

And you can construct a similar reference which conforms to EventHandler interface and applicable an instance method of arity n-1 using one of the parameterless methods of the Event type. It's not likely to be useful in practice, but it would be valid from the compiler perspective of view, so feel free to try it as an exercise.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.