Récupération d'une liste à partir d'un java.util.stream.Stream dans Java 8

443

Je jouais avec des lambdas Java 8 pour filtrer facilement les collections. Mais je n'ai pas trouvé de moyen concis pour récupérer le résultat sous la forme d'une nouvelle liste dans la même déclaration. Voici mon approche la plus concise à ce jour:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Les exemples sur le net n'ont pas répondu à ma question car ils s'arrêtent sans générer de nouvelle liste de résultats. Il doit y avoir un moyen plus concis. Je me serais attendu, que la Streamclasse a des méthodes comme toList(), toSet()...

Existe-t-il un moyen d' targetLongListaffecter directement les variables par la troisième ligne?

Daniel K.
la source
7
Au cas où vous n'en auriez pas besoin par la sourceLongListsuite, c'est Collection.removeIf(…)pour plus de commodité.
Holger
2
Que dis-tu de ça? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah

Réponses:

609

Ce que vous faites peut être le moyen le plus simple, à condition que votre flux reste séquentiel, sinon vous devrez auparavant appeler séquentiel () forEach.

[édition ultérieure: la raison pour laquelle l'appel à sequential () est nécessaire est que le code tel qu'il est ( forEach(targetLongList::add)) serait racé si le flux était parallèle. Même alors, il n'obtiendra pas l'effet escompté, comme cela forEachest explicitement non déterministe - même dans un flux séquentiel, l'ordre de traitement des éléments n'est pas garanti. Vous devrez utiliser forEachOrderedpour assurer une commande correcte. L'intention des concepteurs d'API Stream est que vous utilisiez le collecteur dans cette situation, comme ci-dessous.]

Une alternative est

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());
Maurice Naftalin
la source
10
Addition: Je pense que ce code devient un peu plus court, plus clair et plus joli si vous utilisez une importation statique de toList. Cela se fait en plaçant les éléments suivants parmi les importations du fichier: static import java.util.stream.Collectors.toList;. Ensuite, l'appel de collecte lit simplement .collect(toList()).
Lii
4
Dans Eclipse, il est possible de faire en sorte que l'EDI ajoute une importation statique pour les méthodes. Cela se fait en ajoutant la Collectorsclasse dans Préférences -> Java -> Éditeur -> Assistant de contenu -> Favoris . Après cela, il vous suffit de taper toLiau hit Ctr + Space pour que l'IDE remplisse toListet ajoute l'importation statique.
Lii
3
Une chose à garder à l'esprit est que IntStreamet certains autres presque mais pas tout à fait Streamn'ont pas la collect(Collector)méthode et vous devrez d'abord appeler IntStream.boxed()pour les convertir en une version régulière Stream. Là encore, peut-être que vous voulez juste toArray().
Mutant Bob
pourquoi nous devons utiliser sequential()avant forEachou utiliser 'forEachOrdered`
amarnath harish
1
@amarnathharish Parce que forEach ne garantit pas l'ordre d'exécution des opérations pour un flux parallèle. Le JavaDoc dit "Le comportement de cette opération est explicitement non déterministe. Pour les pipelines de flux parallèle, cette opération ne garantit pas de respecter l'ordre de rencontre du flux, car cela sacrifierait l'avantage du parallélisme." (La première phrase de cette citation signifie en fait que l'ordre n'est pas non plus garanti pour les flux séquentiels, bien qu'en pratique il soit préservé.)
Maurice Naftalin
183

Mise à jour:

Une autre approche consiste à utiliser Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Solution précédente:

Une autre approche consiste à utiliser Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));
MohamedSanaulla
la source
60
Ceci est cependant utile si vous souhaitez une implémentation de List particulière.
orbfish
1
Bien qu'il soit recommandé de coder contre les interfaces, il existe des cas évidents (l'un d'eux étant GWT) lorsque vous devez coder contre des implémentations concrètes (sauf si vous voulez que toutes les implémentations de List soient compilées et livrées en javascript).
Eduard Korenschi
3
Un autre pro de cette méthode, du Collectors::toListjavadoc: "Il n'y a aucune garantie sur le type, la mutabilité, la sérialisation ou la sécurité des threads de la liste retournée; si plus de contrôle sur la liste retournée est nécessaire, utilisez toCollection(Supplier)."
Charles Wood
12

J'aime utiliser une méthode util qui retourne un collecteur ArrayListquand c'est ce que je veux.

Je pense que la solution utilisée Collectors.toCollection(ArrayList::new)est un peu trop bruyante pour une opération aussi courante.

Exemple:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Avec cette réponse, je veux également démontrer à quel point il est simple de créer et d'utiliser des collecteurs personnalisés, ce qui est très utile en général.

Lii
la source
Si vous déclarez le résultat comme List <Long>, vous n'avez pas besoin d'utiliser cette méthode util. Collectors.toList fera l'affaire. De plus, l'utilisation de classes spécifiques au lieu d'interfaces est une odeur de code.
Lluis Martinez
1
@LluisMartinez: "Collectors.toList fera l'affaire." : Non, pas dans de nombreuses situations. Parce que ce n'est pas une bonne idée à utiliser toListsi vous voulez par exemple modifier la liste plus tard dans le programme. La toListdocumentation dit ceci: "Il n'y a aucune garantie sur le type, la mutabilité, la sérialisation ou la sécurité des threads de la liste renvoyée; si un plus grand contrôle sur la liste retournée est requis, utilisez toCollection." . Ma réponse montre un moyen de rendre plus pratique de le faire dans un cas commun.
Lii
Si vous souhaitez créer spécifiquement une ArrayList, c'est correct.
Lluis Martinez
5

Si vous disposez d'un tableau de primitives, vous pouvez utiliser les collections de primitives disponibles dans les collections Eclipse .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Si vous ne pouvez pas modifier la sourceLongList de List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Si vous souhaitez utiliser LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Remarque: je suis un contributeur aux collections Eclipse.

Nikhil Nanivadekar
la source
4

Un moyen un peu plus efficace (évitez la création de la liste source et la décompression automatique par le filtre):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());
msayag
la source
1

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces, la lib cyclops-react d'AOL (divulgation, je suis un contributeur) a des extensions pour tous les types de collections JDK , y compris List . L'interface ListX étend java.util.List et ajoute un grand nombre d'opérateurs utiles, y compris le filtre.

Vous pouvez simplement écrire-

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX peut également être créé à partir d'une liste existante (via ListX.fromIterable)

John McClean
la source
1

Il existe une autre variante de la méthode de collecte fournie par la classe LongStream et également par les classes IntStream et DoubleStream.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Effectue une opération de réduction mutable sur les éléments de ce flux. Une réduction modifiable est une réduction dans laquelle la valeur réduite est un conteneur de résultat modifiable, tel qu'une liste de tableaux, et les éléments sont incorporés en mettant à jour l'état du résultat plutôt qu'en remplaçant le résultat. Cela produit un résultat équivalent à:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Comme réduire (long, LongBinaryOperator), les opérations de collecte peuvent être parallélisées sans nécessiter de synchronisation supplémentaire. Il s'agit d'une opération de terminal.

Et la réponse à votre question avec cette méthode de collecte est la suivante:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

Vous trouverez ci-dessous la variante de référence de la méthode qui est assez intelligente, mais certaines sont difficiles à comprendre:

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

Ci-dessous sera la variante HashSet:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

De même, la variante LinkedList est comme ceci:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
Vaneet Kataria
la source
0

Dans le cas où quelqu'un (comme moi) cherche des moyens de traiter les objets au lieu des types primitifs, utilisez mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

impressions:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o
Kashyap
la source
0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));
sharath
la source
0

Voici le code d' AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Divulgation : Je suis le développeur d'AbacusUtil.

user_3380739
la source
Je ne trouve aucune méthode toList présente dans la classe LongStream. Pourriez-vous exécuter ce code?
Vaneet Kataria
@VaneetKataria try com.landawn.abacus.util.stream.LongStreamor LongStreamExin AbacusUtil
user_3380739
0

Vous pouvez réécrire le code comme ci-dessous:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());
Pratik Pawar
la source
Merci pour votre contribution. Cependant, veuillez expliquer ce que vous avez changé et dans quelle mesure cela se rapporte à la question.
B - rian
Ici, tout d'abord, j'ai converti ma liste de tableaux pour les utiliser à la vapeur en utilisant un filtre, je filtre les données requises. Enfin, j'ai utilisé la méthode de collecte du flux java 8 pour collecter des données dans une nouvelle liste appelée targetLongList.
Pratik Pawar
0

Pour collecter dans une liste mutable:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

Pour collecter dans une liste immuable:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Explication de collectla JavaDoc :

Effectue une opération de réduction mutable sur les éléments de ce flux à l'aide d'un collecteur. Un collecteur encapsule les fonctions utilisées comme arguments à collecter (fournisseur, BiConsumer, BiConsumer), permettant la réutilisation des stratégies de collecte et la composition des opérations de collecte telles que le regroupement ou le partitionnement à plusieurs niveaux. Si le flux est parallèle et que le collecteur est simultané et que le flux n'est pas ordonné ou que le collecteur n'est pas ordonné, une réduction simultanée sera effectuée (voir Collecteur pour plus de détails sur la réduction simultanée).

Il s'agit d'une opération de terminal.

Lorsqu'ils sont exécutés en parallèle, plusieurs résultats intermédiaires peuvent être instanciés, remplis et fusionnés de manière à maintenir l'isolement des structures de données mutables. Par conséquent, même lorsqu'il est exécuté en parallèle avec des structures de données non thread-safe (telles que ArrayList), aucune synchronisation supplémentaire n'est nécessaire pour une réduction parallèle.

Saikat
la source
-3

Si vous n'utilisez pas parallel()cela fonctionnera

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());
Debapriya Biswas
la source
Je n'aime pas que le collect () soit uniquement utilisé pour piloter le flux afin que le crochet peek () soit appelé sur chaque élément. Le résultat de l'opération du terminal est ignoré.
Daniel K.
Il est très étrange d'appeler collectet de ne pas enregistrer la valeur de retour. Dans ce cas, vous pouvez utiliser à la forEachplace. Mais c'est toujours une mauvaise solution.
Lii
1
Utiliser peek () de cette manière est un contre-modèle.
Grzegorz Piwowarek
Selon la méthode Stream Java docs peek, elle doit être utilisée uniquement à des fins de débogage. Elle ne doit pas être utilisée pour un traitement autre que le débogage.
Vaneet Kataria