web-dev-qa-db-fra.com

Comprendre en profondeur les caractéristiques du séparateur

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 LongStreams, IntStreams et DoubleStreams?

Q3: LongStream.range(,) vs LongStream.range(,).boxed()

  • LongRange.range(,): SUBSIZED, IMMUTABLE, NONNULL, SIZED, ORDERED, SORTED, DISTINCT
  • LongStream.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.

57
Tet

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 ou CONCURRENT devrait avoir une politique documentée (par exemple, lancer ConcurrentModificationException) 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 et SUBSIZED. 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écifiez IMMUTABLE; si les données du tableau sont considérées comme ayant un ordre de rencontre, spécifiez ORDERED). La méthode Arrays.spliterator(long[], int, int) peut souvent être utilisée à la place, qui renvoie un séparateur qui signale SIZED, SUBSIZED, IMMUTABLE et ORDERED.

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.

35
Holger