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.
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)
…
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.
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 int
s est limité dans sa taille avec limit()
, de sorte qu'il n'y a pas de int
s 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 */
);