13

I have the following code. Sonar is complaining replace this lambda with a method reference.

Stream.iterate(0, i -> i + 1).limit(100).map(i -> Integer.toString(i));

If I replace it with it code below, it does not compile with compilation error: Type mismatch: cannot convert from Stream<Object> to <unknown>.

Stream.iterate(0, i -> i + 1).limit(100).map(Integer::toString);

How is Integer::toString converting Stream<Object> to <unknown>?

fastcodejava
  • 39,895
  • 28
  • 133
  • 186

4 Answers4

9

It's ambiguous because the static and non-static toString() methods are both compatible with the functional signature Integer -> String. You can use String::valueOf instead.

shmosel
  • 49,289
  • 6
  • 73
  • 138
9

You can't put Integer::toString because Integer has two implementations that fit to functional interface Function<Integer, String>, but you can use String::valueOf instead:

Stream.iterate(0, i -> i + 1)
        .limit(100)
        .map(String::valueOf)
        .collect(Collectors.toList())
Ulad
  • 1,083
  • 8
  • 17
  • 1
    *You can't put `Integer::toString` because it accepts `int` and your case you have `Integer` use.* That's not correct. Lambdas can implicitly box and unbox. – shmosel Dec 10 '18 at 23:29
  • @shmosel then why does it `IntStream.range(1, 100).mapToObj(Integer::toString).collect(Collectors.toList())` work? – Ulad Dec 10 '18 at 23:31
  • Because the `int` overload is more appropriate for a primitive stream. – shmosel Dec 10 '18 at 23:32
  • @shmosel yes, you are right about _Lambdas can implicitly box and unbox_ – Ulad Dec 10 '18 at 23:58
3

As mentioned by @shmosel already replacing the lambda with a method reference will lead to ambiguity as there's two toString method of the signarure:

because the call to Stream.iterate(0, i -> i + 1) returns a Stream<Integer> when you call map with the method reference Integer::toString the compiler is not sure whether you meant to do Integer.toString(i) or i.toString() hence the compilation error.

So here are other options to what's already been provided:

instead of Stream.iterate you can use IntStream.iterate then call mapToObj:

IntStream.iterate(0, i -> i + 1) // IntStream
         .limit(100) // IntStream
         .mapToObj(Integer::toString); // i1 -> Integer.toString(i1)

Another thing suggested by intelliJ is that you can actually do:

Stream.iterate(0, i -> i + 1) // Stream<Integer>
      .limit(100) // Stream<Integer>
      .map(Object::toString); // integer -> integer.toString()

where Object::toString is equivalent to the lambda integer -> integer.toString()


on another note, it's interesting that Sonar is suggesting to replace the lambda with a method reference in the code you've shown. intelliJ IDEA was smart enough not to suggest it.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
0

I think that IntStream is better for your code:

List<String> numbers = IntStream.range(0, 100)
                                .mapToObj(String::valueOf)
                                .collect(Collectors.toList());

Or for your example do use String.valueOf to conver int -> String:

List<String> numbers = Stream.iterate(0, i -> i + 1)
                             .limit(100)
                             .map(String::valueOf)
                             .collect(Collectors.toList());
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35