Buscar..


Observaciones

Los objetos inmutables tienen un estado fijo (no establecedores), por lo que todo el estado debe ser conocido en el momento de la creación del objeto.

Aunque no es técnicamente necesario, es una buena práctica hacer que todos los campos sean final . Esto hará que la clase inmutable sea segura para subprocesos (véase Java Concurrency in Practice, 3.4.1).

Los ejemplos muestran varios patrones que pueden ayudar a lograr esto.

Creación de una versión inmutable de un tipo mediante copia defensiva.

Algunos tipos y clases básicas en Java son fundamentalmente mutables. Por ejemplo, todos los tipos de matriz son mutables, y también lo son las clases como java.util.Data . Esto puede ser incómodo en situaciones donde se requiere un tipo inmutable.

Una forma de lidiar con esto es crear un envoltorio inmutable para el tipo mutable. Aquí hay una envoltura simple para una matriz de enteros

public class ImmutableIntArray {
    private final int[] array;
    
    public ImmutableIntArray(int[] array) {
        this.array = array.clone();
    }

    public int[] getValue() {
        return this.clone();
    }
}

Esta clase funciona utilizando una copia defensiva para aislar el estado mutable (el int[] ) de cualquier código que pueda mutarlo:

  • El constructor usa clone() para crear una copia distinta de la matriz de parámetros. Si la persona que llama al constructor posteriormente cambió la matriz de parámetros, no afectaría el estado de ImmutableIntArray .

  • El método getValue() también usa clone() para crear la matriz que se devuelve. Si la persona que llama cambiara la matriz de resultados, no afectaría el estado de ImmutableIntArray .

También podríamos agregar métodos a ImmutableIntArray para realizar operaciones de solo lectura en la matriz envuelta; por ejemplo, obtener su longitud, obtener el valor en un índice particular, y así sucesivamente.

Tenga en cuenta que un tipo de envoltorio inmutable implementado de esta manera no es compatible con el tipo original. No puedes simplemente sustituir el primero por el segundo.

La receta para una clase inmutable.

Un objeto inmutable es un objeto cuyo estado no se puede cambiar. Una clase inmutable es una clase cuyas instancias son inmutables por diseño e implementación. La clase de Java que se presenta más comúnmente como un ejemplo de inmutabilidad es java.lang.String .

El siguiente es un ejemplo estereotipado:

public final class Person {
    private final String name;
    private final String ssn;     // (SSN == social security number)

    public Person(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }

    public String getName() {
        return name;
    }
   
    public String getSSN() {
        return ssn;
    }
}

Una variante de esto es declarar el constructor como private y proporcionar un método de fábrica public static .


La receta estándar para una clase inmutable es la siguiente:

  • Todas las propiedades deben establecerse en el (los) constructor (es) o en los métodos de fábrica.
  • No debería haber setters.
  • Si es necesario incluir configuradores por razones de compatibilidad de interfaz, no deberían hacer nada o lanzar una excepción.
  • Todas las propiedades deben ser declaradas como private y final .
  • Para todas las propiedades que son referencias a tipos mutables:
    • la propiedad debe inicializarse con una copia en profundidad del valor pasado a través del constructor, y
    • El captador de la propiedad debe devolver una copia profunda del valor de la propiedad.
  • La clase debe declararse como final para evitar que alguien cree una subclase mutable de una clase inmutable.

Un par de otras cosas a tener en cuenta:

  • La inmutabilidad no impide que el objeto sea anulable; Por ejemplo, null puede asignarse a una variable de String .
  • Si las propiedades de una clase inmutable se declaran como final , las instancias son intrínsecamente seguras para subprocesos. Esto hace que las clases inmutables sean un buen bloque de construcción para implementar aplicaciones de subprocesos múltiples.

Fallas típicas de diseño que evitan que una clase sea inmutable.

Usando algunos configuradores, sin configurar todas las propiedades necesarias en el (los) constructor (es)

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

Es fácil mostrar que la clase Person no es inmutable:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

Para solucionarlo, simplemente elimine setSurname() y refactorice el constructor de la siguiente manera:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

No marcando variables de instancia como privadas y finales.

Echa un vistazo a la siguiente clase:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

El siguiente fragmento de código muestra que la clase anterior no es inmutable:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

Para solucionarlo, simplemente marque la propiedad del nombre como private y final .


Exponer un objeto mutable de la clase en un captador.

Echa un vistazo a la siguiente clase:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names clase de Names parece inmutable a primera vista, pero no es como lo muestra el siguiente código:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Esto sucedió porque un cambio en la Lista de referencia devuelto por getNames() puede modificar la lista real de Names .

Para solucionar este problema, simplemente evite devolver referencias que hagan referencia a objetos mutables de la clase, ya sea haciendo copias defensivas, de la siguiente manera:

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

o diseñando captadores de manera que solo se devuelvan otros objetos y primitivas inmutables , de la siguiente manera:

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

Inyectar el constructor con objeto (s) que pueden modificarse fuera de la clase inmutable

Esta es una variación de la falla anterior. Echa un vistazo a la siguiente clase:

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

Como los Names clase antes, también la clase NewNames parece inmutable a primera vista, pero no lo es, de hecho, el siguiente fragmento de código demuestra lo contrario:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Para corregir esto, como en el defecto anterior, simplemente haga copias defensivas del objeto sin asignarlo directamente a la clase inmutable, es decir, el constructor se puede cambiar de la siguiente manera:

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

Dejar que los métodos de la clase sean anulados

Echa un vistazo a la siguiente clase:

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Person clase de Person parece inmutable a primera vista, pero supongamos que se define una nueva subclase de Person :

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

ahora la mutabilidad de Person (im) puede ser explotada a través del polimorfismo usando la nueva subclase:

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

Para solucionar este problema, o bien marcar la clase como final por lo que no se puede ampliar o declarar la totalidad de su constructor (s) como private .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow