user1589188
user1589188

Reputation: 5736

Can you add a function object to a map in Java?

Say I have a small demo to count the number of times a method is called, e.g.

public class Test {

    public static Map<Function<String, Integer>, Integer> map = new HashMap<>();

    public Test() {
        map.put(this::method1, 0);
        map.put(this::method2, 0);
    }

    public Integer method1(String a) {
        Integer time = map.get(this::method1);
        map.put(this::method1, time + 1);
        return time;
    }

    public Integer method2(String a) {
        Integer time = map.get(this::method2);
        map.put(this::method2, time + 1);
        return time;
    }
}

The code above demoed the idea, but the code doesn't compile. It does not complain at the map.put(); it complains at the map.get() parts. Do you have an explanation? As well as a way to fix this (while still using function objects and a map, not two individual integers to do the counting).

Upvotes: 0

Views: 1758

Answers (2)

Magnus
Magnus

Reputation: 8310

You can make this compile by casting the function in the get method:

map.get((Function<String, Integer>)this::method2);

But the code still won't work.

Each lambda expression creates a new function class, and since Function does not implement hashcode and equals, the only way you could use them as map keys is if you use the same instance/object for the insertion and lookup. That said, I can't think of a reason why you would ever want to use a function as a map key.

Upvotes: 4

shmosel
shmosel

Reputation: 50726

As @SotiriosDelimanolis hinted, Map.get() accepts Object, not the key type, so the compiler is unable to infer the target lambda type. There are a few possible workarounds.

  1. Create a temporary variable:

    Function<String, Integer> key = this::method1;
    Integer time = map.get(key);
    
  2. Cast the lambda to the target type:

    Integer time = map.get((Function<String, Integer>)this::method1);
    
  3. Use an overload that does accept the key type, like merge(). This also improves your code by merging the get and put into one statement, and possibly making the constructor initialization unnecessary (did you really mean to initialize to 1?):

    map.merge(this::method1, 1, Integer::sum);
    

Upvotes: 1

Related Questions