21
votes

I expected to be able to use Stream::flatMap like this

public static List<String> duplicate(String s) {

    List<String> l = new ArrayList<String>();
    l.add(s);
    l.add(s);

    return l;
}


listOfStrings.stream().flatMap(str -> duplicate(str)).collect(Collectors.toList());

But I get the following compiler error

Test.java:25: error: incompatible types: cannot infer type-variable(s) R listOfStrings.stream().flatMap(str -> duplicate(str)).collect(Collectors.toList());

(argument mismatch; bad return type in lambda expression List cannot be converted to Stream)
where R,T are type-variables: R extends Object declared in method flatMap(Function>) T extends Object declared in interface Stream

In scala I can do what I believe to be equivalent

scala> List(1,2,3).flatMap(duplicate(_))
res0: List[Int] = List(1, 1, 2, 2, 3, 3)

Why is this not a valid usage of flatMap in java?

2
check this answer among others on how and why to use flapMap() with java 8 Streams: stackoverflow.com/a/49297863/755401arthur

2 Answers

27
votes

The lambda expression in flatMap needs to return a Stream, as can be seen by the argument of flatMap which is of type Function<? super T, ? extends Stream<? extends R>>.

The following code will compile and run fine:

listOfStrings.stream()
             .flatMap(str -> duplicate(str).stream()) // note the .stream() here
             .collect(Collectors.toList());

because the lambda expression str -> duplicate(str).stream() is of type Function<String, Stream<String>>.

8
votes

If you want to duplicate each object in the stream several times, you don't need to waste memory on this with additional ArrayList. There are several shorter and faster alternatives.

  • Generate new stream using Stream.generate, then limit it:

    listOfStrings.stream()
                 .flatMap(str -> Stream.generate(() -> str).limit(2))
                 .collect(Collectors.toList());
    
  • Generate sequence of numbers via IntStream.range and map them to the same string:

    listOfStrings.stream()
                 .flatMap(str -> IntStream.range(0, 2).mapToObj(i -> str))
                 .collect(Collectors.toList());
    
  • Use good old Collections.nCopies:

    listOfStrings.stream()
                 .flatMap(str -> Collections.nCopies(2, str).stream())
                 .collect(Collectors.toList());
    

If you are sure that you will always duplicate exactly two times, there's the shortest alternative:

listOfStrings.stream()
             .flatMap(str -> Stream.of(str, str))
             .collect(Collectors.toList());