Beispiel für eine FlatMap-Methode für Java 8 Streams

85

Ich habe das bevorstehende überprüft Java update, nämlich : Java 8 or JDK 8. Ja, ich bin ungeduldig, es gibt viele neue Dinge, aber es gibt etwas, das ich nicht verstehe, einen einfachen Code:

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

Die Javadocs sind

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

Gibt einen Stream zurück, der aus den Ergebnissen des Ersetzens jedes Elements dieses Streams durch den Inhalt eines zugeordneten Streams besteht, der durch Anwenden der bereitgestellten Zuordnungsfunktion auf jedes Element erstellt wurde. Jeder zugeordnete Stream wird geschlossen, nachdem sein Inhalt in diesen Stream eingefügt wurde. (Wenn ein zugeordneter Stream null ist, wird stattdessen ein leerer Stream verwendet.) Dies ist eine Zwischenoperation.

Ich würde mich freuen, wenn jemand einige einfache Beispiele flatMapaus der Praxis erstellen würde, wie Sie es in früheren Java-Versionen Java[6,7]codieren können und wie Sie dieselben Routinen mit codieren können Java 8.

chiperortiz
quelle
2
Es gibt ungefähr eine Million Beispiele für die Verwendung von flatMap (zumindest für Scala, und sie sind im Grunde die gleichen :)) im Internet. Haben Sie versucht zu suchen? Hier ist einer zu Beginn: brunton-spall.co.uk/post/2011/12/02/…
Peter Svensson
3
Ich verstehe Scala nicht Ich habe noch nie mit Scala gearbeitet
Chiperortiz
Was ich meine ist, dass flatMap ein allgemeines Konzept ist, das jetzt sowohl in Java als auch in Scala existiert.
Peter Svensson
ok ich werde mehr darüber lesen danke mann.
Chiperortiz
10
flatMap in Java ist die gleiche Idee, sieht aber bei Streams ganz anders aus. Zeigen Sie nicht auf Scala!
Orbfish

Antworten:

158

Für flatMapeinen Stream , der bereits flach ist, wie den, macht es keinen SinnStream<Integer> Sie in Ihrer Frage gezeigt haben, ist .

Wenn Sie jedoch ein Stream<List<Integer>>Dann hätten, wäre es sinnvoll und Sie könnten dies tun:

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

Welches würde drucken:

1
2
3
4
5

Um dies vor Java 8 zu tun, benötigen Sie nur eine Schleife:

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);
}
Nick Holt
quelle
113

Beispiel erfunden

Stellen Sie sich vor, Sie möchten die folgende Sequenz erstellen: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 usw. (mit anderen Worten: 1x1, 2x2, 3x3 usw.)

Damit flatMapkönnte es aussehen wie:

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

wo:

  • IntStream.rangeClosed(1, 4)Erstellt einen Stream intvon 1 bis einschließlich 4
  • IntStream.iterate(i, identity()).limit(i)erstellt einen Stream der Länge i von inti - so i = 4wird ein Stream erstellt, der darauf angewendet wird:4, 4, 4, 4
  • flatMap "glättet" den Stream und "verkettet" ihn mit dem ursprünglichen Stream

Mit Java <8 benötigen Sie zwei verschachtelte Schleifen:

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

Beispiel aus der realen Welt

Nehmen wir an, ich habe eine, List<TimeSeries>bei der jede TimeSeriesim Wesentlichen eine ist Map<LocalDate, Double>. Ich möchte eine Liste aller Daten erhalten, für die mindestens eine der Zeitreihen einen Wert hat. flatMapzur Rettung:

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

Es ist nicht nur lesbar, sondern wenn Sie plötzlich 100.000 Elemente verarbeiten müssen, parallel()verbessert das einfache Hinzufügen die Leistung, ohne dass Sie gleichzeitig Code schreiben müssen.

Assylien
quelle
14
Beide Beispiele sind viel besser als in der akzeptierten Antwort.
Sebastian Graf
Compiler beschwert sich über Identität () als undefiniert
Nirmal
2
@ user3320018 Sie müssen statisch importieren Function.identity.
Assylias
@assylias Ich habe versucht, java.util.function.Function zu importieren, aber es hat nicht funktioniert. Ich bin neu in Java 8 und dies kann Java 8-spezifisch sein oder auch nicht. Können Sie mir bitte genau sagen, wie dieser Fehler behoben werden kann?
Nirmal
4
import static java.util.function.Function.identity;
Assylias
18

Extrahieren Sie eindeutige ASC-sortierte Wörter aus einer Liste von Phrasen:

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

... und die Ausgabe:

Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]
Igor Baiborodine
quelle
11

Bin ich der einzige, der Abwicklungslisten langweilig findet? ;-);

Versuchen wir es mit Objekten. Beispiel aus der realen Welt übrigens.

Gegeben: Objekt, das sich wiederholende Aufgaben darstellt. Zu wichtigen Aufgabenfeldern: Erinnerungen beginnen zu klingeln startund wiederholen sich alle repeatPeriod repeatUnit(z. B. 5 STUNDEN) und es werden repeatCountinsgesamt Erinnerungen angezeigt (einschließlich des Startens einer).

Ziel: Erstellen Sie eine Liste mit Aufgabenkopien, eine für jeden Aufruf der Aufgabenerinnerung.

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

Ausgabe:

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: Ich würde mich freuen, wenn jemand eine einfachere Lösung vorschlagen würde, ich bin doch kein Profi.

UPDATE: @RBz hat um detaillierte Erklärung gebeten, also hier ist es. Grundsätzlich fügt flatMap alle Elemente aus Streams in einem anderen Stream in den Ausgabestream ein. Viele Streams hier :). Daher x -> LongStream.iterate...erstellt der Lambda-Ausdruck für jede Aufgabe im anfänglichen Stream einen Stream mit langen Werten, die die Startmomente der Task darstellen. Dieser Stream ist auf x.getRepeatCount()Instanzen beschränkt . Die Werte beginnen bei x.getStart().toEpochSecond(ZoneOffset.UTC)und jeder nächste Wert wird mit Lambda berechnet p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds(). boxed()Gibt den Stream mit jedem langen Wert als Long-Wrapper-Instanz zurück. Dann wird jedes Long in diesem Stream einer neuen Task-Instanz zugeordnet, die sich nicht mehr wiederholt und die genaue Ausführungszeit enthält. Dieses Beispiel enthält nur eine Aufgabe in der Eingabeliste. Aber stell dir vor, du hast tausend. Sie haben dann einen Stream von 1000 Streams von Task-Objekten. Und wasflatMapHier werden alle Aufgaben aus allen Streams auf denselben Ausgabestream übertragen. Das ist alles so wie ich es verstehe. Danke, für ihre Frage!

Aleksandr Kravets
quelle
8
Am I the only one who finds unwinding lists boring?+1
Whitfin
2
Ich finde es wirklich schwer, dieses Beispiel zu verstehen. :(
RBz
@ RBz Stream-Vorgänge sind manchmal nicht leicht zu verstehen, insbesondere wenn mehr als ein Vorgang beteiligt ist. Es ist jedoch eine Frage der Übung. Das Beste, was Sie tun können, ist, jedes unklare Wort aus dem Beispiel zu googeln und es selbst zu verwenden. Tatsächlich wäre das übliche imperative Stilbeispiel viel einfacher zu verstehen gewesen (und manchmal schneller). Denken Sie also nur daran, ob Sie wirklich Streams verwenden müssen.
Aleksandr Kravets
Danke für die Antwort Mann. Allerdings bin ich mit den Streams-Konzepten ganz in Ordnung. Was ich hier habe, ist beispielspezifisch. Ich war nicht so gut mit Time API, aber selbst ein Durchlesen hilft mir nicht zu verstehen, was hier passiert. Vielleicht bin ich naiv, aber es wird großartig sein, ein bisschen mehr Erklärung für Ihre Antwort zu haben. Es würde mir wirklich helfen, Ihr Beispiel zu verstehen. Ich weiß, ich bin nur neugierig daran gebunden! :)
RBz
Erstaunliches Beispiel ... am Anfang etwas schwer zu verstehen, aber sobald ich es in meiner IDE ausgeführt habe ... so mächtige Alternative !! Vielen Dank !
Cristiano
2

Diese Methode verwendet eine Funktion als Argument, diese Funktion akzeptiert einen Parameter T als Eingabeargument und gibt einen Strom von Parameter R als Rückgabewert zurück. Wenn diese Funktion auf jedes Element dieses Streams angewendet wird, wird ein Stream mit neuen Werten erstellt. Alle Elemente dieser neuen Streams, die von jedem Element generiert werden, werden dann in einen neuen Stream kopiert, der ein Rückgabewert dieser Methode ist.

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

lalitbhagtani
quelle
2

Ein sehr einfaches Beispiel: Teilen Sie eine Liste mit vollständigen Namen, um eine Liste mit Namen zu erhalten, unabhängig vom ersten oder letzten

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

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

Dies druckt aus:

Barry
Allen
Bruce
Wayne
Clark
Kent
Somaiah Kumbera
quelle
1

Angesichts dessen:

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

    }

und das:

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

}

Wir können dann folgendes tun:

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 );
G Butler
quelle