¿Por qué Java 8 no incluye colecciones inmutables?

123

El equipo de Java ha realizado una gran cantidad de trabajo eliminando las barreras a la programación funcional en Java 8. En particular, los cambios en las Colecciones java.util hacen un gran trabajo de encadenar transformaciones en operaciones de flujo muy rápido. Teniendo en cuenta lo bien que han hecho al agregar funciones y métodos funcionales de primera clase en las colecciones, ¿por qué no han podido proporcionar colecciones inmutables o incluso interfaces de colección inmutables?

Sin cambiar ningún código existente, el equipo de Java podría, en cualquier momento, agregar interfaces inmutables que sean iguales a las mutables, sin los métodos de "conjunto" y hacer que las interfaces existentes se extiendan desde ellos, como esto:

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

Claro, operaciones como List.add () y Map.put () actualmente devuelven un valor booleano o anterior para la clave dada para indicar si la operación se realizó correctamente o no. Las colecciones inmutables tendrían que tratar dichos métodos como fábricas y devolver una nueva colección que contenga el elemento agregado, que es incompatible con la firma actual. Pero eso podría solucionarse utilizando un nombre de método diferente como ImmutableList.append () o .addAt () e ImmutableMap.putEntry (). La verbosidad resultante estaría más que compensada por los beneficios de trabajar con colecciones inmutables, y el sistema de tipos evitaría los errores de llamar al método incorrecto. Con el tiempo, los métodos antiguos podrían quedar obsoletos.

Victorias de colecciones inmutables:

  • Simplicidad: el razonamiento sobre el código es más simple cuando los datos subyacentes no cambian.
  • Documentación: si un método toma una interfaz de colección inmutable, sabes que no va a modificar esa colección. Si un método devuelve una colección inmutable, sabe que no puede modificarlo.
  • Concurrencia: las colecciones inmutables se pueden compartir de forma segura en subprocesos.

Como alguien que ha probado idiomas que suponen una inmutabilidad, es muy difícil volver al Salvaje Oeste de la mutación desenfrenada. Las colecciones de Clojure (abstracción de secuencia) ya tienen todo lo que las colecciones de Java 8 proporcionan, además de inmutabilidad (aunque tal vez usando memoria extra y tiempo debido a listas vinculadas sincronizadas en lugar de secuencias). Scala tiene colecciones mutables e inmutables con un conjunto completo de operaciones, y aunque esas operaciones son impacientes, llamar a .iterator ofrece una visión perezosa (y existen otras formas de evaluarlas perezosamente). No veo cómo Java puede seguir compitiendo sin colecciones inmutables.

¿Puede alguien indicarme la historia o discusión sobre esto? Seguramente es público en alguna parte.

    
pregunta GlenPeterson 18.12.2013 - 15:53
fuente

6 respuestas

109

Porque las colecciones inmutables absolutamente requieren que se comparta para poder usarse. De lo contrario, cada operación única coloca otra lista en el montón en algún lugar. Los idiomas que son completamente inmutables, como Haskell, generan cantidades asombrosas de basura sin optimizaciones y compartimientos agresivos. Tener una colección que solo se pueda usar con < 50 elementos no vale la pena incluir en la biblioteca estándar.

Además, las colecciones inmutables a menudo tienen implementaciones fundamentalmente diferentes que sus equivalentes mutables. Considere, por ejemplo, ArrayList , ¡un eficiente ArrayList inmutable no sería una matriz en absoluto! Debería implementarse con un árbol equilibrado con un gran factor de ramificación, Clojure utiliza 32 IIRC. Hacer que las colecciones mutables sean "inmutables" con solo agregar una actualización funcional es un error de rendimiento, tanto como lo es una pérdida de memoria.

Además, compartir no es viable en Java. Java proporciona demasiados enlaces sin restricciones a la mutabilidad y la igualdad de referencia para que compartir sea "solo una optimización". Probablemente le molestaría un poco si pudiera modificar un elemento en una lista, y darse cuenta de que acaba de modificar un elemento en las otras 20 versiones de esa lista que tenía.

Esto también descarta enormes clases de optimizaciones vitales para la inmutabilidad eficiente, el intercambio, la fusión de secuencias, lo que sea, la mutabilidad lo rompe. (Eso sería un buen eslogan para los evangelistas de FP)

    
respondido por el jozefg 18.12.2013 - 17:38
fuente
65

Una colección mutable no es un subtipo de una colección inmutable. En su lugar, las colecciones mutables e inmutables son hermanos descendientes de colecciones legibles. Desafortunadamente, los conceptos de "legible", "solo lectura" e "inmutable" parecen confundirse, aunque signifiquen tres cosas diferentes.

  • Una clase base de colección legible o un tipo de interfaz promete que uno puede leer elementos, y no proporciona ningún medio directo para modificar la colección, pero no garantiza que el código que recibe la referencia no pueda convertirlo o manipularlo de tal manera. forma de permitir la modificación.

  • Una interfaz de colección de solo lectura no incluye ningún miembro nuevo, pero solo debe ser implementada por una clase que prometa que no hay forma de manipular una referencia a ella de tal manera que se mute la colección. ni recibir una referencia a algo que pueda hacerlo. Sin embargo, no promete que la colección no será modificada por otra cosa que tenga una referencia a los elementos internos. Tenga en cuenta que una interfaz de recopilación de solo lectura puede no ser capaz de impedir la implementación por clases mutables, pero puede especificar que cualquier implementación, o clase derivada de una implementación, que permita la mutación se considerará una implementación "ilegítima" o un derivado de una implementación .

  • Una colección inmutable es aquella que siempre contendrá los mismos datos siempre que exista alguna referencia a ella. Cualquier implementación de una interfaz inmutable que no siempre devuelve los mismos datos en respuesta a una solicitud en particular se rompe.

A veces es útil tener tipos de colección mutables e inmutables fuertemente asociados que implementan o derivan del mismo tipo "legible", y para que el tipo legible incluya los métodos AsImmutable , AsMutable y AsNewMutable . Un diseño de este tipo puede permitir que el código que desea conservar los datos de una colección llame a AsImmutable ; ese método hará una copia defensiva si la colección es mutable, pero omita la copia si ya es inmutable.

    
respondido por el supercat 21.12.2013 - 09:37
fuente
13

El Java Collections Framework ofrece la posibilidad de crear una versión de solo lectura de una colección mediante seis métodos estáticos en java.util.Collections class :

Como alguien ha señalado en los comentarios a la pregunta original, las colecciones devueltas pueden no considerarse inmutables porque a pesar de que las colecciones no pueden modificarse (no se pueden agregar o eliminar miembros de dicha colección), los objetos reales a los que se hace referencia por la colección se puede modificar si su tipo de objeto lo permite.

Sin embargo, este problema se mantendría independientemente de si el código devuelve un solo objeto o una colección de objetos no modificable. Si el tipo permite que sus objetos sean mutados, entonces esa decisión se tomó en el diseño del tipo y no veo cómo un cambio en el JCF podría alterarlo. Si la inmutabilidad es importante, entonces los miembros de una colección deben ser de un tipo inmutable.

    
respondido por el Arkanon 25.12.2013 - 22:44
fuente
7

Esta es una muy buena pregunta. Disfruto la idea de que de todos los códigos escritos en java y que se ejecutan en millones de computadoras en todo el mundo, todos los días, durante todo el día, se debe desperdiciar aproximadamente la mitad de los ciclos totales del reloj haciendo nada más que hacer copias de seguridad de las colecciones que son siendo devuelto por funciones. (Y recolecte en la basura estas colecciones milisegundos después de su creación.)

Un porcentaje de programadores java es consciente de la existencia de la familia de métodos unmodifiableCollection() de la clase Collections , pero incluso entre ellos, muchos simplemente no se molestan en hacerlo.

Y no puedo culparlos: ¡una interfaz que pretende ser de lectura y escritura, pero que arrojará un UnsupportedOperationException si comete el error de invocar cualquiera de sus métodos de "escritura" es algo muy malo!

Ahora, una interfaz como Collection a la que le faltarían los métodos add() , remove() y clear() no sería una interfaz "ImmutableCollection"; sería una interfaz "UnmodifiableCollection". De hecho, nunca podría haber una interfaz "ImmutableCollection", porque la inmutabilidad es una naturaleza de una implementación, no una característica de una interfaz. Lo sé, eso no está muy claro; déjame explicarte.

Supongamos que alguien te entrega una interfaz de colección de solo lectura; ¿Es seguro pasarlo a otro hilo? Si supiera con seguridad que representa una colección verdaderamente inmutable, entonces la respuesta sería "sí"; desafortunadamente, ya que es una interfaz, no sabes cómo se implementa, por lo que la respuesta debe ser no : para todos los que sabes, puede ser una forma no modificable. (para usted) la vista de una colección que de hecho es mutable, (como lo que obtiene con Collections.unmodifiableCollection() ), por lo que intentar leer en ella mientras otra cadena la está modificando, resultaría en la lectura de datos corruptos.

Entonces, lo que esencialmente ha descrito es un conjunto de interfaces de colección no "Inmutables", sino "No modificables". Es importante entender que "No modificable" simplemente significa que se impide a quien tenga una referencia a dicha interfaz modificar la colección subyacente, y se evita simplemente porque la interfaz carece de cualquier método de modificación, no porque la colección subyacente sea necesariamente inmutable. La colección subyacente bien podría ser mutable; no tienes conocimiento ni control sobre eso.

Para tener colecciones inmutables, tendrían que ser classes , ¡no interfaces!

Estas clases de colección inmutable tendrían que ser definitivas, de modo que cuando se le proporcione una referencia a dicha colección, estará seguro de que se comportará como una colección inmutable, independientemente de lo que usted o cualquier otra persona que tenga una referencia a eso, podría hacer con él.

Entonces, para tener un completo conjunto de colecciones en java, (o cualquier otro lenguaje imperativo declarativo), necesitaríamos lo siguiente:

  1. Un conjunto de no modificable colección interfaces.

  2. Un conjunto de mutable colección interfaces , extendiendo las no modificables.

  3. Un conjunto de mutable clases que implementan las interfaces mutables, y por extensión también las interfaces no modificables.

  4. Un conjunto de inmutable colección clases , implementando las interfaces no modificables, pero en su mayoría se transmiten como clases, para garantizar la inmutabilidad.

He implementado todo lo anterior por diversión, y los estoy usando en proyectos, y funcionan como un encanto.

La razón por la que no forman parte del tiempo de ejecución de Java es probablemente porque se pensó que esto sería demasiado / demasiado complejo / demasiado difícil de entender.

Personalmente, creo que lo que describí anteriormente no es suficiente; Una cosa más que parece ser necesaria es un conjunto de interfaces mutables & Clases para inmutabilidad estructural . (Lo que puede llamarse simplemente "Rígido" porque el prefijo "StructurallyImmutable" es demasiado largo).

    
respondido por el Mike Nakis 04.06.2015 - 17:07
fuente
2

Las colecciones inmutables pueden ser profundamente recursivas, comparadas entre sí, y no injustificadamente ineficientes si la igualdad de objetos se realiza mediante secureHash. Esto se llama un bosque de merkle. Puede ser por colección o dentro de partes de ellos como un árbol AVL (binario de equilibrio automático) para un mapa ordenado.

A menos que todos los objetos java de estas colecciones tengan un ID único o alguna cadena de bits para el hash, la colección no tiene nada que hacer un hash para que se nombre solo.

Ejemplo: en mi portátil de 4x1.6ghz, puedo ejecutar 200K sha256s por segundo del tamaño más pequeño que cabe en 1 ciclo de hash (hasta 55 bytes), en comparación con 500K HashMap ops o 3M ops en un hashtable de longs. Las nuevas colecciones por segundo de 200K / log (collectionSize) son lo suficientemente rápidas para algunas cosas donde la integridad de los datos y la escalabilidad global anónima son importantes.

    
respondido por el Ben Rayfield 04.09.2016 - 21:52
fuente
-3

Rendimiento. Las colecciones por su naturaleza pueden ser muy grandes. Copiar 1000 elementos a una nueva estructura con 1001 elementos en lugar de insertar un solo elemento es simplemente horrible.

Concurrencia. Si tiene varios subprocesos en ejecución, es posible que desee obtener la versión actual de la colección y no la versión que se aprobó hace 12 horas cuando comenzó el subproceso.

Almacenamiento. Con objetos inmutables en un entorno de múltiples subprocesos, puede terminar con docenas de copias del "mismo" objeto en diferentes puntos de su ciclo de vida. No importa para un calendario o un objeto de fecha, pero cuando se trata de una colección de 10,000 widgets, esto te matará.

    
respondido por el James Anderson 19.12.2013 - 02:15
fuente

Lea otras preguntas en las etiquetas