0

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).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1589188
  • 5,316
  • 17
  • 67
  • 130

2 Answers2

4

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Magnus
  • 7,952
  • 2
  • 26
  • 52
1

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);
    
Community
  • 1
  • 1
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • Haha, no, it was a typo. But yeah, good information there you provided. But sadly the function objects was not the same and the map could not look them up... – user1589188 Feb 27 '17 at 04:00
  • @user1589188 You could cache the method references outside the method instead of recreating them inside. But it's probably even simpler to just use a string or enum key, since there's nothing really tying the method reference to the executing method (that would only be possible by [digging though the stack trace](http://stackoverflow.com/q/421280/1553851)). – shmosel Feb 27 '17 at 04:09