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
.
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);
}
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 inclusIntStream.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'origineAvec 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);
}
}
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é.
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]
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 repeatPeriod
repeatUnit
(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!
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
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.
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 );