Skip to content
Advertisement

Compilation error while merging two Maps is being issued for Map.Entry::getKey

Whenever I use Map.Entry::getKey in my streams for my public methods, I get an issue around my method not being static. I even tried making my method static, and it didn’t work.

Below is the compile error I am getting from using Map.Entry()::getKey() :

Non-static method cannot be referenced from a static context

My code

/***
 * Merge 2 Maps and add the values together if they are in both maps
 * 
 * firstMap = {"Eggs": 3, "Cereal": 1}
 * secondMap = {"Eggs": 10, "Coke": 23, "Cereal": 1}
 * 
 * Answer = {"Eggs": 13, "Coke": 23, "Cereal": 2}
 * Notice that the Eggs are now 13
 * 
 * @param firstMap
 * @param secondMap
 * @return
 */
public Map<String, Integer> mergeAndAddValues(Map<String, String> firstMap, Map<String, String> secondMap) {

    return Stream.of(firstMap, secondMap)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(
                       Map.Entry()::getKey,
                       Map.Entry::getValue,
                       Integer::sum,
                       HashMap::new));
}

Advertisement

Answer

The method arguments are of type Map<String, String> and the return type is Map<String, Integer>.

In order to fix your method, you need to coerce entries that are of type Map.Entry<String, String> into entries Map.Entry<String, Integer>.

It could be done by applying map() operation inside the nested stream. Static method Map.entry() is used to create a new entry based on an existing one. I assume that all string are comprised of digits only, otherwise you need to apply an additional clearing before parsing them.

public Map<String, Integer> mergeAndAddValues(Map<String, String> firstMap,
                                              Map<String, String> secondMap) {

    return Stream.of(firstMap, secondMap)
            .flatMap(map -> map.entrySet().stream()
                    .map(entry -> Map.entry(entry.getKey(),
                            Integer.valueOf(entry.getValue()))))
            .collect(Collectors.toMap(
                       Map.Entry::getKey,
                       Map.Entry::getValue,
                       Integer::sum));
}

Note a flavor of Collectors.toMap that doesn’t require a mapFactory (i.e. a version of Collectors.toMap that accepts only three arguments) is used because you’ll get a HashMap as a result by default.

Providing HashMap::new manually doesn’t byes you anything, conversely your code becomes a bit more rigid, and if in the future HashMap will get replaced with a more performant general purpose implementation of the Map you will not get it for free, for that your code will need to be changed.

I even tried making my method static … I am getting from using Map.Entry()::getKey()

Non-static method cannot be referenced from a static context

This issue isn’t connected with static or instance method, unfortunately it’s a rare case when error message isn’t very helpful. If you replace these method references with lambdas, then the compiler will correctly point that types String and Integer are incompatible.

And there’s no need for strange manipulations with a syntax, I kindly advise you to get familiar with these tutorials on lambdas and method references.

Both lambda expressions and method references are used to provide an implementation of a function interface, which is an interface that defines one and only one abstract method. I.e. both lambda and method reference should implement this single method.

All arguments that Collectors.toMap() expects are functional interfaces built-in in Java.

In the case when implantation of the behavior defined by a function interface already exists (i.e. you have a method somewhere which does what the method declared in the interface is expected to do), you can make use of it with a method reference.

There are four kinds of method references (quote from the Oracles’s tutorial referenced above):

  • Reference to a static method ContainingClass::staticMethodName
  • Reference to an instance method of a particular object containingObject::instanceMethodName
  • Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
  • Reference to a constructor ClassName::new

Note that the syntax of method references doesn’t require parentheses, neither after a type nor after a method name.

So correct syntax will be Map.Entry::getKey, and that is a reference to an instance method of an arbitrary object of a particular type. I.e. type Map.Entry actually implies an element of the stream (an object, not an interface).

Reminder: Entry is a nested interface that represents a key-value pair, defined in inside a Map interface. Hence, the correct syntax to refer to it Map.Entry (without any parentheses).

And getKey is one of the instance methods declared by the Entry interface.

Recap

The correct syntax to define a Function (which is standard functional interface expected as the first argument by the Collectors.toMap() with a method reference is:

Map.Entry::getKey

Where the first part before the double colon is the type of the object that will be passed to a function. And second part is a method name.

Parentheses are never used after a type in Java (don’t confuse the reference to a type with a constructor invocation). And after a method name parentheses are dropped because that how the language was designed, and I guess because method references are intended to be concise and expressive.

Advertisement