Question

Assume I have the following hand-crafted functional interface:

@FunctionalInterface
public static interface OnHandCardAddedListener extends HandView {
    @Override
    void onCardAdded(final Card card);

    @Override
    default void onCardPlayed(final int cardIndex) { }

    @Override
    default void onCardsSwapped(final int cardIndexOne, final int cardIndexTwo) { }
}

It is of the signature Card -> void, hence an implementation for this functional interface is a lambda that is an implementation of the Consumer<Card> functional interface.

I know that I perhaps should extend Consumer<Card> and implement the apply with a default method in the OnHandCardAddedListener interface.

But why does it not just work out of the box?

Testing with the following:

private void acceptConsumer(final Consumer<Card> cardConsumer) {

}

and testcode:

Consumer<Card> cardConsumer = card -> { };
acceptConsumer(cardConsumer);
OnHandCardAddedListener viewCardConsumer = card -> { };
acceptConsumer(viewCardConsumer); // does not compile

Here acceptConsumer(viewCardConsumer) does not compile, but why does it not compile? As far as I can see they should resolve to the same type of lambda, hence both be allowed for both concrete definitions.

Edit, I forgot to mention why this would be important, consider the case where I need to use a functional interface that has not been provided by java.util.function, then we can see that two clients need to real out their own concrete functional interface which does the same.
Hence a way is needed to generalize its usage.

Was it helpful?

Solution

The reason this doesn't work is that you have already assigned a type to your lambda expression.

Consider this example:

acceptConsumer(card -> {});

Here, the compiler can check what the type of the argument is supposed to be. Since the lambda provided is a legal representation of that argument type, all is well.
Compare this to your example:

OnHandCardAddedListener viewCardConsumer = card -> { };
acceptConsumer(viewCardConsumer); // does not compile

Here you are not passing a lambda to the method, but a totally different type than is expected. The fact that you use a lambda expression to initialize viewCardConsumer is completely irrelevant. This code is equivalent to the following code which I don't think you would expect to compile:

OnHandCardAddedListener viewCardConsumer = new OnHandCardAddedListener() {
    @Override
    void onCardAdded(final Card card){  };
};
acceptConsumer(viewCardConsumer); // does not compile either

OTHER TIPS

Try (untested):

acceptConsumer(viewCardConsumer::onCardAdded);

Edit:

why does this work and the other not?

One is a plain old Java method call and OnHandCardAddedListener doesn't extend Consumer. The provided solution is a method reference which has its own rules: only input and output parameter (and exceptions?) have to match.

Think about the possibility if it was the other way around:

Consumer<Card> viewCardConsumer = card -> { };
acceptConsumer(viewCardConsumer);

private void acceptConsumer(final OnHandCardAddedListener<Card> cardConsumer) {
    cardConsumer.onCardPlayed(1);
}

If we follow your initial thought that this should be possible, then we quickly encounter a problem: viewCardConsumer (which is a Consumer<Card>) does not have an onCardPlayed method!

The same goes for the example in your case,

private void acceptConsumer(final Consumer<Card> cardConsumer) {
    cardConsumer.apply(new Card());
}

If you would pass OnHandCardAddedListener to this method, it does not contain an apply method.

Given these examples, I wouldn't expect it to - neither would I want it to - work "out of the box".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top