Пример метода Java 8 Streams FlatMap

85

Я проверял предстоящее Java update, а именно: Java 8 or JDK 8. Да, я нетерпелив, есть много нового, но есть кое-что, чего я не понимаю, простой код:

final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();

javadocs

public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

Возвращает поток, состоящий из результатов замены каждого элемента этого потока содержимым сопоставленного потока, созданного путем применения предоставленной функции сопоставления к каждому элементу. Каждый отображаемый поток закрывается после того, как его содержимое было помещено в этот поток. (Если отображаемый поток имеет значение NULL, вместо этого используется пустой поток.) ​​Это промежуточная операция.

Я был бы признателен, если бы кто-нибудь создал несколько простых примеров из реальной жизни о flatMapтом, как вы могли бы кодировать это в предыдущих версиях java Java[6,7]и как вы можете кодировать те же подпрограммы, используя Java 8.

чиперортиз
источник
2
В Интернете есть около миллиона примеров использования flatMap (по крайней мере, для Scala, и они практически одинаковы :)). Вы пробовали искать? Вот один для начала: brunton-spall.co.uk/post/2011/12/02/…
Питер Свенссон
3
Я не понимаю Scala Я никогда не работал со
Scala
Я имею в виду, что flatMap - это общая концепция, которая сейчас существует как в Java, так и в Scala.
Питер Свенссон
Хорошо, я прочитаю об этом больше, спасибо, чувак.
chiperortiz
10
FlatMap в Java - это та же идея, но с потоками выглядит иначе. Не указывайте людям на Scala!
orbfish 04

Ответы:

158

Это не имеет смысла flatMapв поток , который уже плоский, как и Stream<Integer>вы показали в своем вопросе.

Однако, если бы у вас был, Stream<List<Integer>>тогда это имело бы смысл, и вы могли бы сделать это:

Stream<List<Integer>> integerListStream = Stream.of(
    Arrays.asList(1, 2), 
    Arrays.asList(3, 4), 
    Arrays.asList(5)
);

Stream<Integer> integerStream = integerListStream .flatMap(Collection::stream);
integerStream.forEach(System.out::println);

Что напечатает:

1
2
3
4
5

Чтобы сделать это до Java 8, вам просто нужны циклы:

List<List<Integer>> integerLists = Arrays.asList(
    Arrays.asList(1, 2), 
    Arrays.asList(3, 4), 
    Arrays.asList(5)
)

List<Integer> flattened = new ArrayList<>();

for (List<Integer> integerList : integerLists) {
    flattened.addAll(integerList);
}

for (Integer i : flattened) {
    System.out.println(i);
}
Ник Холт
источник
113

Выдуманный пример

Представьте, что вы хотите создать следующую последовательность: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 и т. Д. (Другими словами: 1x1, 2x2, 3x3 и т. Д.)

С flatMapего помощью это могло выглядеть так:

IntStream sequence = IntStream.rangeClosed(1, 4)
                          .flatMap(i -> IntStream.iterate(i, identity()).limit(i));
sequence.forEach(System.out::println);

где:

  • IntStream.rangeClosed(1, 4)создает поток intот 1 до 4 включительно
  • IntStream.iterate(i, identity()).limit(i)создает поток длиной i of inti, поэтому применительно к i = 4нему создает поток:4, 4, 4, 4
  • flatMap "сглаживает" поток и "объединяет" его с исходным потоком

С Java <8 вам понадобятся два вложенных цикла:

List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
    for (int j = 0; j < i; j++) {
        list.add(i);
    }
}

Пример из реального мира

Скажем, у меня есть, List<TimeSeries>где каждый TimeSeriesпо сути является файлом Map<LocalDate, Double>. Я хочу получить список всех дат, для которых хотя бы один временной ряд имеет значение. flatMapна помощь:

list.stream().parallel()
    .flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap
    .distinct()                         // remove duplicates
    .sorted()                           // sort ascending
    .collect(toList());

Он не только читабелен, но и если вам вдруг понадобится обработать 100 тыс. Элементов, простое добавление parallel()улучшит производительность без написания какого-либо параллельного кода.

ассилий
источник
14
Оба примера намного лучше, чем в принятом ответе.
Себастьян Граф
компилятор жалуется на identity () как на undefined
Нирмал
2
@ user3320018 вам нужен статический импорт Function.identity.
assylias
@assylias Я пробовал import java.util.function.Function, но не работал, я новичок в java 8, и это может быть или не быть специфичным для java 8, но не могли бы вы рассказать мне, как именно удалить эту ошибку.
Nirmal
4
import static java.util.function.Function.identity;
assylias
18

Извлеките уникальные слова, отсортированные по ASC, из списка фраз:

List<String> phrases = Arrays.asList(
        "sporadic perjury",
        "confounded skimming",
        "incumbent jailer",
        "confounded jailer");

List<String> uniqueWords = phrases
        .stream()
        .flatMap(phrase -> Stream.of(phrase.split("\\s+")))
        .distinct()
        .sorted()
        .collect(Collectors.toList());
System.out.println("Unique words: " + uniqueWords);

... и вывод:

Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]
Игорь Байбородин
источник
11

Неужели я единственный, кому скучно раскручивать списки? ;-)

Попробуем с предметами. Между прочим, пример из реального мира.

Дано: объект, представляющий повторяющуюся задачу. О важных полях задач: напоминания начинают звонить startи повторяться каждые repeatPeriod repeatUnit(например, 5 ЧАСОВ), и будут repeatCountнапоминания всего (включая начальное).

Цель: составить список копий задач, по одной для каждого вызова напоминания о задаче.

List<Task> tasks =
            Arrays.asList(
                    new Task(
                            false,//completed sign
                            "My important task",//task name (text)
                            LocalDateTime.now().plus(2, ChronoUnit.DAYS),//first reminder(start)
                            true,//is task repetitive?
                            1,//reminder interval
                            ChronoUnit.DAYS,//interval unit
                            5//total number of reminders
                    )
            );

tasks.stream().flatMap(
        x -> LongStream.iterate(
                x.getStart().toEpochSecond(ZoneOffset.UTC),
                p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds())
        ).limit(x.getRepeatCount()).boxed()
        .map( y -> new Task(x,LocalDateTime.ofEpochSecond(y,0,ZoneOffset.UTC)))
).forEach(System.out::println);

Выход:

Task{completed=false, text='My important task', start=2014-10-01T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}
Task{completed=false, text='My important task', start=2014-10-02T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}
Task{completed=false, text='My important task', start=2014-10-03T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}
Task{completed=false, text='My important task', start=2014-10-04T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}
Task{completed=false, text='My important task', start=2014-10-05T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}

PS: Буду признателен, если кто-нибудь предложит более простое решение, в конце концов, я не профессионал.

ОБНОВЛЕНИЕ: @RBz попросил подробное объяснение, вот оно. По сути, flatMap помещает все элементы из потоков внутри другого потока в выходной поток. Здесь много стримов :). Итак, для каждой задачи в лямбда-выражении исходного потока x -> LongStream.iterate...создается поток длинных значений, представляющих моменты начала задачи. Этот поток ограничен x.getRepeatCount()экземплярами. Его значения начинаются с, x.getStart().toEpochSecond(ZoneOffset.UTC)и каждое следующее значение рассчитывается с использованием лямбда p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds(). boxed()возвращает поток с каждым длинным значением как экземпляр оболочки Long. Затем каждый Long в этом потоке сопоставляется с новым экземпляром Task, который больше не повторяется и содержит точное время выполнения. Этот пример содержит только одну задачу во входном списке. Но представьте, что у вас есть тысяча. У вас будет поток из 1000 потоков объектов Task. И чтоflatMapздесь помещает все Задачи из всех потоков в один и тот же выходной поток. Вот и все, как я понимаю. Спасибо Вам за Ваш вопрос!

Александр Кравец
источник
8
Am I the only one who finds unwinding lists boring?+1
whitfin
3
Мне очень трудно понять этот пример. :(
RBz
Операции @RBz Stream иногда нелегко понять, особенно если задействовано более одной операции. Но это вопрос практики. Лучшее, что вы можете сделать, - это погуглить каждое нечеткое слово из образца и попробовать использовать его самостоятельно. Фактически, обычный образец императивного стиля было бы намного легче понять (а иногда и быстрее). Так что подумайте, действительно ли вам нужно использовать потоки.
Александр Кравец
Спасибо за ответ, чувак. Однако я вполне согласен с концепцией потоков. У меня проблемы с конкретным примером. Я не очень хорошо разбирался в Time api, но даже его прочтение не помогает мне понять, что здесь происходит. Возможно, я наивен, но было бы здорово получить более подробное объяснение вашего ответа. Это действительно помогло бы мне понять ваш пример. Я знаю, я просто привязан к этому из любопытства! :)
RBz
Удивительный пример ... сначала трудно понять, но как только я запустил его в своей среде IDE ... такая мощная альтернатива !! большое спасибо !
Cristiano
2

Этот метод принимает одну функцию в качестве аргумента, эта функция принимает один параметр T в качестве входного аргумента и возвращает один поток параметра R в качестве возвращаемого значения. Когда эта функция применяется к каждому элементу этого потока, она создает поток новых значений. Все элементы этих новых потоков, сгенерированные каждым элементом, затем копируются в новый поток, который будет возвращаемым значением этого метода.

http://codedestine.com/java-8-stream-flatmap-method/

лалитбхагтани
источник
2

Очень простой пример: разделите список полных имен, чтобы получить список имен, независимо от имени или фамилии.

 List<String> fullNames = Arrays.asList("Barry Allen", "Bruce Wayne", "Clark Kent");

 fullNames.stream()
            .flatMap(fullName -> Pattern.compile(" ").splitAsStream(fullName))
            .forEach(System.out::println);

Это распечатывает:

Barry
Allen
Bruce
Wayne
Clark
Kent
Сомаиа Кумбера
источник
1

Учитывая это:

  public class SalesTerritory
    {
        private String territoryName;
        private Set<String> geographicExtents;

        public SalesTerritory( String territoryName, Set<String> zipCodes )
        {
            this.territoryName = territoryName;
            this.geographicExtents = zipCodes;
        }

        public String getTerritoryName()
        {
            return territoryName;
        }

        public void setTerritoryName( String territoryName )
        {
            this.territoryName = territoryName;
        }

        public Set<String> getGeographicExtents()
        {
            return geographicExtents != null ? Collections.unmodifiableSet( geographicExtents ) : Collections.emptySet();
        }

        public void setGeographicExtents( Set<String> geographicExtents )
        {
            this.geographicExtents = new HashSet<>( geographicExtents );
        }

        @Override
        public int hashCode()
        {
            int hash = 7;
            hash = 53 * hash + Objects.hashCode( this.territoryName );
            return hash;
        }

        @Override
        public boolean equals( Object obj )
        {
            if ( this == obj ) {
                return true;
            }
            if ( obj == null ) {
                return false;
            }
            if ( getClass() != obj.getClass() ) {
                return false;
            }
            final SalesTerritory other = (SalesTerritory) obj;
            if ( !Objects.equals( this.territoryName, other.territoryName ) ) {
                return false;
            }
            return true;
        }

        @Override
        public String toString()
        {
            return "SalesTerritory{" + "territoryName=" + territoryName + ", geographicExtents=" + geographicExtents + '}';
        }

    }

и это:

public class SalesTerritories
{
    private static final Set<SalesTerritory> territories
        = new HashSet<>(
            Arrays.asList(
                new SalesTerritory[]{
                    new SalesTerritory( "North-East, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Maine", "New Hampshire", "Vermont",
                                                                                    "Rhode Island", "Massachusetts", "Connecticut",
                                                                                    "New York", "New Jersey", "Delaware", "Maryland",
                                                                                    "Eastern Pennsylvania", "District of Columbia" } ) ) ),
                    new SalesTerritory( "Appalachia, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "West-Virgina", "Kentucky",
                                                                                    "Western Pennsylvania" } ) ) ),
                    new SalesTerritory( "South-East, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Virginia", "North Carolina", "South Carolina",
                                                                                    "Georgia", "Florida", "Alabama", "Tennessee",
                                                                                    "Mississippi", "Arkansas", "Louisiana" } ) ) ),
                    new SalesTerritory( "Mid-West, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Ohio", "Michigan", "Wisconsin", "Minnesota",
                                                                                    "Iowa", "Missouri", "Illinois", "Indiana" } ) ) ),
                    new SalesTerritory( "Great Plains, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Oklahoma", "Kansas", "Nebraska",
                                                                                    "South Dakota", "North Dakota",
                                                                                    "Eastern Montana",
                                                                                    "Wyoming", "Colorada" } ) ) ),
                    new SalesTerritory( "Rocky Mountain, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Western Montana", "Idaho", "Utah", "Nevada" } ) ) ),
                    new SalesTerritory( "South-West, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Arizona", "New Mexico", "Texas" } ) ) ),
                    new SalesTerritory( "Pacific North-West, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "Washington", "Oregon", "Alaska" } ) ) ),
                    new SalesTerritory( "Pacific South-West, USA",
                                        new HashSet<>( Arrays.asList( new String[]{ "California", "Hawaii" } ) ) )
                }
            )
        );

    public static Set<SalesTerritory> getAllTerritories()
    {
        return Collections.unmodifiableSet( territories );
    }

    private SalesTerritories()
    {
    }

}

Затем мы можем сделать это:

System.out.println();
System.out
    .println( "We can use 'flatMap' in combination with the 'AbstractMap.SimpleEntry' class to flatten a hierarchical data-structure to a set of Key/Value pairs..." );
SalesTerritories.getAllTerritories()
    .stream()
    .flatMap( t -> t.getGeographicExtents()
        .stream()
        .map( ge -> new SimpleEntry<>( t.getTerritoryName(), ge ) )
    )
    .map( e -> String.format( "%-30s : %s",
                              e.getKey(),
                              e.getValue() ) )
    .forEach( System.out::println );
Дж. Батлер
источник