javamapsjava-streamcollectorsscope-resolution

Unable to initialize new List as value of a Map with the :: Operator?


While working on a card game project, I was trying to create a new Map while already having a List I wanted to use as a KeySet. The Map has to use Keys of type Player and have each of them hold a Value consisting of a single List<Trick>, with Player being an Interface and Trick - a class.

Initially I tried to use a Stream of the List<Player> players I had and then collect it to a Map in the following way:

private Map<Player, List<Trick>> playerTricks = players.stream()
        .collect(Collectors.toMap(x -> x, ArrayList::new));

My IDE, IntelliJ, however did not allow me this format, as it said "Cannot resolve Constructor ArrayList".

Much to my surprise, the IDE stopped complaining when I removed the ::-Operator:

(Collectors.toMap(x -> x, x -> new ArrayList<Trick>()));

While I now do have code with no Exceptions, I still can't understand why the Scope Resolution Operator would produce such an error. I'd be grateful if anyone was to explain it properly.

P.S. While searching to a solution, I found this StackOverflow Thread:

Storing a new object as the value of a hashmap?

It does concern a similar issue, but doesn't really address the problem I was facing.


Solution

  • The second parameter of Collectors.toMap takes a Function that takes the stream type and maps it to the value type of the new Map.

    Function<? super T,? extends U> valueMapper
    

    First, :: isn't called the scope resolution operator like in C++, although it functions somewhat similarly when creating a method reference in Java. It's just (simply) "double-colon operator".

    The method reference ArrayList::new doesn't match Function<Player, List<Trick>>, because there is no ArrayList(Player) constructor. This method reference cannot be used. You're attempting to ignore the Player argument, but the method reference must use it.

    When you replaced the method reference with a lambda expression x -> new ArrayList<Trick>(), that does match Function<Player, List<Trick>>. You're taking x, which is implicitly defined to be Player, and the type of the lambda expression is List<Trick>, which matches. You're ignoring the Player x in the lambda expression, before it would even get to the call to create a new ArrayList.

    Your lambda expression is not equivalent to the method reference, because your method reference attempts to find a ArrayList(Player) constructor while the lambda expression takes the Player and ignores it while just calling the no-arg ArrayList constructor explicitly.