Search code examples
javadictionaryjava-streamcollectorscollect

Function::identity doesn't work in Collectors.toMap


I am trying to turn a List<String> into a Map<T, String>, with the value of the map being an element contained within the previous List<String>, and the key being some attribute of that String (for example, the length of the String, in which case, T would actually be an Integer).

I first attempted to do it this way. This is an implementation of the example I mentioned above. I want the length of the String to be the key, and the String itself to be the value. I wanted to use the Function::identity function to clearly specify that the value is the String itself.

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class SOQ_ME_20220522_fail
{

   public static void main(String[] args)
   {
   
      final List<String> list = List.of("apple", "banana", "coconut");
      
      var result = 
         list.stream()
            .collect(
               Collectors.toMap(
                  each -> each.length(),
                  Function::identity
               )
            )
         ;

      System.out.println(result);
   
   }
   
}

However, when I tried to compile it, I got the following compilation error.

SOQ_ME_20220522_fail.java:15: error: no suitable method found for toMap((each)->ea[...]gth(),Function::identity)
               Collectors.toMap(
                         ^
    method Collectors.<T#1,K#1,U#1>toMap(Function<? super T#1,? extends K#1>,Function<? super T#1,? extends U#1>) is not applicable
      (cannot infer type-variable(s) T#1,K#1,U#1
        (argument mismatch; unexpected static method <T#2>identity() found in unbound lookup))
    method Collectors.<T#3,K#2,U#2>toMap(Function<? super T#3,? extends K#2>,Function<? super T#3,? extends U#2>,BinaryOperator<U#2>) is not applicable
      (cannot infer type-variable(s) T#3,K#2,U#2
        (actual and formal argument lists differ in length))
    method Collectors.<T#4,K#3,U#3,M>toMap(Function<? super T#4,? extends K#3>,Function<? super T#4,? extends U#3>,BinaryOperator<U#3>,Supplier<M>) is not applicable
      (cannot infer type-variable(s) T#4,K#3,U#3,M
        (actual and formal argument lists differ in length))
  where T#1,K#1,U#1,T#2,T#3,K#2,U#2,T#4,K#3,U#3,M are type-variables:
    T#1 extends Object declared in method <T#1,K#1,U#1>toMap(Function<? super T#1,? extends K#1>,Function<? super T#1,? extends U#1>)
    K#1 extends Object declared in method <T#1,K#1,U#1>toMap(Function<? super T#1,? extends K#1>,Function<? super T#1,? extends U#1>)
    U#1 extends Object declared in method <T#1,K#1,U#1>toMap(Function<? super T#1,? extends K#1>,Function<? super T#1,? extends U#1>)
    T#2 extends Object declared in method <T#2>identity()
    T#3 extends Object declared in method <T#3,K#2,U#2>toMap(Function<? super T#3,? extends K#2>,Function<? super T#3,? extends U#2>,BinaryOperator<U#2>)
    K#2 extends Object declared in method <T#3,K#2,U#2>toMap(Function<? super T#3,? extends K#2>,Function<? super T#3,? extends U#2>,BinaryOperator<U#2>)
    U#2 extends Object declared in method <T#3,K#2,U#2>toMap(Function<? super T#3,? extends K#2>,Function<? super T#3,? extends U#2>,BinaryOperator<U#2>)
    T#4 extends Object declared in method <T#4,K#3,U#3,M>toMap(Function<? super T#4,? extends K#3>,Function<? super T#4,? extends U#3>,BinaryOperator<U#3>,Supplier<M>)
    K#3 extends Object declared in method <T#4,K#3,U#3,M>toMap(Function<? super T#4,? extends K#3>,Function<? super T#4,? extends U#3>,BinaryOperator<U#3>,Supplier<M>)
    U#3 extends Object declared in method <T#4,K#3,U#3,M>toMap(Function<? super T#4,? extends K#3>,Function<? super T#4,? extends U#3>,BinaryOperator<U#3>,Supplier<M>)
    M extends Map<K#3,U#3> declared in method <T#4,K#3,U#3,M>toMap(Function<? super T#4,? extends K#3>,Function<? super T#4,? extends U#3>,BinaryOperator<U#3>,Supplier<M>)
1 error

Now, this is very easy to workaround - I just need to sacrifice some readability by replacing Function::identity with (t -> t). Doing this, the class compiles and runs just fine.

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class SOQ_ME_20220522_success
{

   public static void main(String[] args)
   {
   
      final List<String> list = List.of("apple", "banana", "coconut");
      
      var result = 
         list.stream()
            .collect(
               Collectors.toMap(
                  each -> each.length(),
                  t -> t
               )
            )
         ;
         
      System.out.println(result);
   
   }
   
}
{5=apple, 6=banana, 7=coconut}

My question is this - why am I unable to use Function::identity to be able to fetch the data I need? Looking at the documentation for Function::identity, we can see that this static interface method is supposed to return a Function<T, T> that returns the object it receives. And if we go to the source code itself, we can see that it creates the exact same lambda that I did. So I am confused why the first attempt failed. Obviously, I am able to get past it easily, but I still want to know why the first attempt failed.


Solution

  • Replace Function::identity with Function.identity().

    The former, which is a method reference, would only work if you had a stream of Function objects and you wanted to call the identity method on each of them. The latter is simply a static method call which returns a Function that you pass as the value mapper argument.

    Two notes:

    1. The each -> each.length() lambda expression can be written as String::length (a method reference). Though you of course can keep it as a lambda expression if you want to.
    2. If multiple elements can map to the same key (e.g., in your example, if multiple strings can have the same length), then consider either providing a merge function or using Collectors#groupingBy(...).