L’équipe Java a déployé des efforts considérables pour supprimer les obstacles à la programmation fonctionnelle dans Java 8. En particulier, les modifications apportées aux collections java.util permettent d’enchaîner les transformations en opérations très rapides. Compte tenu de la qualité de leur travail d’ajout de fonctions de première classe et de méthodes fonctionnelles sur les collections, pourquoi ont-ils complètement échoué à fournir des collections immuables, voire des interfaces de collection immuables?
Sans modifier aucun code existant, l'équipe Java pourrait à tout moment ajouter des interfaces immuables identiques à celles qui sont mutables, moins les méthodes "set", et en faire s'étendre les interfaces existantes, comme suit:
ImmutableIterable
____________/ |
/ |
Iterable ImmutableCollection
| _______/ / \ \___________
| / / \ \
Collection ImmutableList ImmutableSet ImmutableMap ...
\ \ \_________|______________|__________ |
\ \___________|____________ | \ |
\___________ | \ | \ |
List Set Map ...
Bien sûr, des opérations comme List.add () et Map.put () renvoient actuellement une valeur booléenne ou précédente pour la clé donnée pour indiquer si l'opération a réussi ou échoué. Les collections immuables doivent traiter ces méthodes comme des usines et renvoyer une nouvelle collection contenant l'élément ajouté - ce qui est incompatible avec la signature actuelle. Mais cela pourrait être résolu en utilisant un nom de méthode différent comme ImmutableList.append () ou .addAt () et ImmutableMap.putEntry (). Les avantages de travailler avec des collections immuables l'emporteraient sur la verbosité qui en résulterait et le système de types éviterait les erreurs d'appeler la mauvaise méthode. Avec le temps, les anciennes méthodes pourraient être obsolètes.
Victoires des collections immuables:
- Simplicité - le raisonnement sur le code est plus simple lorsque les données sous-jacentes ne changent pas.
- Documentation - Si une méthode prend une interface de collection immuable, vous savez qu'elle ne modifiera pas cette collection. Si une méthode retourne une collection immuable, vous savez que vous ne pouvez pas la modifier.
- Concurrence - les collections immuables peuvent être partagées en toute sécurité entre les threads.
En tant que personne ayant goûté à des langues supposées immuables, il est très difficile de retourner dans le Far West avec une mutation rampante. Les collections de Clojure (séquence abstraite) ont déjà tout ce que les collections de Java 8 fournissent, ainsi que leur immuabilité (bien que l'utilisation de mémoire et de temps supplémentaires soient dus à des listes chaînées synchronisées plutôt qu'à des flux). Scala possède à la fois des collections modifiables et immuables avec un ensemble complet d'opérations. Bien que ces opérations soient ardentes, appeler .iterator vous donne une vue paresseuse (et il existe d'autres moyens de les évaluer paresseusement). Je ne vois pas comment Java peut continuer à rivaliser sans collections immuables.
Quelqu'un peut-il m'indiquer l'histoire ou la discussion à ce sujet? C'est sûrement public quelque part.
la source
const
collectionsCollections.unmodifiable*()
. mais ne les traitez pas comme immuables quand ils ne le sont pasImmutableList
dans ce diagramme, les gens peuvent-ils passer dans un mutableList
? Non, c'est une très mauvaise violation de LSP.Réponses:
Parce que les collections immuables nécessitent absolument un partage pour être utilisables. Sinon, chaque opération place une liste entière dans le tas quelque part. Les langages totalement immuables, comme Haskell, génèrent une quantité incroyable de déchets sans optimisations ni partage agressifs. Avoir une collection utilisable uniquement avec <50 éléments ne vaut pas la peine d’être inséré dans la bibliothèque standard.
De plus, les collections immuables ont souvent des implémentations fondamentalement différentes de celles de leurs homologues mutables. Considérons par exemple
ArrayList
, une immuable efficaceArrayList
ne serait pas un tableau du tout! Il devrait être implémenté avec un arbre équilibré avec un grand facteur de ramification, Clojure utilise 32 IIRC. Faire en sorte que les collections mutables soient "immuables" en ajoutant simplement une mise à jour fonctionnelle est un bug de performance au même titre qu'une fuite de mémoire.De plus, le partage n'est pas viable en Java. Java fournit trop de crochets illimités à la mutabilité et à l'égalité de référence pour que le partage ne soit "qu'une optimisation". Si vous pouviez modifier un élément d'une liste et vous rendre compte que vous venez de modifier un élément dans les 20 autres versions de cette liste, cela vous déplairait probablement un peu.
Cela exclut également d'énormes classes d'optimisations très vitales pour une immutabilité efficace, le partage, la fusion de flux, etc. (Cela ferait un bon slogan pour les évangélistes FP)
la source
String
dans unStringBuffer
ordinateur, de les manipuler, puis de les copier dans une nouvelle immuableString
. Utiliser un tel modèle avec des ensembles et des listes peut être aussi efficace que d’utiliser des types immuables conçus pour faciliter la production d’instances légèrement modifiées, mais qui pourraient tout de même être meilleurs ...Une collection mutable n'est pas un sous-type d'une collection immuable. Au lieu de cela, les collections mutables et immuables sont des descendants frères de collections lisibles. Malheureusement, les concepts de "lisible", "lecture seule" et "immuable" semblent s'estomper, même s'ils signifient trois choses différentes.
Une classe de base ou un type d'interface de collection lisible garantit la possibilité de lire des éléments et ne fournit aucun moyen direct de modifier la collection, mais ne garantit pas que le code recevant la référence ne puisse pas le transtyper ni le manipuler de manière à permettre sa modification.
Une interface de collection en lecture seule n'inclut pas de nouveaux membres, mais ne devrait être implémentée que par une classe qui promet qu'il n'y a aucun moyen de manipuler une référence à celle-ci de manière à muter la collection ou à recevoir une référence à quelque chose cela pourrait le faire. Cependant, cela ne promet pas que la collection ne sera pas modifiée par quelque chose d'autre faisant référence aux éléments internes. Notez qu'une interface de collection en lecture seule peut ne pas être capable d'empêcher l'implémentation par des classes mutables, mais peut spécifier que toute implémentation, ou classe dérivée d'une implémentation, qui permet une mutation doit être considérée comme une implémentation "illégitime" ou le dérivé d'une implémentation. .
Une collection immuable est une collection qui contiendra toujours les mêmes données tant que toute référence à celles-ci existe. Toute implémentation d'une interface immuable qui ne renvoie pas toujours les mêmes données en réponse à une requête particulière est interrompue.
Il est parfois utile d'avoir des types de collecte mutables et immuables fortement associés à la fois la mise en œuvre qui ou dériver du même type « lisible », et d'avoir le type lisible comprennent
AsImmutable
,AsMutable
et desAsNewMutable
méthodes. Une telle conception peut permettre au code qui veut faire persister les données d’une collection d’appelerAsImmutable
; cette méthode fera une copie défensive si la collection est modifiable, mais ignore la copie si elle est déjà immuable.la source
int
, avec méthodesgetLength()
etgetItemAt(int)
.ReadableIndexedIntSequence
, on pourrait produire une instance d'un type immuable basé sur un tableau en copiant tous les éléments dans un tableau, mais supposons qu'une implémentation particulière renvoie simplement 16777216 pour la longueur et((long)index*index)>>24
pour chaque élément. Ce serait une séquence immuable légitime d'entiers, mais la copier dans un tableau serait une énorme perte de temps et de mémoire.asImmutable
d'une instance de la séquence définie ci-dessus par rapport au coût de la construction une copie immuable sauvegardée sur un tableau]. Je dirais qu'il est probablement préférable de définir des interfaces à ces fins que d'essayer d'utiliser des approches ad hoc; IMHO, la plus grande raison ...Java Collections Framework offre la possibilité de créer une version en lecture seule d'une collection au moyen de six méthodes statiques dans la classe java.util.Collections :
Comme quelqu'un l'a souligné dans les commentaires sur la question initiale, les collections renvoyées ne peuvent pas être considérées comme immuables, car même si les collections ne peuvent pas être modifiées (aucun membre ne peut être ajouté ou supprimé d'une telle collection), les objets réels référencés par la collection peuvent être modifiés si leur type d'objet le permet.
Toutefois, ce problème persisterait, que le code retourne un seul objet ou une collection d'objets non modifiable. Si le type autorise la mutation de ses objets, alors cette décision a été prise dans la conception du type et je ne vois pas en quoi une modification de la JCF pourrait modifier cela. Si l'immuabilité est importante, les membres d'une collection doivent être d'un type immuable.
la source
immutableList
méthodes d'usine qui renverraient une enveloppe en lecture seule autour de la copie d'un document transmis. liste sauf si la liste transmise était déjà immuable . Il serait facile de créer de tels types définis par l'utilisateur, mais pour un problème: il n'y aurait aucun moyen pour lajoesCollections.immutableList
méthode de reconnaître qu'elle n'aurait pas besoin de copier l'objet renvoyé parfredsCollections.immutableList
.C'est une très bonne question. J'apprécie l'idée que, de tout le code écrit en java et utilisé sur des millions d'ordinateurs du monde entier, chaque jour et vingt-quatre heures sur vingt-quatre, il faut gaspiller environ la moitié du nombre total de cycles d'horloge pour ne faire que des copies de sécurité des collections être retourné par des fonctions. (Et ramasser les ordures quelques millisecondes après leur création.)
Un pourcentage de programmeurs java est conscient de l'existence de la
unmodifiableCollection()
famille de méthodes de laCollections
classe, mais même parmi elles, beaucoup ne s'en préoccupent pas.Et je ne peux pas leur en vouloir: une interface qui prétend être en lecture-écriture mais jettera un
UnsupportedOperationException
si vous faites l'erreur d'invoquer l'une de ses méthodes d'écriture est tout à fait un mal!Maintenant, une interface comme celle
Collection
qui manqueraitadd()
,remove()
etclear()
méthodes ne serait pas une interface "ImmutableCollection"; ce serait une interface "UnmodifiableCollection". En fait, il ne pourrait jamais y avoir d'interface "ImmutableCollection", car l'immutabilité est une nature d'une implémentation, pas une caractéristique d'une interface. Je sais, ce n'est pas très clair. laissez-moi expliquer.Supposons que quelqu'un vous remette une telle interface de collection en lecture seule; Est-il prudent de le passer à un autre thread? Si vous saviez avec certitude qu'il s'agit d'une collection véritablement immuable, la réponse serait «oui». Malheureusement, comme il s’agit d’une interface, vous ne savez pas comment elle est mise en œuvre, la réponse doit donc être un non : pour autant que vous sachiez, il peut s’agir d’une vue non modifiable (pour vous) d’une collection qui est en fait modifiable, (comme ce que vous obtenez avec
Collections.unmodifiableCollection()
), donc tenter de le lire pendant qu'un autre thread le modifie entraînerait la lecture de données corrompues.Donc, ce que vous avez essentiellement décrit est un ensemble d’interfaces de collection non "immuables", mais "non modifiables". Il est important de comprendre que "non modifiable" signifie simplement que quiconque ayant une référence à une telle interface est empêché de modifier la collection sous-jacente, et ce simplement parce que l'interface manque de méthodes de modification, et non pas parce que la collection sous-jacente est nécessairement immuable. La collection sous-jacente pourrait très bien être mutable; vous n'avez aucune connaissance et aucun contrôle sur cela.
Pour avoir des collections immuables, il faudrait que ce soit des classes , pas des interfaces!
Ces classes de collections immuables doivent être définitives. Ainsi, lorsque vous recevez une référence à une telle collection, vous savez avec certitude qu’elle se comportera comme une collection immuable, peu importe ce que vous-même, ou quiconque faire avec.
Donc, pour avoir un ensemble complet de collections en java (ou tout autre langage impératif déclaratif), nous aurions besoin des éléments suivants:
Un ensemble d' interfaces de collection non modifiables .
Un ensemble d' interfaces de collection mutables , étendant celles non modifiables.
Un ensemble de classes de collection mutables implémentant les interfaces mutables et, par extension, les interfaces non modifiables.
Un ensemble de classes de collections immuables , implémentant les interfaces non modifiables, mais généralement transmises sous forme de classes afin de garantir l’immuabilité
J'ai mis en œuvre tout ce qui précède pour le plaisir, et je les utilise dans des projets, et ils fonctionnent à merveille.
La raison pour laquelle ils ne font pas partie de l'exécution de Java est probablement due au fait que cela aurait été trop / trop complexe / trop difficile à comprendre.
Personnellement, je pense que ce que j'ai décrit ci-dessus ne suffit même pas; une dernière chose qui semble nécessaire est un ensemble d’interfaces et de classes mutables pour l’ immutabilité structurelle . (Ce qui peut simplement s'appeler "Rigide" car le préfixe "StructurallyImmutable" est trop long.)
la source
List<T> add(T t)
- toutes les méthodes "mutateur" doivent renvoyer une nouvelle collection qui reflète le changement. 2. Pour le meilleur ou pour le pire, les interfaces représentent souvent un contrat en plus d'une signature. Sérialisable est l'une de ces interfaces. De même, Comparable nécessite que vous implémentiez correctement votrecompareTo()
méthode pour fonctionner correctement et être parfaitement compatible avecequals()
ethashCode()
.add()
. Mais je suppose que si des méthodes mutatrices devaient être ajoutées aux classes immuables, elles devraient alors renvoyer également des classes immuables. Donc, s'il y a un problème qui se cache là-bas, je ne le vois pas.Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?
Supposons que quelqu'un vous transmette une instance d'une interface de collection mutable. Est-il prudent d’invoquer une méthode? Vous ne savez pas que l'implémentation ne fonctionne pas en boucle, ne génère pas d'exception ou ignore complètement le contrat de l'interface. Pourquoi avoir un double standard spécifiquement pour les collections immuables?SortedSet
en sous-classant l'ensemble avec une implémentation non conforme. Ou en passant une incohérenteComparable
. Presque tout peut être cassé si vous voulez. J'imagine que c'est ce que @Doval voulait dire par "deux poids deux mesures".Les collections immuables peuvent être profondément récursives, comparées les unes aux autres, et pas excessivement inefficaces si l'égalité d'objet est définie par secureHash. C'est ce qu'on appelle une forêt de merkle. Il peut s'agir d'une collection ou de parties de celles-ci, par exemple un arbre AVL (auto-équilibré) pour une carte triée.
À moins que tous les objets java de ces collections aient un identifiant unique ou une chaîne de caractères à hachage, la collection n'a rien à hacher pour se nommer de manière unique.
Exemple: sur mon ordinateur portable 4x1,6 GHz, je peux exécuter 200 Ko sha256 par seconde de la plus petite taille pouvant être insérée dans un cycle de hachage (jusqu'à 55 octets), par rapport à 500 Ko HashMap ou 3M dans une table de hachage de longue durée. 200 Ko / log (collectionSize) Le nombre de nouvelles collections par seconde est suffisamment rapide pour permettre l’intégrité des données et l’évolutivité globale anonyme.
la source
Performance. Les collections de par leur nature peuvent être très volumineuses. Copier 1000 éléments dans une nouvelle structure avec 1001 éléments au lieu d'insérer un seul élément est tout simplement horrible.
La concurrence. Si vous avez plusieurs threads en cours d'exécution, ils peuvent vouloir obtenir la version actuelle de la collection et non la version passée il y a 12 heures lorsque le thread a démarré.
Espace de rangement. Avec des objets immuables dans un environnement multi-thread, vous pouvez vous retrouver avec des dizaines de copies du "même" objet à différents moments de son cycle de vie. Peu importe pour un objet Calendrier ou Date, mais quand il s'agit d'une collection de 10 000 widgets, cela vous tuera.
la source