Приклад методу 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)

Повертає потік, що складається з результатів заміни кожного елемента цього потоку вмістом відображеного потоку, створеного шляхом застосування наданої функції відображення до кожного елемента. Кожен зіставлений потік закривається після розміщення його вмісту в цьому потоці. (Якщо зіставлений потік нульовий, замість нього використовується порожній потік.) Це проміжна операція.

Буду вдячний, якщо хтось створить кілька простих прикладів з реального життя про flatMapте, як ви можете кодувати його в попередніх версіях Java Java[6,7]і як ви можете кодувати ті самі підпрограми, використовуючи Java 8.


2
Є близько мільйона прикладів використання flatMap (принаймні для Scala, і вони в основному однакові :)) в Інтернеті, чи пробували ви шукати? Ось одне для початку: brunton-spall.co.uk/post/2011/12/02/…
Пітер Свенссон

3
я не розумію Scala, я ніколи не працював із Scala
chiperortiz

Я маю на увазі, що flatMap - це загальне поняття, яке зараз існує як на Java, так і на Scala.
Пітер Свенссон,

Гаразд, я прочитаю про це більше, дякую.
chiperortiz

10
flatMap в Java - та сама ідея, але з потоками виглядає зовсім інакше. Не вказуйте людям на Скалу!
orbfish

Відповіді:


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 з 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
Обидва приклади набагато кращі, ніж у прийнятій відповіді.
Себастьян Граф,

компілятор скаржиться на ідентичність () як невизначену
Нірмал

2
@ user3320018 вам потрібно статичне імпортування Function.identity.
assylias

@assylias Я спробував імпортувати java.util.function.Function, але не працював, я новачок у java 8, і це може бути, а може і не бути специфічним для java 8, але чи можете ви сказати мені, як саме видалити цю помилку.
Нірмал,

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 у цьому потоці відображається на новий екземпляр Завдання, який більше не повторюється і містить точний час виконання. Цей зразок містить лише одне Завдання у списку введення. Але уявіть, що у вас тисяча. Тоді у вас буде потік з 1000 потоків об’єктів Завдання. І щоflatMapтут робить розміщення всіх завдань з усіх потоків на один вихідний потік. Це все, як я розумію. Дякуємо за Ваше запитання!


8
Am I the only one who finds unwinding lists boring?+1
whitfin

3
Мені дуже важко зрозуміти цей приклад. :(
RBz

Операції @RBz Stream часом непросто зрозуміти, особливо якщо задіяно більше однієї операції. Однак це питання практики. Найкраще, що ви можете зробити, - це погуглити кожне незрозуміле слово із зразка та спробувати використати його самостійно. Насправді звичайний зразок імперативного стилю було б набагато легше зрозуміти (а іноді і швидше). Тож просто подумайте, чи дійсно вам потрібно використовувати потоки.
Олександр Кравець,

Дякую за відповідь. Однак я цілком добре з концепціями потоків. Що у мене тут виникає, це конкретно для прикладу. Я не був настільки хороший з api Time, але навіть прочитане не допомагає мені зрозуміти, що тут відбувається. Можливо, я налаштований наївно, але було б чудово мати трохи більше пояснень для своєї відповіді. Це справді допомогло б мені зрозуміти ваш приклад. Я знаю, я просто зацікавлений у цьому з цікавістю! :)
RBz

Дивовижний приклад ... трохи важко зрозуміти спочатку, але як тільки я запустив його у своїй IDE ... така потужна альтернатива !! дуже дякую !
Кріштіану

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 );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.