Supposons que j'ai une méthode qui renvoie une vue en lecture seule dans une liste de membres:
class Team {
private List < Player > players = new ArrayList < > ();
// ...
public List < Player > getPlayers() {
return Collections.unmodifiableList(players);
}
}
Supposons en outre que tout ce que le client fait est de parcourir la liste une fois, immédiatement. Peut-être pour mettre les joueurs dans une JList ou quelque chose comme ça. Le client ne stocke pas de référence à la liste pour une inspection ultérieure!
Compte tenu de ce scénario courant, dois-je renvoyer un flux à la place?
public Stream < Player > getPlayers() {
return players.stream();
}
Ou renvoyer un flux n'est-il pas idiomatique en Java? Les flux ont-ils été conçus pour être toujours «terminés» dans la même expression dans laquelle ils ont été créés?
java
collections
java-8
encapsulation
java-stream
fredoverflow
la source
la source
players.stream()
c'est une telle méthode qui renvoie un flux à l'appelant. La vraie question est la suivante: voulez-vous vraiment contraindre l'appelant à une traversée unique et lui refuser également l'accès à votre collection via l'Collection
API? Peut-être que l'appelant veut juste l'envoyeraddAll
à une autre collection?Réponses:
La réponse est, comme toujours, "ça dépend". Cela dépend de la taille de la collection retournée. Cela dépend de l'évolution du résultat dans le temps et de l'importance de la cohérence du résultat renvoyé. Et cela dépend beaucoup de la manière dont l'utilisateur est susceptible d'utiliser la réponse.
Tout d'abord, notez que vous pouvez toujours obtenir une collection à partir d'un flux, et vice versa:
La question est donc de savoir ce qui est plus utile pour vos appelants.
Si votre résultat peut être infini, il n'y a qu'un seul choix: Stream.
Si votre résultat peut être très volumineux, vous préférez probablement Stream, car il peut ne pas être utile de tout matérialiser en même temps, ce qui pourrait créer une pression considérable sur le tas.
Si tout ce que l'appelant va faire est de le parcourir (recherche, filtre, agrégation), vous devriez préférer Stream, car Stream les a déjà intégrés et il n'est pas nécessaire de matérialiser une collection (surtout si l'utilisateur ne peut pas traiter le résultat global.) C'est un cas très courant.
Même si vous savez que l'utilisateur l'itérera plusieurs fois ou le gardera autrement, vous voudrez peut-être renvoyer un Stream à la place, pour le simple fait que quelle que soit la collection dans laquelle vous choisissez de la mettre (par exemple, ArrayList) peut ne pas être la forme qu'ils veulent, puis l'appelant doit le copier de toute façon. si vous renvoyez un flux, ils peuvent le faire
collect(toCollection(factory))
et l'obtenir exactement sous la forme souhaitée.Les cas ci-dessus "prefer Stream" découlent principalement du fait que Stream est plus flexible; vous pouvez vous lier tardivement à la façon dont vous l'utilisez sans encourir les coûts et les contraintes liés à sa matérialisation dans une collection.
Le seul cas où vous devez renvoyer une collection est lorsqu'il existe des exigences de cohérence fortes et que vous devez produire un instantané cohérent d'une cible en mouvement. Ensuite, vous voudrez mettre les éléments dans une collection qui ne changera pas.
Je dirais donc que la plupart du temps, Stream est la bonne réponse - il est plus flexible, il n'impose pas de coûts de matérialisation généralement inutiles et peut être facilement transformé en collection de votre choix si nécessaire. Mais parfois, vous devrez peut-être retourner une collection (par exemple, en raison de fortes exigences de cohérence), ou vous voudrez peut-être renvoyer une collection car vous savez comment l'utilisateur l'utilisera et savez que c'est la chose la plus pratique pour eux.
la source
J'ai quelques points à ajouter à l'excellente réponse de Brian Goetz .
Il est assez courant de renvoyer un Stream à partir d'un appel de méthode de style "getter". Consultez la page d'utilisation de Stream dans Java 8 javadoc et recherchez les "méthodes ... qui retournent Stream" pour les packages autres que
java.util.Stream
. Ces méthodes sont généralement sur des classes qui représentent ou peuvent contenir plusieurs valeurs ou agrégations de quelque chose. Dans de tels cas, les API ont généralement renvoyé des collections ou des tableaux de celles-ci. Pour toutes les raisons que Brian a notées dans sa réponse, il est très flexible d'ajouter des méthodes de retour de flux ici. Beaucoup de ces classes ont déjà des méthodes de retour de collections ou de tableaux, car les classes sont antérieures à l'API Streams. Si vous concevez une nouvelle API et qu'il est judicieux de fournir des méthodes de retour de flux, il peut ne pas être nécessaire d'ajouter également des méthodes de retour de collection.Brian a mentionné le coût de «matérialisation» des valeurs dans une collection. Pour amplifier ce point, il y a en fait deux coûts ici: le coût de stockage des valeurs dans la collection (allocation de mémoire et copie) et aussi le coût de création des valeurs en premier lieu. Ce dernier coût peut souvent être réduit ou évité en tirant parti du comportement de recherche de paresse d'un Stream. Un bon exemple de ceci sont les API dans
java.nio.file.Files
:Non seulement doit
readAllLines
conserver le contenu entier du fichier en mémoire pour le stocker dans la liste de résultats, mais il doit également lire le fichier jusqu'à la toute fin avant qu'il ne renvoie la liste. Lalines
méthode peut revenir presque immédiatement après avoir effectué une configuration, laissant la lecture du fichier et le saut de ligne jusqu'à plus tard lorsque cela est nécessaire - ou pas du tout. C'est un énorme avantage, si par exemple, l'appelant ne s'intéresse qu'aux dix premières lignes:Bien sûr, un espace mémoire considérable peut être économisé si l'appelant filtre le flux pour ne renvoyer que les lignes correspondant à un modèle, etc.
Un idiome qui semble émerger est de nommer les méthodes de retour de flux après le pluriel du nom des choses qu'elles représentent ou contiennent, sans
get
préfixe. De plus, bien questream()
soit un nom raisonnable pour une méthode de retour de flux lorsqu'il n'y a qu'un seul ensemble possible de valeurs à renvoyer, il existe parfois des classes qui ont des agrégations de plusieurs types de valeurs. Par exemple, supposons que vous ayez un objet contenant à la fois des attributs et des éléments. Vous pouvez fournir deux API de retour de flux:la source
C'est ainsi qu'ils sont utilisés dans la plupart des exemples.
Remarque: renvoyer un Stream n'est pas si différent du retour d'un Iterator (admis avec une puissance beaucoup plus expressive)
À mon humble avis, la meilleure solution est d'encapsuler pourquoi vous faites cela et de ne pas renvoyer la collection.
par exemple
ou si vous comptez les compter
la source
Si le flux est fini et qu'il y a une opération attendue / normale sur les objets retournés qui lèvera une exception vérifiée, je retourne toujours une Collection. Parce que si vous allez faire quelque chose sur chacun des objets qui peuvent lever une exception de vérification, vous détesterez le flux. Un vrai manque avec les flux est l'incapacité de traiter les exceptions vérifiées avec élégance.
Maintenant, c'est peut-être un signe que vous n'avez pas besoin des exceptions vérifiées, ce qui est juste, mais parfois elles sont inévitables.
la source
Contrairement aux collections, les flux ont des caractéristiques supplémentaires . Un flux renvoyé par n'importe quelle méthode peut être:
Ces différences existent aussi dans les collections, mais là elles font partie du contrat évident:
En tant que consommateur d'un flux (provenant d'un retour de méthode ou d'un paramètre de méthode), c'est une situation dangereuse et déroutante. Pour s'assurer que leur algorithme se comporte correctement, les consommateurs de flux doivent s'assurer que l'algorithme ne fait pas d'hypothèse erronée sur les caractéristiques du flux. Et c'est une chose très difficile à faire. Dans les tests unitaires, cela signifierait que vous devez multiplier tous vos tests pour être répétés avec le même contenu de flux, mais avec des flux qui sont
Il est difficile d' écrire des méthodes de protection pour les flux qui lèvent une IllegalArgumentException si le flux d'entrée a des caractéristiques qui cassent votre algorithme, car les propriétés sont masquées.
Cela ne laisse à Stream qu'un choix valide dans une signature de méthode lorsque aucun des problèmes ci-dessus n'a d'importance, ce qui est rarement le cas.
Il est beaucoup plus sûr d'utiliser d'autres types de données dans les signatures de méthode avec un contrat explicite (et sans traitement implicite du pool de threads) qui rend impossible le traitement accidentel de données avec des hypothèses erronées sur l'ordre, la taille ou la parallélité (et l'utilisation du pool de threads).
la source
Je pense que cela dépend de votre scénario. Peut-être, si vous faites votre
Team
outilIterable<Player>
, c'est suffisant.ou dans un style fonctionnel:
Mais si vous voulez une API plus complète et plus fluide, un flux pourrait être une bonne solution.
la source
stream.count()
assez facile aussi), ce nombre n'est pas vraiment très significatif pour autre chose que le débogage ou l'estimation.Alors que certains des répondants les plus en vue ont donné d'excellents conseils généraux, je suis surpris que personne n'ait tout à fait déclaré:
Si vous avez déjà un "matérialisé"
Collection
en main (c'est-à-dire qu'il a déjà été créé avant l'appel - comme c'est le cas dans l'exemple donné, où il s'agit d'un champ membre), il ne sert à rien de le convertir en unStream
. L'appelant peut facilement le faire lui-même. Alors que, si l'appelant veut consommer les données dans leur forme d'origine, vous les convertissez en un lesStream
oblige à effectuer un travail redondant pour re-matérialiser une copie de la structure d'origine.la source
la source
J'aurais probablement 2 méthodes, une pour retourner a
Collection
et une pour retourner la collection en tant queStream
.C'est le meilleur des deux mondes. Le client peut choisir s'il veut la liste ou le flux et il n'a pas à faire la création d'objet supplémentaire consistant à créer une copie immuable de la liste juste pour obtenir un flux.
Cela n'ajoute également qu'une seule méthode supplémentaire à votre API afin que vous n'ayez pas trop de méthodes
la source