web-dev-qa-db-fra.com

Java 8 Exemple de méthode Streams FlatMap

J'ai vérifié le Java update à venir, à savoir: Java 8 or JDK 8. Oui, je suis impatient, il y a beaucoup de nouveautés, mais il y a quelque chose que je ne comprends pas, un code simple:

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

les javadocs sont

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

Renvoie un flux contenant les résultats du remplacement de chaque élément de ce flux par le contenu d'un flux mappé produit en appliquant la fonction de mappage fournie à chaque élément. Chaque flux mappé est fermé une fois que son contenu a été placé dans ce flux. (Si un flux mappé est null, un flux vide est utilisé à la place.) Il s'agit d'une opération intermédiaire.

J'apprécierais que quelqu'un crée de simples exemples concrets sur flatMap, comment vous pouvez le coder dans les versions antérieures de Java Java[6,7] et comment vous pouvez coder les mêmes routines à l'aide de Java 8.

83
chiperortiz

Cela n'a aucun sens de flatMap a Stream c'est déjà plat, comme le Stream<Integer> que vous avez montré dans votre question.

Cependant, si vous aviez un Stream<List<Integer>> alors cela aurait du sens et vous pourriez faire ceci:

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

Quel serait imprimer:

1
2
3
4
5

Pour faire cela avant Java 8, vous avez juste besoin de boucles:

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

Exemple composé

Imaginez que vous souhaitiez créer la séquence suivante: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, etc. (autrement dit: 1x1, 2x2, 3x3, etc.).

Avec flatMap, cela pourrait ressembler à:

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

où:

  • IntStream.rangeClosed(1, 4) crée un flux de int de 1 à 4 inclus
  • IntStream.iterate(i, identity()).limit(i) crée un flux de longueur i de int i - ainsi appliqué à i = 4 il crée un flux: 4, 4, 4, 4
  • flatMap "aplatit" le flux et le "concatène" au flux d'origine

Avec Java <8, vous auriez besoin de deux boucles imbriquées:

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

Exemple du monde réel

Disons que j'ai un List<TimeSeries> où chaque TimeSeries est essentiellement un Map<LocalDate, Double>. Je souhaite obtenir une liste de toutes les dates pour lesquelles au moins une des séries temporelles a une valeur. flatMap à la rescousse:

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

Non seulement il est lisible, mais si vous devez soudainement traiter 100 000 éléments, il vous suffit d’ajouter parallel() pour améliorer les performances sans écrire de code simultané.

113
assylias

Extrait des mots uniques triés ASC d'une liste de phrases:

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

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

... et la sortie:

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

Suis-je le seul à trouver que les listes de déroulement sont ennuyeuses? ;-)

Essayons avec des objets. Exemple du monde réel au fait.

Donné: objet représentant une tâche répétitive. À propos des champs de tâches importants: les rappels commencent à sonner à start et répètent chaque repeatPeriodrepeatUnit (par exemple 5 HEURES) et il y aura au total repeatCount rappels (y compris le premier ).

Objectif: créer une liste de copies de tâches, une pour chaque appel de rappel de tâches.

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

Sortie:

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}

P.S .: J'apprécierais que si quelqu'un suggère une solution plus simple, je ne suis pas un pro après tout.

PDATE: @RBz a demandé une explication détaillée, alors la voici. En principe, flatMap place tous les éléments des flux d'un autre flux dans le flux de sortie. Beaucoup de ruisseaux ici :). Ainsi, pour chaque tâche du flux initial, l'expression lambda x -> LongStream.iterate... crée un flux de valeurs longues représentant les instants de début de la tâche. Ce flux est limité à x.getRepeatCount() instances. Ses valeurs commencent à partir de x.getStart().toEpochSecond(ZoneOffset.UTC) et chaque valeur suivante est calculée à l'aide de lambda p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds(). boxed() renvoie le flux avec chaque valeur longue en tant qu'instance de wrapper long. Chaque long de ce flux est ensuite mappé sur une nouvelle instance de tâche qui n’est plus répétitive et qui contient une heure d’exécution exacte. Cet exemple contient une seule tâche dans la liste des entrées. Mais imaginez que vous en avez mille. Vous aurez alors un flux de 1000 flux d'objets Task. Et ce que flatMap fait ici, c'est mettre toutes les tâches de tous les flux sur le même flux de sortie. C'est tout ce que je comprends. Merci pour votre question!

11
Aleksandr Kravets

Un exemple très simple: divisez une liste de noms complets pour obtenir une liste de noms, indépendamment du premier ou du dernier

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

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

Cela imprime:

Barry
Allen
Bruce
Wayne
Clark
Kent
2
Somaiah Kumbera

Cette méthode prend une fonction en tant qu'argument, cette fonction accepte un paramètre T en tant qu'argument d'entrée et renvoie un flux du paramètre R en tant que valeur de retour. Lorsque cette fonction est appliquée à chaque élément de ce flux, elle génère un flux de nouvelles valeurs. Tous les éléments de ces nouveaux flux générés par chaque élément sont ensuite copiés dans un nouveau flux, qui sera une valeur de retour de cette méthode.

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

1
lalitbhagtani

Compte tenu de ceci:

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

    }

et ça:

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

}

Nous pouvons alors faire ceci:

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