web-dev-qa-db-fra.com

Java 8 Streams peut-il fonctionner sur un élément d'une collection, puis le supprimer?

Comme à peu près tout le monde, j'apprends toujours les subtilités (et les aime) de la nouvelle API Java 8 Streams. J'ai une question concernant l'utilisation des flux. Je vais vous donner un exemple simplifié.

Java Streams nous permet de prendre une Collection et d’utiliser la méthode stream() pour recevoir un flux de tous ses éléments. Il contient un certain nombre de méthodes utiles, telles que filter(), map() et forEach(), qui nous permettent d’utiliser des opérations lambda sur le contenu.

J'ai un code qui ressemble à ceci (simplifié):

set.stream().filter(item -> item.qualify())
    .map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());

L'idée est d'obtenir un mappage de tous les éléments de l'ensemble, correspondant à un certain qualificatif, puis de les utiliser. Après l'opération, ils ne servent à rien et doivent être supprimés de l'ensemble d'origine. Le code fonctionne bien, mais je ne peux m'empêcher de penser qu'il existe une opération dans Stream qui pourrait le faire pour moi, en une seule ligne.

Si c'est dans les Javadocs, il se peut que je le néglige.

Est-ce que quelqu'un plus familier avec l'API voit quelque chose comme ça?

60

Vous pouvez le faire comme ça:

set.removeIf(item -> {
    if (!item.qualify())
        return false;
    item.operate();
    return true;
});

Si item.operate() retourne toujours true, vous pouvez le faire très succinctement.

set.removeIf(item -> item.qualify() && item.operate());

Cependant, je n’aime pas ces approches car il n’est pas clair tout de suite ce qui se passe. Personnellement, je continuerais à utiliser une boucle for et une Iterator pour cela.

for (Iterator<Item> i = set.iterator(); i.hasNext();) {
    Item item = i.next();
    if (item.qualify()) {
        item.operate();
        i.remove();
    }
}
112
Paul Boddington

Dans une ligne non, mais vous pourriez peut-être utiliser le collecteur partitioningBy:

Map<Boolean, Set<Item>> map = 
    set.stream()
       .collect(partitioningBy(Item::qualify, toSet()));

map.get(true).forEach(i -> ((Qualifier)i).operate());
set = map.get(false);

Cela pourrait être plus efficace, car cela évite d'itérer l'ensemble deux fois, une pour filtrer le flux et une autre pour supprimer les éléments correspondants.

Sinon, je pense que votre approche est relativement bonne.

4
Alexis C.

Il y a beaucoup d'approches. Si vous utilisez myList.remove (element) doit remplacer equals (). Deuxièmement, je préfère:

allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));

Bonne chance et bon codage :) 

3
panayot_kulchev_bg

Ce que vous voulez vraiment faire est de partitionner votre ensemble. Malheureusement, en Java 8, le partitionnement n’est possible que via la méthode "collect" du terminal. Vous vous retrouvez avec quelque chose comme ça:

// test data set
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5);
// predicate separating even and odd numbers
Predicate<Integer> evenNumber = n -> n % 2 == 0;

// initial set partitioned by the predicate
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber));

// print even numbers
partitioned.get(true).forEach(System.out::println);
// do something else with the rest of the set (odd numbers)
doSomethingElse(partitioned.get(false))

Mis à jour:

Version Scala du code ci-dessus

val set = Set(1, 2, 3, 4, 5)
val partitioned = set.partition(_ % 2 == 0)
partitioned._1.foreach(println)
doSomethingElse(partitioned._2)`
2

si je comprends bien votre question:

set = set.stream().filter(item -> {
    if (item.qualify()) {
        ((Qualifier) item).operate();
        return false;
    }
    return true;
}).collect(Collectors.toSet());
1
user_3380739

Non, votre implémentation est probablement la plus simple. Vous pourriez faire quelque chose de profondément mal en modifiant l'état dans le prédicat removeIf, mais veuillez ne pas le faire. D'un autre côté, il pourrait être raisonnable de passer à une implémentation impérative basée sur un itérateur, ce qui pourrait être plus approprié et efficace pour ce cas d'utilisation.

1
Louis Wasserman

Après l'opération, ils ne servent à rien et doivent être supprimés de l'ensemble d'origine. Le code fonctionne bien, mais je ne peux pas m'empêcher de penser qu'il existe une opération dans Stream qui pourrait le faire pour moi, en une seule ligne.

Vous ne pouvez pas supprimer des éléments de la source du flux avec le flux. Depuis le Javadoc :

La plupart des opérations de flux acceptent des paramètres décrivant le comportement spécifié par l'utilisateur ..... Pour conserver le comportement correct, ces paramètres comportementaux:

  • ne doit pas interférer (ils ne modifient pas la source du flux); et
  • dans la plupart des cas, doivent être sans état (leur résultat ne devrait dépendre d'aucun état susceptible de changer au cours de l'exécution du pipeline de flux).
1
user7502825

Je vois les préoccupations de Paul concernant la clarté lors de l'utilisation de flux, énoncées dans la réponse principale. Peut-être que l'ajout de la variable explicative clarifie un peu les intentions.

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});
0
vacant78