web-dev-qa-db-fra.com

Java 8 Stream pour rechercher un élément dans la liste

J'ai la classe suivante:

public class Item {
    int id;
    String name;
    // few other fields, contructor, getters and setters
}

J'ai une liste d'articles. Je veux parcourir la liste et trouver l'instance qui a un identifiant particulier. J'essaie de le faire à travers des flux.

public void foobar() {

    List<Item> items = getItemList();
    List<Integer> ids = getIdsToLookup();
    int id, i = ids.size() - 1;

    while (i >= 0) {
        id = ids.get(i);
        Optional<Item> item = items
            .stream()
            .filter(a -> a.getId() == id)
            .findFirst();
        // do stuff
        i--;
    }
}

Est-ce le meilleur moyen de parcourir la liste et d'obtenir l'élément dont j'ai besoin? De plus, une erreur sur la ligne de filtre pour id indique que les variables utilisées dans les expressions lambda doivent être finales ou effectivement définitives. Peut-être que je peux définir l'identifiant dans la boucle while, cela devrait éliminer l'exception. Merci.

8
Gengis Khan

Si vous avez beaucoup d'identifiants à rechercher, il est recommandé d'utiliser une solution le faisant en un seul passage plutôt que d'effectuer une recherche linéaire pour chaque identifiant:

Map<Integer,Optional<Item>> map=ids.stream()
    .collect(Collectors.toMap(id -> id, id -> Optional.empty()));
items.forEach(item ->
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item)));
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) {
    map.get(it.previous()).ifPresent(item -> {
        // do stuff
    });
}

La première instruction crée simplement une carte à partir de la liste des identifiants, en mappant chaque identifiant de recherche à une variable Optional vide.

La deuxième instruction parcourt les éléments à l'aide de forEach et vérifie, pour chaque élément, s'il existe un mappage de son identifiant à un Optional vide et le remplace par un Optional encapsulant l'élément, le cas échéant, en une seule opération computeIfPresent.

La dernière boucle for parcourt la liste ids de façon à ce que vous souhaitiez les traiter dans cet ordre et exécuter l’action s’il existe une Optional non vide. Puisque la carte a été initialisée avec tous les identifiants trouvés dans la liste, get ne retournera jamais null, elle retournera une Optional vide, si l'identifiant n'a pas été trouvé dans la liste items.

Ainsi, en supposant que la recherche Map ’s ait la complexité temporelle O(1), comme c’est le cas dans les implémentations classiques, la complexité temporelle nette est passée de O(m×n) à O(m+n)

8
Holger

Vous pouvez essayer d'utiliser quelque chose comme ceci:

ids.forEach(id -> 
    list.stream()
    .filter(p -> p.getId() == id)
    .findFirst()
    .ifPresent(p -> {
        // do stuff here
    });
);

Facultatif, indique ici que votre méthode de filtrage peut renvoyer un flux vide. Par conséquent, si vous appelez findFirst, elle peut rechercher un ou zéro éléments.

9
ByeBye

Si vous souhaitez vous en tenir aux flux et effectuer une itération inverse, vous pouvez le faire de la manière suivante:

IntStream.iterate(ids.size() - 1, i -> i - 1)
    .limit(ids.size())
    .map(ids::get) // or .map(i -> ids.get(i))
    .forEach(id -> items.stream()
        .filter(item -> item.getId() == id)
        .findFirst().ifPresent(item -> {
            // do stuff
        }));

Ce code fait la même chose que le vôtre. 

Il itère en arrière, en commençant par une graine: ids.size() - 1. Le flux initial de ints est limité dans sa taille avec limit(), de sorte qu'il n'y a pas de ints négatif et que le flux a la même taille que la liste de ids. Ensuite, une opération map() convertit l'index en la variable id actuelle située à la vingtième position dans la liste ids (cette opération est effectuée au moyen de l'appel de ids.get(i)). Enfin, l'élément est recherché dans la liste items de la même manière que dans votre code.

Vous voulez trouver au plus un élément pour chaque identifiant donné et faire quelque chose avec l'élément trouvé, n'est-ce pas? Un peu plus d'amélioration de la performance:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set

items.stream()
    .filter(e -> idsToLookup.remove(e.getId()))
    .forEach(
       /* doing something */
     );
0
user_3380739