Afin d'essayer de comprendre en profondeur Java flux et séparateurs, j'ai quelques questions subtiles sur caractéristiques du séparateur:
Q1: Stream.empty()
vs Stream.of()
(Stream.of () sans args)
Stream.empty()
: SUBSIZÉ, TAILLE Stream.of()
: SUBSIZED, IMMUABLE , SIZED, ORDONNÉ Pourquoi Stream.empty()
n'a pas les mêmes caractéristiques que Stream.of()
? Notez qu'il a des impacts lors de l'utilisation en conjonction avec Stream.concat () (surtout ne pas avoir ORDERED
). Je dirais que Stream.empty()
devrait non seulement IMMUTABLE et ORDERED mais aussi DISTINCT et NONNULL . Cela a également du sens Stream.of()
avec un seul argument ayant [~ # ~] distict [~ # ~] .
Q2: LongStream.of()
n'ayant pas [~ # ~] non nul [~ # ~]
Je viens de remarquer que NONNULL n'est pas disponible dans LongStream.of
. NONNULL
n'est-il pas une caractéristique principale de tous les LongStream
s, IntStream
s et DoubleStream
s?
Q3: LongStream.range(,)
vs LongStream.range(,).boxed()
LongRange.range(,)
: SUBSIZED, IMMUTABLE, NONNULL, SIZED, ORDERED, SORTED, DISTINCTLongStream.range(,).boxed()
: SUBSIZÉ, TAILLE, COMMANDÉ Pourquoi .boxed()
perd toutes ces caractéristiques? Il ne devrait en perdre aucun.
Je comprends que .mapToObj()
peut perdre le NONNULL, IMMUTABLE et DISTICT , mais .boxed()
... doesn ' t sens.
Q4: .peek()
perd IMMUTABLE et NONNULL
LongStream.of(1)
: SUBSIZE, IMMUTABLE, NONNULL, SIZED, ... LongStream.of(1).peek()
: SUBSIZÉ, TAILLE, ...
Pourquoi .peek()
perd ces caractéristiques? .peek
Ne devrait pas vraiment en perdre.
Q5: .skip()
, .limit()
perd SUBSIZE, IMMUTABLE, NONNULL, SIZED
Notez juste que ces opérations perdent SUBSIZED, IMMUTABLE, NONNULL, SIZED . Pourquoi? Si la taille est disponible, il est également facile de calculer la taille finale.
Q6: .filter()
perd IMMUTABLE, NONNULL
Notez juste que cette opération perd également SUBSIZED, IMMUTABLE, NONNULL, SIZED . Il est logique de perdre SUBSIZED et SIZED , mais les deux autres n'ont pas de sens. Pourquoi?
J'apprécierai si quelqu'un qui comprend profondément le séparateur pourrait apporter une certaine clarté. Merci.
Je dois admettre que j'ai également eu des difficultés lorsque j'ai essayé pour la première fois de découvrir la signification réelle des caractéristiques et que j'avais l'impression que leur signification n'était pas clairement définie pendant la phase de mise en œuvre de Java 8 et qu'elle était utilisée de manière incohérente pour cette raison.
Considérez Spliterator.IMMUTABLE
:
Valeur caractéristique signifiant que la source de l'élément ne peut pas être structurellement modifiée; autrement dit, les éléments ne peuvent pas être ajoutés, remplacés ou supprimés, de sorte que de tels changements ne peuvent pas se produire pendant la traversée.
Il est étrange de voir "remplacé" dans cette liste, qui n'est généralement pas considérée comme une modification structurelle lorsque l'on parle d'un List
ou d'un tableau et par conséquent, les usines de flux et de séparateur acceptant un tableau (qui n'est pas cloné) rapportent IMMUTABLE
, comme LongStream.of(…)
ou Arrays.spliterator(long[])
.
Si nous interprétons cela plus généreusement comme "aussi longtemps que non observable par le client", il n'y a pas de différence significative avec CONCURRENT
, comme dans les deux cas certains les éléments seront signalés au client sans aucun moyen de reconnaître s'ils ont été ajoutés pendant la traversée ou si certains n'ont pas été signalés en raison de la suppression, car il n'y a aucun moyen de rembobiner un séparateur et de comparer.
La spécification continue:
Un Spliterator qui ne signale pas
IMMUTABLE
ouCONCURRENT
devrait avoir une politique documentée (par exemple, lancerConcurrentModificationException
) concernant les interférences structurelles détectées pendant la traversée.
Et c'est la seule chose pertinente, un séparateur signalant que IMMUTABLE
ou CONCURRENT
, est garanti de ne jamais lancer un ConcurrentModificationException
. Bien sûr, CONCURRENT
exclut SIZED
sémantiquement, mais cela n'a aucune conséquence sur le code client.
En fait, ces caractéristiques ne sont utilisées pour rien dans l'API Stream, par conséquent, leur utilisation incohérente ne serait jamais remarquée quelque part.
Ceci explique également pourquoi chaque opération intermédiaire a pour effet d'effacer les CONCURRENT
, IMMUTABLE
et NONNULL
caractéristiques: l'implémentation Stream ne les utilise pas et ses classes internes représentant l'état du flux ne les conservent pas.
De même, NONNULL
n'est utilisé nulle part, donc son absence pour certains flux n'a aucun effet. Je pourrais retrouver le problème de LongStream.of(…)
jusqu'à l'utilisation interne de Arrays.spliterator(long[], int, int)
qui délègue àSpliterators.spliterator(long[] array, int fromIndex, int toIndex, int additionalCharacteristics)
:
Le séparateur renvoyé rapporte toujours les caractéristiques
SIZED
etSUBSIZED
. L'appelant peut fournir des caractéristiques supplémentaires que le séparateur doit signaler. (Par exemple, s'il est connu, le tableau ne sera plus modifié, spécifiezIMMUTABLE
; si les données du tableau sont considérées comme ayant un ordre de rencontre, spécifiezORDERED
). La méthodeArrays.spliterator(long[], int, int)
peut souvent être utilisée à la place, qui renvoie un séparateur qui signaleSIZED
,SUBSIZED
,IMMUTABLE
etORDERED
.
Notez (encore) l'utilisation incohérente de la caractéristique IMMUTABLE
. Il est à nouveau traité comme ayant à garantir l'absence de toute modification, alors qu'en même temps, Arrays.spliterator
Et à son tour Arrays.stream
Et LongStream.of(…)
rapporteront le IMMUTABLE
caractéristique, même par spécification, sans pouvoir garantir que l'appelant ne modifiera pas son tableau. À moins que nous considérions que définir un élément ne soit pas une modification structurelle, mais ensuite, la distinction entière redevient absurde, car les tableaux ne peuvent pas être structurellement modifiés.
Et il ne spécifiait clairement aucune caractéristique NONNULL
. Bien qu'il soit évident que les valeurs primitives ne peuvent pas être null
et que les classes Spliterator.Abstract<Primitive>Spliterator
Injectent invariablement une caractéristique NONNULL
, le séparateur renvoyé par Spliterators.spliterator(long[],int,int,int)
ne n'hérite pas de Spliterator.AbstractLongSpliterator
.
La mauvaise chose est que cela ne peut pas être corrigé sans changer les spécifications, la bonne chose est que cela n'a aucune conséquence de toute façon.
Donc, si nous ignorons tout problème avec CONCURRENT
, IMMUTABLE
ou NONNULL
, qui n'a aucune conséquence, nous avons
SIZED
et skip
& limit
. Il s'agit d'un problème bien connu, résultant de la façon dont skip
et limit
ont été implémentés par l'API Stream. D'autres implémentations sont imaginables. Cela s'applique également à la combinaison d'un flux infini avec un limit
, qui devrait avoir une taille prévisible, mais compte tenu de l'implémentation actuelle, ne l'a pas.
Combinaison de Stream.concat(…)
avec Stream.empty()
. Il semble raisonnable qu'un flux vide n'impose pas des contraintes sur l'ordre des résultats. Mais le comportement de Stream.concat(…)
de libérer la commande quand une seule entrée n'a pas de commande, est discutable. Notez qu'être trop agressif en ce qui concerne la commande n'est pas nouveau, voir ce Q&A concernant un comportement qui était d'abord considéré comme intentionnel, mais qui a ensuite été corrigé aussi tard que Java 8, mise à jour 60. Peut-être que Stream.concat
aurait aussi dû être discuté en ce moment…
Le comportement de .boxed()
est facile à expliquer. Lorsqu'il a été implémenté naïvement comme .mapToObj(Long::valueOf)
, il perdra simplement toutes les connaissances, car mapToObj
ne peut pas supposer que le résultat est toujours trié ou distinct. Mais cela a été corrigé avec Java 9. Là, LongStream.range(0,10).boxed()
a SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT
Caractéristiques, en conservant toutes les caractéristiques qui sont pertinentes pour l'implémentation.