Hola a todos!
Hoy, su atención está invitada a la traducción de un artículo cuidadosamente escrito sobre uno de los problemas básicos de Java: la mutabilidad y cómo afecta la estructura de las estructuras de datos y cómo trabajar con ellas. El material está tomado del blog de Nicolai Parlog, cuyo brillante estilo literario realmente intentamos mantener en la traducción. El propio Nicholas se caracteriza notablemente por un
extracto del blog de la compañía JUG.ru sobre Habré; citemos este pasaje en su totalidad:

Nikolay Parlog es un tipo de medios que revisa las características de Java. Pero él no es de Oracle al mismo tiempo, por lo que las críticas son sorprendentemente francas y comprensibles. A veces, después de ellos, alguien es despedido, pero rara vez. Nikolay hablará sobre el futuro de Java, lo que estará en la nueva versión. Es bueno hablando de tendencias y, en general, del gran mundo. Es un compañero erudito y muy leído. Incluso los informes simples son agradables de escuchar, todo el tiempo aprendes algo nuevo. Además, Nicholas sabe más allá de lo que está diciendo. Es decir, puede llegar a cualquier informe y disfrutarlo, incluso si este no es su tema en absoluto. El esta enseñando. Escribió "The Java Module System" para Manning Publishing House, mantiene blogs sobre desarrollo de software en codefx.org y ha estado involucrado durante mucho tiempo en varios proyectos de código abierto. Puede ser contratado en la conferencia, es un profesional independiente. Es cierto, un profesional independiente muy caro. Aquí está el informe .
Leemos y votamos. A quién le gusta especialmente la publicación, también le recomendamos que mire los comentarios de los lectores sobre la publicación original.
La volatilidad es mala, ¿verdad? En consecuencia, la
inmutabilidad es buena . Las principales estructuras de datos en las que la inmutabilidad es especialmente fructífera son las colecciones: en Java es una lista (
List
), un conjunto (
Set
) y un diccionario (
Map
). Sin embargo, aunque el JDK viene con colecciones inmutables (¿o no modificables?), El sistema de tipos no sabe nada al respecto. No hay
ImmutableList
en el JDK, y este tipo de guayaba me parece completamente inútil. Pero por que? ¿Por qué no simplemente agregar
Immutable...
a esta mezcla y no decir que debería ser?
¿Qué es una colección inmutable?
En la terminología de JDK, los significados de las palabras "
inmutable " e "
inmodificable " han cambiado en los últimos años. Inicialmente, "no modificable" se llamaba una instancia que no permitía la mutabilidad (mutabilidad): en respuesta a los métodos cambiantes, lanzó
UnsupportedOperationException
. Sin embargo, podría cambiarse de otra manera, tal vez porque era solo una envoltura alrededor de una colección mutable. Estas vistas se reflejan en los métodos
Collections::unmodifiableList
,
unmodifiableSet
y
unmodifiableMap
, así como en
su JavaDoc .
Inicialmente, el término "
inmutable " se refiere a colecciones devueltas por los
métodos de fábrica de colecciones Java 9 . Las colecciones en sí no podrían modificarse de ninguna manera (sí, hay
reflexión , pero no cuenta), por lo tanto, parece que justifican su nombre. Por desgracia, a menudo surge la confusión debido a esto. Supongamos que hay un método que muestra todos los elementos de una colección inmutable en la pantalla: ¿siempre dará el mismo resultado? ¿Eh? O no?
Si no respondiste inmediatamente que
no , significa que acabas de ver exactamente qué confusión es posible aquí. Una "
Colección inmutable de agentes secretos ": parece que suena bastante similar a una "
colección inmutable de agentes secretos inmutables "
, pero estas dos entidades pueden no ser idénticas. Una colección inmutable no puede editarse mediante operaciones de inserción / eliminación / limpieza, etc., pero si los agentes secretos son mutables (aunque los rasgos de los personajes en las películas de espías son tan malos que realmente no lo creen), entonces eso no significa que que toda la colección de agentes secretos es inmutable. Por lo tanto, ahora hay un cambio hacia el nombramiento de tales colecciones
no modificables , en lugar de
inmutables , que está consagrado en la
nueva edición de JavaDoc .
Las colecciones inmutables discutidas en este artículo pueden contener elementos mutables.
Personalmente, no me gusta tal revisión de la terminología. En mi opinión, el término "colección inmutable" solo debe significar que la colección en sí no es susceptible de cambio, pero no debe caracterizar los elementos que contiene. En este caso, hay un punto positivo más: el término "inmutabilidad" en el ecosistema de Java no se convierte en una completa tontería.
De una forma u otra, en este artículo hablaremos sobre
colecciones inmutables , donde ...
- Las instancias contenidas en la colección se determinan en la etapa de diseño.
- Estas copias: una cantidad uniforme, ni reducir ni agregar
- No se hacen declaraciones con respecto a la mutabilidad de estos elementos.
Detengámonos en el hecho de que ahora practicaremos agregando colecciones inmutables. Para ser precisos, una lista inmutable. Todo lo que se dirá sobre las listas es igualmente aplicable a colecciones de otros tipos.
¡Comenzando a agregar colecciones inmutables!
ImmutableList
interfaz
ImmutableList
y hagámosla, en relación con
List
, uh ... ¿qué? Supertipo o subtipo? Detengámonos en la primera opción.

Bellamente,
ImmutableList
no
ImmutableList
métodos
ImmutableList
, por lo que usarlo siempre es seguro, ¿verdad? Entonces? No señor.
List<Agent> agents = new ArrayList<>();
Este ejemplo muestra que es posible transferir una lista no totalmente inmutable a la API, cuya operación puede basarse en la inmutabilidad y, en consecuencia, anular todas las garantías de que un nombre de este tipo puede insinuar. Aquí hay una receta para usted que podría llevar al desastre.
OK, entonces
ImmutableList
extiende
List
. Tal vez?

Ahora, si la API espera una lista inmutable, recibirá dicha lista, pero hay dos inconvenientes:
- Las listas inmutables aún deberían ofrecer métodos de modificación (como se definen en el supertipo), y la única implementación posible arrojará una excepción
ImmutableList
instancias de ImmutableList
también ImmutableList
instancias de List
, y cuando se asignan a dicha variable, se pasan como tal argumento o se devuelven de este tipo, es lógico suponer que la mutabilidad está permitida.
Por lo tanto, resulta que solo puede usar
ImmutableList
localmente, porque pasa los bordes de la API como una
List
, lo que requiere un nivel de precaución sobrehumano o explota en tiempo de ejecución. No es tan malo como una
List
extiende una
List
ImmutableList
, pero esa solución aún está lejos de ser ideal.
Esto es exactamente lo que tenía en mente cuando dije que el tipo
ImmutableList
de Guava es prácticamente inútil. Este es un gran ejemplo de código, muy confiable cuando se trabaja con listas locales inmutables (es por eso que lo uso activamente), pero, recurriendo a él, es muy fácil ir más allá de la ciudadela compilada garantizada e inexpugnable, cuyas paredes estaban compuestas de tipos inmutables, y solo en esto Los tipos inmutables pueden revelar completamente su potencial. Esto es mejor que nada, pero ineficiente como una solución JDK.
Si
ImmutableList
no puede extender la
List
, y la solución aún no funciona, ¿cómo se supone que haga que todo funcione?
La inmutabilidad es una característica
El problema que encontramos en los primeros dos intentos de agregar tipos inmutables fue nuestra idea errónea de que la inmutabilidad es simplemente la ausencia de algo: tomamos una
List
, eliminamos el código cambiante, obtenemos una
List
ImmutableList
. Pero, de hecho, todo esto no funciona de esa manera.
Si simplemente eliminamos los métodos de modificación de la
List
, obtenemos una lista de solo lectura. O, si se adhiere a la terminología formulada anteriormente, puede denominarse
UnmodifiableList
; aún puede cambiar, pero no la cambiará.
Ahora podemos agregar dos cosas más a esta imagen:
- Podemos hacerlo mutable agregando métodos apropiados
- Podemos hacerlo inmutable agregando garantías apropiadas.
La inmutabilidad no es la ausencia de mutabilidad, sino una garantía de que no habrá cambios.
En este caso, es importante comprender que en ambos casos estamos hablando de características completas: la inmutabilidad no es la ausencia de cambios, sino una garantía de que no habrá cambios. Una característica existente puede no ser necesariamente algo que se use para bien, también puede garantizar que algo malo no sucederá en el código; en este caso, piense, por ejemplo, en la seguridad de los subprocesos.
Obviamente, la mutabilidad y la inmutabilidad entran en conflicto entre sí y, por lo tanto, no podemos usar simultáneamente las dos jerarquías de herencia mencionadas anteriormente. Los tipos heredan capacidades de otros tipos, por lo que no importa cómo los corte, si uno de los tipos hereda del otro, contendrá ambas características.
Tan bien,
List
e
ImmutableList
no pueden extenderse entre sí. Pero nos trajeron aquí trabajando con
UnmodifiableList
, y realmente resulta que ambos tipos tienen la misma API, solo lectura, lo que significa que deberían extenderla.

Aunque no llamaría a las cosas exactamente estos nombres, la jerarquía de este tipo es razonable. En Scala, por ejemplo, esto se
hace prácticamente . La diferencia es que el supertipo compartido, que llamamos
UnmodifiableList
, define métodos mutables que devuelven una colección modificada y dejan intacto el original. Por lo tanto, una lista inmutable resulta ser
persistente y le da a una variante mutable dos conjuntos de métodos de modificación: heredados para recibir copias modificadas y la suya propia para los cambios en el lugar.
¿Qué hay de Java? ¿Es posible modernizar tal jerarquía agregando nuevos supertipos y hermanos?
¿Es posible mejorar las colecciones no modificables e inmutables?
Por supuesto, no hay problema en agregar los
ImmutableList
e
ImmutableList
y crear la jerarquía de herencia descrita anteriormente. El problema es que a corto y mediano plazo será prácticamente inútil. Déjame explicarte.
Lo
ImmutableList
tener
UnmodifiableList
,
ImmutableList
y
List
como tipos; en este caso, las API podrán expresar claramente lo que necesitan y lo que ofrecen.
public void payAgents(UnmodifiableList<Agent> agents) {
Sin embargo, a menos que comience el proyecto desde cero, es probable que tenga dicha funcionalidad, y se verá más o menos así:
Esto no es bueno, porque para que las nuevas colecciones que acabamos de presentar sean útiles, ¡debemos trabajar con ellas! (uf) Lo anterior recuerda el código de la aplicación, por lo tanto, la refactorización
ImmutableList
UnmodifiableList
e
ImmutableList
, y puede implementarlo, como se mostró en la lista anterior. Esto puede ser una gran parte del trabajo, junto con la confusión cuando necesita organizar la interacción del código antiguo y actualizado, pero al menos parece factible.
¿Qué pasa con los frameworks, las bibliotecas y el propio JDK? Todo aquí parece sombrío. Un intento de cambiar el parámetro o el tipo de retorno de
List
a
ImmutableList
dará lugar a
incompatibilidad con el código fuente , es decir. el código fuente existente no se compilará con la nueva versión, ya que estos tipos no están relacionados entre sí. Del mismo modo, cambiar el tipo de retorno de
List
a un nuevo supertype
UnmodifiableList
dará como resultado errores de compilación.
Con la introducción de nuevos tipos, será necesario hacer cambios y recompilar en todo el ecosistema.
Sin embargo, incluso si expandimos el tipo de parámetro de
List
a
UnmodifiableList
, nos encontraremos con un problema, ya que dicho cambio causa
incompatibilidad a nivel de bytecode . Cuando el código fuente llama a un método, el compilador convierte esta llamada a bytecode que hace referencia al método de destino mediante:
- El nombre de la clase cuya instancia se declara el objetivo
- Nombre del método
- Tipos de parámetros del método
- Tipo de retorno de método
Cualquier cambio en el parámetro o tipo de retorno del método hará que el bytecode indique una firma incorrecta al referirse al método; Como resultado, se producirá un error
NoSuchMethodError
durante la ejecución. Si el cambio que realiza es compatible con el código fuente, por ejemplo, si está hablando de reducir el tipo de retorno o expandir el tipo del parámetro, entonces la recompilación debería ser suficiente. Sin embargo, con cambios de gran alcance, por ejemplo, con la introducción de nuevas colecciones, todo no es tan simple: para consolidar dichos cambios, debe volver a compilar todo el ecosistema de Java. Este es un negocio perdido.
La única forma concebible de aprovechar tales nuevas colecciones sin romper la compatibilidad es duplicar cada método existente con un nuevo nombre, cambiar la API y luego marcar la versión anterior como no deseable. ¿Te imaginas lo monumental y virtualmente infinita que sería una tarea así?
Reflexion
Por supuesto, los tipos de colecciones inmutables son una gran cosa que me encantaría tener, pero es poco probable que veamos algo así en el JDK. Las implementaciones competentes de
List
e
ImmutableList
nunca
ImmutableList
expandirse entre sí (de hecho, ambas extienden el mismo tipo de lista
UnmodifiableList
, que es de solo lectura), lo que dificulta la integración de estos tipos en las API existentes.
Fuera del contexto de cualquier relación específica entre tipos, cambiar las firmas de métodos existentes siempre está lleno de problemas, ya que dichos cambios son incompatibles con el código de bytes. Cuando se introducen, se requiere al menos una compilación, y este es un cambio devastador que afectará a todo el ecosistema de Java.
Por lo tanto, creo que nada de esto sucederá, nunca y nunca.