web-dev-qa-db-fra.com

Pourquoi Java 8 n'inclut-il pas les collections immuables?

L'équipe Java a accompli une tonne de travail formidable pour éliminer les obstacles à la programmation fonctionnelle dans Java 8. En particulier, les modifications apportées aux collections Java.util font excellent travail de chaîner les transformations en opérations en flux très rapide. Compte tenu de la qualité du travail qu'ils ont fait en ajoutant des fonctions et des méthodes fonctionnelles de première classe sur les collections, pourquoi ont-ils complètement échoué à fournir des collections immuables ou même des interfaces de collection immuables?

Sans changer aucun code existant, l'équipe Java Java pourrait à tout moment ajouter des interfaces immuables qui sont les mêmes que les mutables, moins les méthodes "set" et étendre les interfaces existantes à partir de celles-ci, comme cette:

                  ImmutableIterable
     ____________/       |
    /                    |
Iterable        ImmutableCollection
   |    _______/    /          \   \___________
   |   /           /            \              \
 Collection  ImmutableList  ImmutableSet  ImmutableMap  ...
    \  \  \_________|______________|__________   |
     \  \___________|____________  |          \  |
      \___________  |            \ |           \ |
                  List            Set           Map ...

Bien sûr, des opérations telles que 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 devraient 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 contourné en utilisant un nom de méthode différent comme ImmutableList.append () ou .addAt () et ImmutableMap.putEntry (). La verbosité qui en résulterait serait plus que compensée par les avantages de travailler avec des collections immuables, et le système de type empêcherait les erreurs d'appeler la mauvaise méthode. Au fil du temps, les anciennes méthodes pourraient devenir obsolètes.

Victoires de 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 va pas modifier cette collection. Si une méthode renvoie 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 qui a goûté aux langues qui supposent l'immuabilité, il est très difficile de retourner dans le Far West de la mutation rampante. Les collections de Clojure (abstraction de séquence) ont déjà tout ce que Java 8 collections fournissent, plus l'immuabilité (bien que peut-être utiliser de la mémoire supplémentaire et du temps en raison de listes liées synchronisées au lieu de flux). Scala possède des collections mutables et immuables avec un ensemble complet d'opérations, et bien que ces opérations soient désireuses, appeler .iterator donne une vue paresseuse (et il existe d'autres façons 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.

136
GlenPeterson

Parce que les collections immuables nécessitent absolument le partage pour être utilisables. Sinon, chaque opération place une toute autre liste quelque part dans le tas. Des langages entièrement immuables, comme Haskell, génèrent des quantités étonnantes de déchets sans optimisations agressives ni partage. Avoir une collection qui n'est utilisable qu'avec <50 éléments ne vaut pas la peine d'être placée dans la bibliothèque standard.

De plus, les collections immuables ont souvent des implémentations fondamentalement différentes de leurs homologues mutables. Considérez par exemple ArrayList, un ArrayList immuable efficace ne serait pas du tout un tableau! Il doit être implémenté avec un arbre équilibré avec un grand facteur de ramification, Clojure utilise 32 IIRC. Rendre les collections mutables "immuables" en ajoutant simplement une mise à jour fonctionnelle est un bogue de performance tout autant 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 rendre le partage "juste une optimisation". Cela vous irait probablement un peu si vous pouviez modifier un élément dans une liste et vous rendre compte vient de modifier un élément dans les 20 autres versions de cette liste que vous aviez.

Cela exclut également d'énormes classes d'optimisations très vitales pour une immuabilité efficace, le partage, la fusion de flux, vous l'appelez, la mutabilité le brise. (Cela ferait un bon slogan pour FP évangélistes)

115
Daniel Gratzer

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", "en lecture seule" et "immuable" semblent se confondre, même s'ils signifient trois choses différentes.

  • Une classe de base de collection ou un type d'interface lisible promet que l'on peut 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 peut pas le transtyper ou le manipuler de manière à permettre la modification.

  • Une interface de collection en lecture seule n'inclut aucun nouveau membre, mais ne doit ê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 ni à 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 qui a une référence aux internes. Notez qu'une interface de collecte en lecture seule ne peut pas 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 autorise la mutation doit être considérée comme une implémentation "illégitime" ou un dérivé d'une implémentation .

  • Une collection immuable est une collection qui contiendra toujours les mêmes données tant qu'il y aura une référence à elle. Toute implémentation d'une interface immuable qui ne renvoie pas toujours les mêmes données en réponse à une demande particulière est rompue.

Il est parfois utile d'avoir des types de collections mutables et immuables fortement associés qui implémentent ou dérivent du même type "lisible", et d'avoir le type lisible inclure AsImmutable, AsMutable et AsNewMutable méthodes. Une telle conception peut permettre au code qui souhaite conserver les données d'une collection d'appeler AsImmutable; cette méthode crée une copie défensive si la collection est modifiable, mais ignore la copie si elle est déjà immuable.

79
supercat

Le 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 de la question d'origine, les collections retournées peuvent ne 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é à une telle collection), les objets réels référencés par la collection peut être modifié si leur type d'objet le permet.

Toutefois, ce problème persisterait indépendamment du fait que le code renvoie un seul objet ou une collection d'objets non modifiables. Si le type permet à ses objets d'être mutés, alors cette décision a été prise dans la conception du type et je ne vois pas comment un changement dans le JCF pourrait changer cela. Si l'immuabilité est importante, les membres d'une collection doivent être de type immuable.

15
Arkanon

C'est une très bonne question. J'apprécie l'idée que de tout le code écrit en Java et fonctionnant sur des millions d'ordinateurs partout dans le monde, tous les jours, 24 heures sur 24, environ la moitié du nombre total de cycles d'horloge doit être gaspillé en faisant rien que faire des copies de sécurité des collections qui sont retournées par les fonctions (et ramasser ces collections des millisecondes après leur création).

Un pourcentage de Java sont conscients de l'existence de la famille de méthodes unmodifiableCollection() de la classe Collections, mais même parmi eux, beaucoup ne font que ' t dérange pas avec elle.

Et je ne peux pas leur en vouloir: une interface qui prétend être en lecture-écriture mais lancera un UnsupportedOperationException si vous faites l'erreur d'invoquer l'une de ses méthodes 'd'écriture' est une chose assez mauvaise à avoir!

Maintenant, une interface comme Collection à laquelle il manquerait les méthodes add(), remove() et clear() ne serait pas une interface "ImmutableCollection"; ce serait une interface "UnmodifiableCollection". En fait, il ne pourrait jamais y avoir d'interface "ImmutableCollection", car l'immuabilité est une nature d'une implémentation, pas une caractéristique d'une interface. Je sais, ce n'est pas très clair; laisse-moi expliquer.

Supposons que quelqu'un vous remette une telle interface de collecte en lecture seule; est-il sûr de le passer à un autre thread? Si vous saviez avec certitude qu'elle représente une collection vraiment immuable, alors la réponse serait "oui"; malheureusement, comme il s'agit d'une interface, vous ne savez pas comment elle est implémentée, donc la réponse doit être --- no : pour tout ce que vous savez, 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 essayez de lire à partir d'elle alors qu'un autre thread est sa modification entraînerait la lecture de données corrompues.

Donc, ce que vous avez décrit essentiellement est un ensemble d'interfaces de collection non "immuables", mais "non modifiables". Il est important de comprendre que "non modifiable" signifie simplement que quiconque a une référence à une telle interface est empêché de modifier la collection sous-jacente, et ils sont empêchés simplement parce que l'interface n'a aucune méthode de modification, pas parce que la collection sous-jacente est nécessairement immuable. La collection sous-jacente pourrait très bien être mutable; vous n'en avez aucune connaissance et aucun contrôle.

Pour avoir des collections immuables, il faudrait qu'elles soient classes , pas des interfaces!

Ces classes de collection immuables devraient être définitives, de sorte que 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, ou toute autre personne qui y a référence, pourriez faire avec.

Ainsi, afin d'avoir un complet ensemble de collections en Java, (ou tout autre langage impératif déclaratif), nous aurions besoin des éléments suivants:

  1. Un ensemble de non modifiable collection interfaces.

  2. Un ensemble de mutable collection interfaces, étendant les non modifiables.

  3. Un ensemble de mutable collection classes implémentant les interfaces mutables, et par extension également les interfaces non modifiables.

  4. Un ensemble de immuable collection classes, implémentant les interfaces non modifiables, mais principalement transmises en tant que 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 comme un charme.

La raison pour laquelle ils ne font pas partie du runtime Java est probablement parce que l'on pensait que ce serait trop/trop complexe/trop difficile à comprendre.

Personnellement, je pense que ce que j'ai décrit ci-dessus n'est même pas suffisant; une autre chose qui semble nécessaire est un ensemble d'interfaces et de classes mutables pour l'immuabilité structurelle . (Ce qui peut simplement être appelé "Rigide" car le préfixe "StructurallyImmutable" est trop long.)

8
Mike Nakis

Les collections immuables peuvent être profondément récursives les unes par rapport aux autres et pas déraisonnablement inefficaces si l'égalité des objets est assurée par secureHash. C'est ce qu'on appelle une forêt de merkle. Il peut être par collection ou à l'intérieur de certaines parties comme un arbre AVL (binaire à équilibrage automatique) pour une carte triée.

À moins que tous les objets Java dans ces collections aient un identifiant unique ou une chaîne de bits à hacher, la collection n'a rien à hacher pour se nommer de manière unique.

Exemple: Sur mon ordinateur portable 4x1.6ghz, je peux exécuter 200K sha256s par seconde de la plus petite taille qui tient dans 1 cycle de hachage (jusqu'à 55 octets), par rapport aux opérations HashMap 500K ou aux opérations 3M dans une table de hachage longue. 200K/log (collectionSize) de nouvelles collections par seconde est assez rapide pour certaines choses où l'intégrité des données et l'évolutivité globale anonyme sont importantes.

2
Ben Rayfield