Inmutabilidad Extra

Definición de inmutabilidad

En programación, la inmutabilidad se refiere a la propiedad de un objeto cuyo estado no puede ser modificado una vez que ha sido creado. En Java, esto se logra haciendo que las instancias de una clase sean “inmutables”, lo que significa que sus campos no pueden cambiar después de que se hayan inicializado.

Clase inmutable

Una clase inmutable es simplemente aquella cuyas instancias no pueden ser modificadas una vez que su información ha sido definida. No habrá ninguna modificación a la misma durante su ciclo de vida. Para ello se usa el modificador final, y estos pasos a seguir:

  • No proporcionar ningún método que permita modificar el objeto (como p. ej. los setter).
  • Usar visibilidad privada para los atributos
  • Definir los atributos como final Aunque puede no ser estrictamente necesario, es conveniente, ya que también previene la modificación interna.
  • Declarar la clase como final.

Ejemplos:

public final class Persona {
    private final String nombre;
    private final int edad;

    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public String getNombre() {
        return nombre;
    }

    public int getEdad() {
        return edad;
    }
}

final class Circulo { 

  final private Punto origen;
  final private double radio; 

  public Circulo(Punto p, double r) {
    origen = new Punto(p); 
    radio=r; 
  }
  public Punto getOrigen() {
    return new Punto(origen);
  }
  public double getRadio() { return radio; }
  
}

Métodos estáticos de Colecciones

  • List.of(…), Set.of(…) y Map.of(…).
  • Por su parte Java 10 añadió un nuevo método estático copyOf dentro de las interfaces List, Set y Map para facilitar la creación de colecciones inmutables a partir de una colección dada:
List.copyOf(lista);
Set.copyOf(conjunto);
Map.copyOf(mapa);

Usando Arrays.asList()

Arrays.asList() El método devuelve una lista de tamaño fijo respaldada por la array especificada. Dado que una array no se puede modificar estructuralmente, es imposible agregar elementos a la lista o eliminar elementos de ella. La lista arrojará un UnsupportedOperationException si se realiza alguna operación de cambio de tamaño en él. Sin embargo los objetos de la lista si que se pueden modificar. Ejemplo:

import java.util.Arrays;
import java.util.List;

class Main
{
	public static void main(String[] args)
	{
		String[] lang = new String[] { "C", "C++", "Java" };

		List<String> fixedLengthList = Arrays.asList(lang);

		try {
			// inmutable -> UnsupportedOperationException
			fixedLengthList.add("C#");

			System.out.println("List  : " + fixedLengthList);
		}
		catch (UnsupportedOperationException ex) {
			System.out.println("java.lang.UnsupportedOperationException");
		}

		// los objetos si se pueden modificar
		fixedLengthList.set(1, "Go");

		lang[2] = "JS";

        // cualquier cambio se ve reflejado, estamos con referencias
		System.out.println("Array : " + Arrays.toString(lang));
		// lambda
		fixedLengthList.forEach(e ->System.out.print(e.toString()+ " "));
		
	}
}

Crear un String si es inmutable

Una variable tipo String es inmutable. Por si queréis saber la razón -> enlace Es decir siempre que se crea uno y luego se modifica implica crear uno nuevo. Es decir lo que hace realmente hace Java es crear un String nuevo. Hay que pensar que un String es un array de caracteres, y como bien sabéis un array es una estructura estática.

String saludo = "Hola"; // Creamos la cadena con un contenido

saludo = saludo + " mundo"; // y le agregamos después una subcadena

Implica la creación de un nuevo objeto String como resultado. Cuando en la segunda línea extendemos el contenido original de la variable saludo, lo que ocurre es que se libera el objeto String original, el creado en la primera sentencia, y se crea otro nuevo para alojar el resultado de la operación. Dado que el tipo String es inmutable (no podemos modificar su contenido), cualquier operación de modificación sobre una variable de este tipo, como puede ser concatenar una cadena a otra o usar métodos como toUpperCase(), replace() o similares, implica la creación de un nuevo objeto String como resultado.

Si queremos crear programas más eficientes (en el caso en que se usen muchos Strings), la mejor solución es usar un “generador” de Strings mutable. Una posibilidad es usar StringBuilder:

StringBuilder saludo = new StringBuilder("Hola");
saludo.append(" mundo");

Si el programa va a tener concurrencia, lo más seguro será utilizar StringBuffer: enlace

No modificable

No modificable no nos permiten agregar, eliminar o cambiar objetos de una colección, pero cada objeto puede cambiar. Es decir no implica inmutabilidad

Se han agregado en Collections, metodos que nos aporta colecciones no modificables por ejemplo: Collections.unmodifiableSet() o Collections.unmodifiableCollection()

Ejemplo:

class Persona {
    private String nombre;
    private int edad;

    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public String getNombre() {
        return nombre;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }
}

public class Main {
    public static void main(String[] args) {
        // Crear un conjunto mutable
        Set<Persona> conjuntoMutable = new HashSet<>();
        conjuntoMutable.add(new Persona("Juan", 30));
        conjuntoMutable.add(new Persona("María", 25));
        
        // Crear un conjunto no modificable a partir del conjunto mutable
        Set<Persona> conjuntoNoModificable = Collections.unmodifiableSet(conjuntoMutable);

        // Intentar modificar un objeto en el conjunto no modifiable
        Persona juan = conjuntoNoModificable.iterator().next();
        juan.setEdad(40); // Esto modifica el objeto dentro del conjunto

        // Imprimir el conjunto no modifiable para mostrar que refleja los cambios en el objeto
        System.out.println("Contenido del conjunto no modifiable después de modificar un objeto:");
        for (Persona persona : conjuntoNoModificable) {
            System.out.println(persona.getNombre() + " - " + persona.getEdad());
        }

         // Intentar agregar un elemento al conjunto no modifiable
        try {
        	conjuntoNoModificable.add(new Persona("Carlos", 22)); // Esto lanzará UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.out.println("No se puede agregar elementos al conjunto no modifiable.");
        }
    }
}

Pura inmutabilidad

Crear clases inmutables + Estructura de datos no modificable

Diferencia entre no modificable e inmutable

Es sútil pero importante:

  • Una colección No modificable es como una vista, por tanto indirectamente puede ser modificada por otra refencia a la misma colección modicable (tal como habéis podido comprobar en el anterior ejemplo). Es simplemente una vista de sólo lectura de una colección. No se puede alterar directamente, pero si en el origen cambia también se verá alterado.

  • Sin embargo, una colección Inmutable, se considera una copia de sólo lectura de otra colección y no se puede modificar. Si la colección de origen cambia, la copia inmutable no se verá afectada.

Ejemplo:

ArrayList<String> arrayInicial = new ArrayList();
arrayInicial.add("Hola");
arrayInicial.add("Adios");
arrayInicial.add("Ciao");

List<String> arr = List.copyOf(arrayInicial);
// Coleccion inmutable 
arr.forEach(e -> System.out.println(e));
// Modificamos la colecion original
arrayInicial.add("Adeus");
System.out.println("*".repeat(5));
// Volvemos a recorrer la coleccion y no hay ningún cambio
arr.forEach(e -> System.out.println(e));
// La original
System.out.println("*".repeat(5));
arrayInicial.forEach(e -> System.out.println(e));

Conclusión

En muchos casos necesitaremos “elementos” que conserven los datos, por ejemplo mejorar la eficiencia. Situaciones como:

  • Patrones de diseño.
  • Programación funcional.
  • Programación concurrente (multihilo).

Bibliografía

https://luizcostatech.medium.com/java-lists-unmodifiable-vs-immutable-d670afc58106 https://www.baeldung.com/java-stream-immutable-collection https://gelopfalcon.medium.com/programaci%C3%B3n-funcional-con-java-inmutabilidad-ddab9d40df4a https://javaconceptoftheday.com/java-9-immutable-collections/ https://www.techiedelight.com/es/mutable-unmodifiable-immutable-empty-list-java/