Programación funcional con Java: inmutabilidad

Gerardo Lopez Falcón
7 min readDec 18, 2020

--

La inmutabilidad es uno de los conceptos centrales de la programación funcional. Los lenguajes de programación funcionales reales lo soportan por diseño, a nivel de lenguaje. Pero en Java y la mayoría de los lenguajes no funcionales, necesitamos diseñarlo e implementarlo nosotros mismos, a nivel de código.

La idea fundamental detrás de la inmutabilidad es simple: si queremos cambiar una estructura de datos, necesitamos crear una nueva copia de ella con los cambios, en lugar de mutar la estructura de datos original.

Por qué es importante la inmutabilidad

¿Por qué deberíamos tomar medidas adicionales para cambiar un valor? Porque con estructuras de datos inmutables, obtenemos muchas ventajas:

- Los datos no cambiarán a nuestras espaldas.
- Una vez verificado, será válido indefinidamente.
- Sin efectos secundarios ocultos.
- No hay objetos medio inicializados, moviéndose a través de diferentes métodos hasta que esté completo. Esto desacoplará los métodos y, con suerte, los convertirá en funciones puras.
- Seguridad del hilo: no más condiciones de carrera. Si una estructura de datos nunca cambia, podemos usarla de forma segura en varios subprocesos.
- Mejor capacidad de almacenamiento en caché.
- Posibles técnicas de optimización, como transparencia referencial y memorización.

Estado de la inmutabilidad de Java

En el momento de escribir este artículo, Java no es un lenguaje con inmutabilidad por diseño. Pero nos proporciona todas las piezas necesarias para crear nosotros mismos estructuras de datos inmutables.

Tipos inmutables incorporados

Hay varios tipos inmutables disponibles en el JDK. Aquí hay algunos que probablemente ya encontraste:

- Contenedores primitivos (java.lang.Integer, java.lang.Boolean, etc.)
- java.lang.String (excepto un código hash calculado de forma diferida)
- Tipos matemáticos (java.math.BigInteger, java.math.BigDecimal)
- Enumeraciones
- java.util.Locale
- java.util.UUID
- API de fecha / hora de Java 8

La palabra clave “final”

La palabra clave final` se utiliza para definir una variable que solo se puede asignar una vez. Puede parecer inmutabilidad al principio, pero en realidad no lo es en un sentido más amplio.

En lugar de crear una estructura de datos inmutable, solo la referencia a ella será inmutable. Esto solo garantiza que una variable siempre apunte al mismo punto en la memoria. No dice nada sobre el contenido de la memoria en sí.

Cómo volverse inmutable

Existen efectivamente dos formas de crear estructuras de datos inmutables sin registros de Java 14: hacerlo nosotros mismos o usar frameworks.

Inmutables DIY

Piense en un JavaBean típico:

Los JavaBeans están diseñados con getters y setters, por lo que se pueden usar en varios escenarios. Muchos frameworks, como los diseñadores de ORM o GUI, se basan en la reflexión. Analizan clases para identificar captadores y definidores, sin usar campos directamente a través de la reflexión y, por lo tanto, son en su mayoría incompatibles con diseños inmutables.

Otro peligro pueden ser los efectos secundarios. Los setters pueden hacer más que simplemente establecer un valor único: pueden rastrear un estado sucio o establecer varios valores. Esta no es la mejor práctica, pero sucede todo el tiempo.

Este diseño de JavaBean en particular no es una regla obligatoria o fija. Es una convención que usamos a menudo gracias al desarrollo basado en hábitos, y no porque realmente necesitemos ese diseño en particular.

Rompamos con el diseño tradicional y hagámoslo inmutable:

Eso fue realmente simple y obtuvimos un código más corto y conciso. Ahora nuestra estructura de datos no cambiará inesperadamente una vez que se inicialice.

A menos que agreguemos más campos con un tipo mutable …

Cada campo de una estructura de datos inmutable tiene que ser inmutable. Y todos los campos de los tipos también deben ser inmutables. Con un solo tipo mutable, tal vez incluso profundamente anidado, todos los beneficios de ser inmutable serán destruidos.

Las colecciones también son tipos problemáticos. Las colecciones no modificables han existido desde Java 7.

Con Java 9, se agregaron métodos de fábrica fáciles de usar. Pero, al igual que final, solo significa que la colección en sí no es modificable, no los objetos contenidos. Así que asegúrese de guardar solo estructuras de datos ya inmutables en ellos.

Patrón de constructor

Nos aseguramos de que todos nuestros campos sean inmutables, sin importar cuán profundamente anidados estén. Pero todavía tenemos un problema: cómo construir la estructura de datos.

Todos los campos deben establecerse en la inicialización, por lo que necesitamos un constructor con todos los campos. ¿Qué pasa si no necesitamos que todos los campos estén configurados? ¿Deberíamos proporcionar varios constructores o métodos de construcción estáticos? ¿Y cómo nos aseguramos de que todos los campos obligatorios estén configurados y el objeto resultante sea válido?

Utilizando el patrón del constructor.

Necesitamos una clase adicional que encapsule el complejo proceso de construir una estructura de datos inmutable. Al separar el proceso de creación de la representación, obtenemos un control preciso sobre el proceso de ensamblaje de la estructura de datos e incluso podemos agregar validación. También significa que introducimos un constructor mutable, por lo que podemos tener una estructura de datos inmutable.

Los campos active y lastLogin son opcionales; por defecto, un usuario no está activo hasta que se indique explícitamente y nunca haya iniciado sesión. O proporcionamos los argumentos cada vez que creamos un usuario o agregamos constructores adicionales para que coincidan con las diferentes combinaciones de argumentos.

Pero cuanto más complejo se vuelve el tipo, más constructores necesitaríamos. En cambio, creamos un constructor:

Ahora podemos crear un usuario con una API fluida:

User user = new UserBuilder(“john@doe.com”, “pa$$w0rd”).active(true) .build();

O la podemos construir en múltiples pasos:

Creamos con éxito un tipo inmutable y un constructor correspondiente. Pero es un montón de repetición y puede ser engorroso hacerlo cada vez que presentamos un nuevo tipo. Cada fragmento de código que escribimos puede introducir errores.

Es por eso que usar frameworks puede ser un verdadero alivio, ya que ahorra tener que escribir todo el código y brinda muchos métodos de conveniencia y validación.

Frameworks / Third-parties

En lugar de escribir todo el código nosotros mismos, podemos emplear frameworks para concentrarnos en diseñar y modelar las estructuras de datos. El código generado es menos propenso a errores y las estructuras de datos resultantes pueden ser más concisas.

Inmutables

Como se describe a sí mismo el proyecto “Immutables”:

Java annotation processors to generate simple, safe and consistent value objects. Do not repeat yourself, try Immutables, the most comprehensive tool in this field!

Crear un tipo inmutable es tan simple como crear un tipo abstracto, ya sea una clase abstracta o una interfaz, y agregar las anotaciones correctas:

El procesador de anotaciones genera la implementación real detrás de escena, que incluye:

  • Soporte de serialización.
  • Clase de constructor.
  • Validación de requisitos.
  • Métodos prácticos para copiar, etc.
  • equals, hashCode y toString

Creemos un usuario inmutable con el constructor proporcionado:

Gracias al uso de la anotación @ Value.Default y el tipo Optional, obtenemos automáticamente la validación al llamar a build (). Si no se cumplen todos los requisitos, se lanza una IllegalStateException.

Proyecto Lombok

Project Lombok es una herramienta integral que intenta reducir la cantidad de código estándar de Java habitual, como captadores y definidores, comprobaciones nulas, equals / hashCode o toString.

Y por supuesto inmutables:

La anotación @Value equivale a usar estas anotaciones bastante autoexplicativas:

@Adquiridor
@FieldDefaults (makeFinal = true, level = AccessLevel.PRIVATE)
@AllArgsConstructor
@EqualsAndHashCode
@Encadenar
El único que falta es @Builder, que agrega User.builder () a nuestro ejemplo.

El proyecto es una gran herramienta para reducir la repetición. Pero para construir estructuras de datos flexibles e inmutables, recomendaría Immutables. Ambos proyectos tienen objetivos diferentes, y creo que es un hecho que un proyecto llamado Immutables tiene mejores características con respecto a la inmutabilidad.

Conclusión

La inmutabilidad es una buena idea para muchos tipos de proyectos de software, no solo para idiomas sin soporte integrado. Incluso para el almacenamiento de datos, puede ofrecer ventajas, como el sistema de control de versiones Git, por ejemplo. Utiliza confirmaciones inmutables para garantizar la integridad.

Pero eso no significa que todos los problemas se puedan resolver con estructuras de datos inmutables. El estado mutable no es algo malo en sí mismo. Solo tenemos que estar seguros de cuándo usarlo y ser conscientes de los peligros del cambio de estado.

Una desventaja, al menos al principio, es adaptar su base de código a las nuevas estructuras de datos. Cambiarlos no es un reemplazo inmediato. Pero nos ayuda a crear un flujo de datos más predecible, con cambios de estado fácilmente observables.

Mi ejemplo del mundo real

Decidí comenzar a usar inmutables para simplificar la administración de sesiones. Detectar una sesión sucia puede ser difícil con estructuras de datos no inmutables profundamente anidadas. Y persistir en cada sesión con cada solicitud crea una gran cantidad de gastos generales innecesarios.

Gracias a la nueva estructura de datos de sesión, que solo contiene tipos inmutables como campos, es mucho más sencillo detectar una sesión modificada: si se actualiza un campo, la sesión está sucia. No más cambios en los objetos anidados a nuestras espaldas.

Pero no me detuve en la gestión de sesiones. He comenzado a reemplazar cada vez más tipos con inmutables, para eliminar muchos errores sutiles, como estados no válidos o condiciones de carrera.

Si una estructura de datos no cambia demasiado a lo largo de su vida, intentamos que sea inmutable.

--

--

Gerardo Lopez Falcón
Gerardo Lopez Falcón

Written by Gerardo Lopez Falcón

Google Developer Expert & Sr Software Engineer & DevOps &. Soccer Fan

No responses yet