0

I have some stream handling code that takes a stream of words and performs some operations on them, then reduces them to a Map containing the words as keys and the number of occurrences of the word as a Long value. For the sake of the brevity of the code, I used the jOOL library's Seq class, which contains a number of useful shortcut methods.

The code compiles just fine if I write it like this:

item.setWordIndex (
        getWords (item)                      // returns a Seq<String>
              .map (this::removePunctuation) // String -> String
              .map (stemmer::stem)           // String -> String
              .groupBy(str -> str, Collectors.counting ()));

However, if I attempt to replace the str -> str lambda with the more self-documenting Function::identity, I get the following errors:

The method setWordIndex(Map<String,Long>) in the type MyClass is not applicable for the arguments (Map<Object,Long>)
The type Function does not define identity(String) that is applicable here

Why does Function::identity behave any differently to str -> str, which I (perhaps naively) assumed was directly equivalent, and why can't the compiler handle it when it is used?

(And yes, I'm aware I could remove the identity function by moving the previous map application into the groupBy operation, but I find the code clearer like this, because it follows the application logic more directly)

Jules
  • 14,841
  • 9
  • 83
  • 130
  • Possible duplicate of [Java 8 lambdas, Function.identity() or t->t](https://stackoverflow.com/questions/28032827/java-8-lambdas-function-identity-or-t-t) – Kraylog Dec 23 '17 at 13:09
  • You want `Function.identity()` rather than `Function::identity`. – Oliver Charlesworth Dec 23 '17 at 13:18
  • @NimrodArgov - that question asks which is preferable in cases where both work, rather than why `Function::identity` doesn't work in this specific case. – Jules Dec 23 '17 at 13:19
  • 1
    This reminds me of [a question I asked a while ago](https://stackoverflow.com/q/44935248/3788176). Perhaps the compiler options I mention there will give some useful insight? – Andy Turner Dec 23 '17 at 13:22
  • @AndyTurner - That's interesting. I'm guessing the logic here is working on similar lines, but I can't quite figure out how. – Jules Dec 23 '17 at 13:32
  • Can you simplify your example? Or make it more easily reproducible at least without jool: does the same happen with `Arrays.asList("").stream().groupBy(...)`? – Andy Turner Dec 23 '17 at 13:34

2 Answers2

7

You want Function.identity() (which returns a Function<T, T>), not Function::identity (which matches the SAM type Supplier<Function<T, T>>).

The following code compiles fine:

static String removePunctuation(String x) { return x; }
static String stem(String x) { return x; }

// ...

final Map<String, Long> yeah = Seq.of("a", "b", "c")
        .map(Test::removePunctuation)
        .map(Test::stem)
        .groupBy(Function.identity(), Collectors.counting());
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
2

There is a slight difference between the two types; they are not directly equivalent:

  • Function.identity() has to return the input type, because its type is Function<T, T>;
  • str -> str can return a wider type; effectively it is Function<? extends T, T>.
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • While this is true, I don't understand how it is relevant here: the correct type that makes the code work is `Function`, so the necessity for a wider type doesn't seem to be there? – Jules Dec 23 '17 at 13:16
  • @Jules I am merely pointing out that they are not directly equivalent. – Andy Turner Dec 23 '17 at 13:18