Esempio di metodo FlatMap di Java 8 Streams


85

Sono stato controllando il prossimo Java update, vale a dire: Java 8 or JDK 8. Sì, sono impaziente, ci sono molte cose nuove, ma c'è qualcosa che non capisco, un semplice codice:

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

i javadoc sono

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

Restituisce un flusso costituito dai risultati della sostituzione di ogni elemento di questo flusso con il contenuto di un flusso mappato prodotto applicando la funzione di mappatura fornita a ciascun elemento. Ogni flusso mappato viene chiuso dopo che i suoi contenuti sono stati inseriti in questo flusso. (Se un flusso mappato è nullo, viene invece utilizzato un flusso vuoto.) Questa è un'operazione intermedia.

Apprezzerei se qualcuno creasse alcuni semplici esempi di vita reale su flatMapcome potresti codificarlo nelle precedenti versioni di Java Java[6,7]e su come puoi codificare le stesse routine usando Java 8.


2
Ci sono circa un milione di esempi di utilizzo di flatMap (almeno per Scala, e sono sostanzialmente gli stessi :)) su Internet, hai provato a cercare? Eccone uno con cui iniziare: brunton-spall.co.uk/post/2011/12/02/…
Peter Svensson

3
Non capisco Scala non ho mai lavorato con scala
chiperortiz

Quello che voglio dire è che flatMap è un concetto generale che ora esiste sia in Java che in Scala.
Peter Svensson

ok leggerò di più a riguardo grazie amico.
chiperortiz

10
flatMap in Java è la stessa idea ma ha un aspetto molto diverso con i flussi. Non indirizzare le persone a Scala!
orbfish

Risposte:


158

Non ha senso per flatMapuno Stream che è già piatto, come quello Stream<Integer>che hai mostrato nella tua domanda.

Tuttavia, se avessi un Stream<List<Integer>>, avrebbe senso e potresti farlo:

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

Che stamperebbe:

1
2
3
4
5

Per fare questo pre-Java 8 hai solo bisogno di un loop:

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

Esempio inventato

Immagina di voler creare la seguente sequenza: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ecc. (In altre parole: 1x1, 2x2, 3x3 ecc.)

Con flatMapesso potrebbe sembrare:

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

dove:

  • IntStream.rangeClosed(1, 4)crea un flusso intda 1 a 4, inclusi
  • IntStream.iterate(i, identity()).limit(i)crea un flusso di lunghezza i di inti - così applicato ad i = 4esso crea un flusso:4, 4, 4, 4
  • flatMap "appiattisce" lo stream e lo "concatena" allo stream originale

Con Java <8 avresti bisogno di due cicli annidati:

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

Esempio del mondo reale

Diciamo che ho un List<TimeSeries>dove ognuno TimeSeriesè essenzialmente un Map<LocalDate, Double>. Voglio ottenere un elenco di tutte le date per le quali almeno una delle serie temporali ha un valore. flatMapAl salvataggio:

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

Non solo è leggibile, ma se improvvisamente hai bisogno di elaborare 100k elementi, la semplice aggiunta parallel()migliorerà le prestazioni senza che tu debba scrivere codice simultaneo.


14
Entrambi gli esempi sono molto migliori rispetto alla risposta accettata.
Sebastian Graf

il compilatore si lamenta di identity () come undefined
Nirmal

2
@ user3320018 devi eseguire l'importazione statica Function.identity.
assylias

@assylias Ho provato a importare java.util.function.Function ma non ha funzionato, sono nuovo in java 8 e questo può o non può essere specifico per java 8 ma puoi dirmi esattamente come rimuovere quell'errore.
Nirmal

4
import static java.util.function.Function.identity;
assylias

18

Estrai parole uniche ordinate ASC da un elenco di frasi:

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

... e l'output:

Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]

11

Sono l'unico che trova noioso sciogliere le liste? ;-)

Proviamo con gli oggetti. A proposito, esempio del mondo reale.

Dato: oggetto che rappresenta un'attività ripetitiva. Informazioni sui campi attività importanti: i promemoria iniziano a squillare starte si ripetono ogni repeatPeriod repeatUnit(ad esempio 5 ORE) e ci saranno repeatCountpromemoria in totale (compreso quello iniziale).

Obiettivo: ottenere un elenco di copie dell'attività, una per ogni chiamata di promemoria dell'attività.

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

Produzione:

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: Apprezzerei se qualcuno suggerisse una soluzione più semplice, dopotutto non sono un professionista.

AGGIORNAMENTO: @RBz ha chiesto una spiegazione dettagliata, quindi eccola qui. Fondamentalmente flatMap inserisce tutti gli elementi dai flussi all'interno di un altro flusso nel flusso di output. Molti stream qui :). Pertanto, per ogni attività nel flusso iniziale, l'espressione lambda x -> LongStream.iterate...crea un flusso di valori lunghi che rappresentano i momenti di inizio dell'attività. Questo flusso è limitato alle x.getRepeatCount()istanze. I valori iniziano da x.getStart().toEpochSecond(ZoneOffset.UTC)e ogni valore successivo viene calcolato utilizzando lambda p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds(). boxed()restituisce il flusso con ogni valore long come istanza del wrapper Long. Quindi ogni Long in quel flusso viene mappato alla nuova istanza di Task che non è più ripetitiva e contiene il tempo di esecuzione esatto. Questo esempio contiene una sola attività nell'elenco di input. Ma immagina di averne mille. Avrai quindi un flusso di 1000 flussi di oggetti Task. E cosaflatMap che qui sta mettendo tutte le attività da tutti i flussi nello stesso flusso di output. È tutto quello che ho capito. Grazie per la tua domanda!


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

3
Trovo davvero difficile capire questo esempio. :(
RBz

Le operazioni di @RBz Stream a volte non sono facili da capire, soprattutto se sono coinvolte più operazioni. Tuttavia è questione di pratica. La cosa migliore che puoi fare è cercare su Google ogni parola poco chiara del campione e provare a usarla da solo. In effetti il ​​solito campione di stile imperativo sarebbe stato molto più facile da capire (e talvolta più veloce). Quindi pensa se hai davvero bisogno di usare gli stream.
Aleksandr Kravets

Grazie per la risposta amico. Tuttavia sono abbastanza d'accordo con i concetti di stream. Quello che sto riscontrando qui è specifico dell'esempio. Non ero così bravo con Time api, ma anche leggerlo non mi aiuta a capire cosa sta succedendo qui. Forse sono ingenuo, ma sarebbe fantastico avere qualche spiegazione in più per la tua risposta. Mi aiuterebbe davvero a capire il tuo esempio. Lo so, ci sono solo curioso! :)
RBz

Esempio incredibile ... un po 'difficile da capire all'inizio, ma una volta eseguito nel mio IDE ... un'alternativa così potente !! molte grazie !
Cristiano

2

Questo metodo accetta una funzione come argomento, questa funzione accetta un parametro T come argomento di input e restituisce un flusso di parametro R come valore di ritorno. Quando questa funzione viene applicata a ogni elemento di questo flusso, produce un flusso di nuovi valori. Tutti gli elementi di questi nuovi flussi generati da ogni elemento vengono quindi copiati in un nuovo flusso, che sarà un valore di ritorno di questo metodo.

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


2

Un esempio molto semplice: dividere un elenco di nomi completi per ottenere un elenco di nomi, indipendentemente dal primo o dall'ultimo

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

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

Questo stampa:

Barry
Allen
Bruce
Wayne
Clark
Kent

1

Dato ciò:

  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 + '}';
        }

    }

e questo:

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()
    {
    }

}

Possiamo quindi fare questo:

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 );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.