4

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.

davidalayachew
  • 1,279
  • 1
  • 11
  • 22
  • 4
    You want `Function.identity()`, not `Function::identity`. – Slaw May 23 '22 at 02:45
  • Also, if there might be multiple strings with the same length then you either want to provide a merge function or use [`Collectors.groupingBy`](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/stream/Collectors.html#groupingBy(java.util.function.Function)). – Slaw May 23 '22 at 02:48
  • @Slaw I see what you mean. That definitely worked. Please turn your comment into an answer and I will give it a checkmark. – davidalayachew May 23 '22 at 02:49
  • @Slaw Yes, that was an arbitrary example that was poorly thought up. Truthfully, I'm using this pattern style to glean metrics from a `Collection` of data. Ran into this bug, and then I created this runnable example that captured the point in it's smallest form. – davidalayachew May 23 '22 at 02:50

1 Answers1

5

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(...).
Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Thank you, this is very helpful. Are static methods incapable of using the `::` syntax? Please let me know if that should be a separate question. – davidalayachew May 23 '22 at 02:57
  • 1
    No. It's possible for a static method can be used as the target of a method reference. Say you had a static method `static void display(Foo f)` and you had a `List`. You could do `fooList.forEach(TheClass::display)`. The method reference `TheClass::display` is the same as the lambda expression `(Foo f) -> TheClass.display(f)`. – Slaw May 23 '22 at 03:01
  • 1
    In other words, you need to make sure a method reference can be applied for the given number of arguments and the types (including return type, if any) involved. – Slaw May 23 '22 at 03:03
  • That makes sense. It sounds like the reason why my attempt failed is because `Function.identity()` already returns a function. So, to do `Function::identity` is to essentially say, *return a function that returns a `Function`* – davidalayachew May 23 '22 at 03:04
  • 1
    Yeah, essentially. `Function::identity` would be the same as doing `(Function func) -> func.identity()`. Basically a `Function, Function>` (I think; the generics might be off as I haven't tested it). If the `func.identity()` is confusing to you, that's because it's confusing; you can [call static methods on an instance variable](https://stackoverflow.com/questions/32039657/accessing-static-method-through-an-object-instance), but [you should not do so](https://stackoverflow.com/questions/7884004/is-calling-static-methods-via-an-object-bad-form-why). – Slaw May 23 '22 at 03:08
  • That clarified everything. Thank you for the detailed explanation. I understand the concept much better now. – davidalayachew May 23 '22 at 03:09