Почему это использование Stream:: flatMap не так?

Я ожидал, что смогу использовать Stream:: flatMap, как это

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());

Но я получаю следующую ошибку компилятора

Test.java:25: ошибка: несовместимые типы: не может вызывать переменную типа (ов) R listOfStrings.stream(). FlatMap (str → duplicate (str)). Collect (Collectors.toList());

(несоответствие аргументов, неправильный тип возврата в выражении лямбда       Список не может быть преобразован в Stream)
где R, T - переменные типа:     R extends Объект, объявленный в методе flatMap (Function > )     T extends Объект, объявленный в интерфейсе Stream

В scala я могу сделать то, что, как я считаю, эквивалентно

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

Почему это не допустимое использование flatMap в java?

Ответ 1

В выражении лямбда в flatMap необходимо вернуть Stream, что видно из аргумента flatMap, который имеет тип Function<? super T, ? extends Stream<? extends R>>.

Следующий код будет компилироваться и работать нормально:

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

поскольку выражение лямбда str -> duplicate(str).stream() имеет тип Function<String, Stream<String>>.

Ответ 2

Если вы хотите дублировать каждый объект в потоке несколько раз, вам не нужно тратить память на это с помощью дополнительного ArrayList. Существует несколько более быстрых и быстрых альтернатив.

  • Создайте новый поток, используя Stream.generate, затем ограничьте его:

    listOfStrings.stream()
                 .flatMap(str -> Stream.generate(() -> str).limit(2))
                 .collect(Collectors.toList());
    
  • Сгенерировать последовательность чисел через IntStream.range и сопоставить их с одной строкой:

    listOfStrings.stream()
                 .flatMap(str -> IntStream.range(0, 2).mapToObj(i -> str))
                 .collect(Collectors.toList());
    
  • Используйте старый добрый Collections.nCopies:

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

Если вы уверены, что вы всегда будете дублировать ровно два раза, есть самая короткая альтернатива:

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