web-dev-qa-db-fra.com

Obtenir le dernier élément de Stream / List dans un one-liner

Comment puis-je obtenir le dernier élément d'un flux ou d'une liste dans le code suivant?

data.careas Est un List<CArea>:

CArea first = data.careas.stream()
                  .filter(c -> c.bbox.orientationHorizontal).findFirst().get();

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList()).; //how to?

Comme vous pouvez le constater, obtenir le premier élément, avec un certain filter, n’est pas difficile.

Cependant, obtenir le dernier élément dans une ligne est une vraie douleur:

  • Il semble que je ne puisse pas l'obtenir directement d'un Stream. (Cela n'aurait de sens que pour les flux finis)
  • Il semble également que vous ne puissiez pas obtenir des choses comme first() et last() à partir de l'interface List, ce qui est vraiment pénible.

Je ne vois aucun argument pour ne pas fournir de méthode first() et last() dans l'interface List, car les éléments qu'il contient sont ordonnés et, en outre, la taille est connu.

Mais comme dans la réponse initiale: Comment obtenir le dernier élément d’un Stream fini?

Personnellement, c'est le plus proche que j'ai pu obtenir:

int lastIndex = data.careas.stream()
        .filter(c -> c.bbox.orientationHorizontal)
        .mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);

Cependant, cela implique l'utilisation d'un indexOf sur chaque élément, ce qui n'est probablement pas ce que vous voulez en général, car cela pourrait nuire aux performances.

90
skiwi

Il est possible d’obtenir le dernier élément avec la méthode Stream :: réduire . La liste suivante contient un exemple minimal pour le cas général:

Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);

Cette implémentation fonctionne pour tous les flux ordonnés (y compris les flux créés à partir de Listes ). Pour les flux non ordonnés , il est impossible de spécifier quel élément sera retourné pour des raisons évidentes.

L'implémentation fonctionne pour les flux parallèles séquentiels et . Cela peut paraître surprenant à première vue, et malheureusement la documentation ne l’indique pas explicitement. Cependant, il s’agit d’une caractéristique importante des flux, et j’essaie de le préciser:

  • La Javadoc de la méthode Stream :: réduire indique qu'elle "est non contrainte d'exécuter séquentiellement ".
  • La Javadoc exige également que la fonction accumulateur "soit une associative , non interférant , fonction sans état pour combiner deux valeurs ", ce qui est évidemment le cas pour l'expression lambda (first, second) -> second.
  • La Javadoc pour opérations de réduction indique: "Les classes de flux ont plusieurs formes d'opérations de réduction générales, appelées reduction () et collect () [..] " et " une opération de réduction correctement construite est intrinsèquement parallélisable , tant que la fonction (s) utilisés pour traiter les éléments sont associatif et sans état . "

La documentation pour le proche parent Collectors est encore plus explicite: "Pour assurer que séquentiel et Les exécutions parallèles produisent des résultats équivalents , les fonctions de collecteur doivent satisfaire une identité et un - associativité contraintes. "


Retour à la question d'origine: le code suivant stocke une référence au dernier élément de la variable last et lève une exception si le flux est vide. La complexité est linéaire dans la longueur du flux.

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();
158
nosid

Si vous avez une collection (ou plus générale une Iterable), vous pouvez utiliser Google Guava

Iterables.getLast(myIterable)

comme oneliner pratique.

36
Peti

Une doublure (pas besoin de flux;):

Object lastElement = list.get(list.size()-1);
9
nimo23

Guava a dédié une méthode pour ce cas:

Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);

Cela équivaut à stream.reduce((a, b) -> b), mais les créateurs prétendent que ses performances sont bien meilleures.

De documentation :

Le temps d'exécution de cette méthode se situera entre O (log n) et O (n), ce qui donnera de meilleurs résultats sur les flux pouvant être fractionnés efficacement.

Il vaut la peine de mentionner que si le flux n'est pas ordonné, cette méthode se comporte comme findAny().

1
k13i