NeatNerd
NeatNerd

Reputation: 2373

Pass implementation of functional interface as parameter

Functional Interface is described like this:

package models;

@FunctionalInterface
public interface EventReducer {
    void apply(Event event, GameState state);
}

I implement the interface in the following class:

package models.reducers;

import models.Event;
import models.EventReducer;
import models.GameState;
import models.events.MinionDeathEvent;

public class MinionDeath implements EventReducer {

    @Override
    public void apply(Event event, GameState state) {
        MinionDeathEvent deathEvent = (MinionDeathEvent)event;
        deathEvent.getPlayer().getBoard().remove(deathEvent.getMinion());
    }
}

How can I pass the implementation as a parameter? For example,

    private static final Map<EventType, EventReducer> ReducersMap = Map.ofEntries(
        entry(EventType.DEATH, MinionDeath::apply);
    );

Apparently, MinionDeath::apply is not a way to go

Upvotes: 2

Views: 1231

Answers (2)

Turing85
Turing85

Reputation: 20185

The problem

In the current solution, we try to reference an instance method without having an instance of a class

entry(EventType.DEATH, MinionDeath::apply);

The runtime is not able to create an instance of MinionDeath by itself, mainly because it is not guaranteed that a no-args constructor exists (it exists in this particular case, but not every class has a no-args constructor).

Solutions

There are several possibilities to solve the problem. Here are some of them:

  1. Create an instance of MinionDeath, pass it as argument.

    entry(EventType.DEATH, new MinionDeath());
    
  2. Lambda expression: This is basically inlining the implementation of MinionDeath diretcly into the lambda.

    entry(
        EventType.DEATH, 
        (event, state) -> ((MinionDeathEvent) event).getPlayer()
                .getBoard()
                .remove(deathEvent.getMinion()));
    
  3. Define the lambda as, e.g., static method, pass it as method reference.

    public class Game { // The class name is an assumption of mine
    
        ...
    
        private static void processMonsterDeathEvent(Event event, GameState state) {
            MinionDeathEvent deathEvent = (MinionDeathEvent)event;
            deathEvent.getPlayer().getBoard().remove(deathEvent.getMinion());
        }
    
        private static final Map<EventType, EventReducer> ReducersMap = Map.ofEntries(
            entry(EventType.DEATH, Game::processMonsterDeathEvent)
        );
    
        ...
    }
    

    A remark on this solution: This is different from the original solution since we are now referencing a static method. We could also change method apply in MinionDeath to be static. In this case, we should remove the ... implements EventRecorder from the class definition since it is no longer needed.

Upvotes: 5

Nikolas
Nikolas

Reputation: 44398

You want to pass an instance of the MinionDeath as long as it is a functional interface itself with exactly one abstract method, therefore is qualified. Also MinionDeath is EventReducer and is assignable into that class.

private static final Map<EventType, EventReducer> ReducersMap = Map.ofEntries(
    entry(EventType.DEATH, new MinionDeath())
);

Note this is effectively as same as you pass the lambda expression itself:

private static final Map<EventType, EventReducer> ReducersMap = Map.ofEntries(
    entry(
        EventType.DEATH,                                                      // key
        (event, state) -> {                                                   // value
            MinionDeathEvent deathEvent = (MinionDeathEvent)event;
            deathEvent.getPlayer().getBoard().remove(deathEvent.getMinion());
        }
    )
);

Also note, if you write entry(EventType.DEATH, MinionDeath::apply), it is not what you want and a new functional interface compatible with such lambda expression would have been created:

interface TriConsumer<T,R,S> {
    void consume(T t, R r, S s);
}
Map<EventType, TriConsumer<MinionDeath, Event, GameState>> map = new HashMap<>();
map.put(EventType.DEATH, MinionDeath::apply);

Upvotes: 1

Related Questions