¿Clase inmutable?

Respuestas:

135

¿Qué es un objeto inmutable?

Un objeto inmutable es aquel que no cambiará de estado después de crear una instancia.

¿Cómo hacer inmutable un objeto?

En general, un objeto inmutable se puede hacer definiendo una clase que no tiene ninguno de sus miembros expuestos y no tiene ningún setter.

La siguiente clase creará un objeto inmutable:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

Como se puede ver en el ejemplo anterior, el valor de ImmutableIntsolo se puede establecer cuando se crea una instancia del objeto, y al tener solo un getter ( getValue), el estado del objeto no se puede cambiar después de la instancia.

Sin embargo, se debe tener cuidado de que todos los objetos a los que hace referencia el objeto también sean inmutables, o podría ser posible cambiar el estado del objeto.

Por ejemplo, permitir una referencia a una matriz o ArrayListque se obtenga a través de un captador permitirá que el estado interno cambie al cambiar la matriz o colección:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

El problema con el código anterior es que ArrayListse puede obtener getListy manipular, lo que lleva a que el estado del objeto en sí sea alterado, por lo tanto, no inmutable.

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

Una forma de solucionar este problema es devolver una copia de una matriz o colección cuando se llama desde un captador:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

¿Cuál es la ventaja de la inmutabilidad?

La ventaja de la inmutabilidad viene con la concurrencia. Es difícil mantener la corrección en objetos mutables, ya que varios subprocesos podrían estar intentando cambiar el estado del mismo objeto, lo que provocaría que algunos subprocesos vean un estado diferente del mismo objeto, según el tiempo de las lecturas y escrituras en dicho objeto.

Al tener un objeto inmutable, uno puede asegurarse de que todos los subprocesos que miran al objeto verán el mismo estado, ya que el estado de un objeto inmutable no cambiará.

coobird
fuente
5
La seguridad de los subprocesos (importante para la concurrencia) no es la única ventaja de la inmutabilidad; también significa que no tiene que hacer copias defensivas de objetos y previene errores porque no puede modificar por error objetos que no se supone que deben ser modificados.
Jesper
7
En lugar de devolver una copia de la lista, también puede return Collections.unmodifiableList(list);devolver una vista de solo lectura en la lista.
Jesper
18
La clase también se tiene que hacer final. De lo contrario, puede ampliarse con un método de establecimiento (u otros tipos de métodos de mutación y campos mutables).
Abhinav Sarkar
6
@AbhinavSarkar No tiene por qué serlo finalsi la clase solo tiene privatecampos, ya que no se puede acceder a ellos desde las subclases.
icza
3
La clase @icza tiene que ser final, porque tenemos un método getter público, podemos extender la clase y anular el método getter y luego cambiar el campo a nuestro modo ... para que ya no sea inmutable
sunil
19

Además de las respuestas ya dadas, recomendaría leer sobre inmutabilidad en Effective Java, 2nd Ed., Ya que hay algunos detalles que son fáciles de pasar por alto (por ejemplo, copias defensivas). Además, Effective Java 2nd Ed. es una lectura obligada para todos los desarrolladores de Java.

Fabian Steeg
fuente
3
Este es el recurso exacto para mirar. Los beneficios que se mencionan van desde un código menos propenso a errores hasta la seguridad de los subprocesos.
gpampara
6

Haces una clase inmutable como esta:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

    public String getName() { return this.name; } 

    // No setter;
}

Los siguientes son los requisitos para hacer que una clase Java sea inmutable:

  • La clase debe declararse como final(para que no se puedan crear clases secundarias)
  • Los miembros de la clase deben declararse como final(para que no podamos cambiar su valor después de la creación del objeto)
  • Escriba métodos Getter para todas las variables en él para obtener valores de miembros
  • Sin métodos Setters

Las clases inmutables son útiles porque
: Son seguras para subprocesos.
- También expresan algo profundo sobre su diseño: "No se puede cambiar esto". Cuando se aplica, es exactamente lo que necesita.

duffymo
fuente
5

La inmutabilidad se puede lograr principalmente de dos formas:

  • usar finalatributos de instancia para evitar la reasignación
  • usando una interfaz de clase que simplemente no permite ninguna operación que pueda modificar lo que está dentro de su clase (solo getters y no setters

Las ventajas de la inmutabilidad son las suposiciones que puede hacer sobre estos objetos:

  • obtienes la regla de ningún efecto secundario (que es muy popular en los lenguajes de programación funcionales) y te permite usar objetos en un entorno concurrente más fácilmente, ya que sabes que no se pueden cambiar de una manera atómica o no atómica cuando están utilizado por muchos hilos
  • Las implementaciones de lenguajes pueden tratar estos objetos de manera diferente, colocándolos en zonas de memoria que se utilizan para datos estáticos, lo que permite un uso más rápido y seguro de estos objetos (esto es lo que sucede dentro de la JVM para cadenas)
Jack
fuente
Inmutable no es lo mismo que igual libre de efectos secundarios. Por ejemplo, un objeto inmutable podría producir efectos secundarios como el registro en un archivo. Es un poco inexacto decir que hacer que un objeto sea inmutable también lo hace libre de efectos secundarios.
Grundlefleck
1
@Grundleflek, creo que esto puede estar partiendo los pelos. La clase no es inmutable si modifica un archivo de registro como parte de su contrato y ese archivo de registro es accesible para otras clases. Si el archivo de registro está oculto para otras clases, y no es parte del contrato de la clase, entonces la clase es efectivamente inmutable y, para todos los efectos, está libre de efectos secundarios. La introducción (sin fuente) en la página de efectos secundarios de Wikipedia dice "... se dice que una expresión tiene un efecto secundario si, además de producir un valor, también modifica algún estado o tiene una interacción observable con funciones de llamada".
Jeff Axelrod
3

Las clases inmutables no pueden reasignar valores después de crear una instancia. El constructor asigna valores a sus variables privadas. Hasta que el objeto se vuelva nulo, los valores no se pueden cambiar debido a la falta de disponibilidad de métodos de establecimiento.

para ser inmutable debe satisfacer lo siguiente,

  • Todas las variables deben ser privadas .
  • No se proporcionan métodos mutantes (setters).
  • Evite la invalidación del método haciendo que la clase sea final (inmutabilidad fuerte) o los métodos finales (inmutabilidad de la semana).
  • Clone profundamente si contiene clases no primitivas o mutables.

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

Ventajas: Los objetos inmutables contienen sus valores inicializados hasta que mueren.

java-inmutable-clases-nota-corta

Kandy
fuente
2

Las clases inmutables son aquellas cuyos objetos no se pueden cambiar después de la creación.

Las clases inmutables son útiles para

  • Propósito de almacenamiento en caché
  • Entorno concurrente (ThreadSafe)
  • Difícil de heredar
  • El valor no se puede cambiar en ningún entorno

Ejemplo

Clase de cadena

Ejemplo de código

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return this.name;
    }

    public String getRollNumber() {
        return this.rollNumber;
    }
}
Yasir Shabbir Choudhary
fuente
2

¿Cómo se puede hacer que una clase de Java sea inmutable?

Desde JDK 14+ que tiene JEP 359 , podemos usar " records". Es la forma más sencilla y sin complicaciones de crear una clase inmutable.

Una clase de registro es un portador transparente y poco inmutable para un conjunto fijo de campos conocido como el registro componentsque proporciona una statedescripción para el registro. Cada uno componentda lugar a un finalcampo que contiene el valor proporcionado y un accessormétodo para recuperar el valor. El nombre del campo y el nombre del descriptor de acceso coinciden con el nombre del componente.

Consideremos el ejemplo de crear un rectángulo inmutable.

record Rectangle(double length, double width) {}

No es necesario declarar ningún constructor, no es necesario implementar métodos equals & hashCode. Cualquier registro necesita un nombre y una descripción del estado.

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

Si desea validar el valor durante la creación del objeto, tenemos que declarar explícitamente el constructor.

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

El cuerpo del registro puede declarar métodos estáticos, campos estáticos, inicializadores estáticos, constructores, métodos de instancia y tipos anidados.

Métodos de instancia

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

campos estáticos, métodos

Dado que el estado debe ser parte de los componentes, no podemos agregar campos de instancia a los registros. Pero podemos agregar campos y métodos estáticos:

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}

¿Cuál es la necesidad de inmutabilidad y hay alguna ventaja en usar esto?

Las respuestas publicadas anteriormente son lo suficientemente buenas para justificar la necesidad de inmutabilidad y sus ventajas.

Bhanu Hoysala
fuente
0

Otra forma de hacer un objeto inmutable es usando la biblioteca Immutables.org :

Suponiendo que se agregaron las dependencias necesarias, cree una clase abstracta con métodos de acceso abstractos. Puede hacer lo mismo anotando con interfaces o incluso anotaciones (@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

Ahora es posible generar y luego usar la implementación inmutable generada:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}
Tal como
fuente
0

Una clase inmutable es simplemente una clase cuyas instancias no se pueden modificar.

Toda la información contenida en cada instancia es fija durante la vida útil del objeto, por lo que nunca se pueden observar cambios.

Las clases inmutables son más fáciles de diseñar, implementar y usar que las clases mutables.

Para hacer que una clase sea inmutable, siga estas cinco reglas:

  1. No proporcione métodos que modifiquen el estado del objeto

  2. Asegúrese de que la clase no se pueda extender.

  3. Haga que todos los campos sean definitivos.

  4. Haga que todos los campos sean privados.

  5. Garantice el acceso exclusivo a cualquier componente mutable.

Los objetos inmutables son inherentemente seguros para subprocesos; no requieren sincronización.

Los objetos inmutables se pueden compartir libremente.

Los objetos inmutables son excelentes bloques de construcción para otros objetos.

sandeep vanama
fuente
0

@Jack, tener campos finales y establecedores en la clase no hará que la clase sea inmutable. La palabra clave final solo asegúrese de que nunca se reasigne una variable. Necesita devolver una copia profunda de todos los campos en los métodos getter. Esto asegurará que, después de obtener un objeto del método getter, el estado interno del objeto no se vea afectado.

Amit Toor
fuente
-1

Como hablante no nativo de inglés, no me gusta la interpretación común de que "clase inmutable" es "los objetos de clase construidos son inmutables"; más bien, yo mismo me inclino a interpretar que "el objeto de clase en sí mismo es inmutable".

Dicho esto, la "clase inmutable" es una especie de objeto inmutable. La diferencia está en responder cuál es el beneficio. Según mi conocimiento / interpretación, la clase inmutable evita que sus objetos modifiquen el comportamiento en tiempo de ejecución.

bbgw
fuente
-1

La mayoría de las respuestas aquí son buenas, y algunas mencionaron las reglas, pero creo que es bueno expresar con palabras por qué y cuándo debemos seguir estas reglas. Así que estoy dando la siguiente explicación.

  • Declarar las variables miembro como 'finales': cuando las declaramos como finales, el compilador nos obliga a inicializarlas. Podemos inicializar directamente, por defecto, mediante el constructor arg (ver código de muestra a continuación) y después de la inicialización no podemos modificarlos ya que son finales.
  • Y, por supuesto, si intentamos usar Setters para esas variables finales, el compilador arroja un error.

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

¿Necesitamos declarar la clase como 'final'?

  • Sin la palabra clave final en la declaración de clase, la clase se puede heredar. Entonces, la subclase puede anular los métodos de obtención. Aquí tenemos que considerar dos escenarios:

1. Tener solo miembros primitivos: no tenemos problema. Si la clase solo tiene miembros primitivos, entonces no es necesario declarar la clase como final.

2. Tener objetos como variables miembro: si tenemos objetos como variables miembro, entonces tenemos que hacer que los miembros de esos objetos también sean finales. Significa que necesitamos atravesar el árbol profundo y hacer que todos los objetos / primitivos sean finales, lo que puede no ser posible todas las veces. Entonces, la solución es hacer que la clase sea final, lo que evita la herencia. Por lo tanto, no se trata de que la subclase anule los métodos getter.

Srikant M
fuente
-1

La anotación @Value de Lombok se puede utilizar para generar clases inmutables. Es tan simple como el siguiente código.

@Value
public class LombokImmutable {
    int id;
    String name;
}

Según la documentación del sitio de Lombok:

@Value es la variante inmutable de @Data; todos los campos se hacen privados y definitivos de forma predeterminada, y no se generan establecedores. La clase en sí también se hace final de forma predeterminada, porque la inmutabilidad no es algo que se pueda imponer a una subclase. Al igual que @Data, también se generan métodos útiles toString (), equals () y hashCode (), cada campo obtiene un método getter y también se genera un constructor que cubre todos los argumentos (excepto los campos finales que se inicializan en la declaración del campo) .

Puede encontrar un ejemplo completamente funcional aquí.

Anubhav
fuente
Antes de responder una pregunta anterior que tenga una respuesta aceptada (busque la ✓ verde), así como otras respuestas, asegúrese de que su respuesta agregue algo nuevo o sea útil en relación con ellas. Tenga cuidado al responder la pregunta de OP ¿ Cómo se puede hacer que una clase Java sea inmutable, cuál es la necesidad de inmutabilidad y hay alguna ventaja en usar esto? . - Solo está dando una respuesta parcial que solo establece que se puede usar Lombok, un marco / biblioteca de terceros, que no es necesariamente de lo que trata la pregunta de OP. También Java 15 está fuera, ¿por qué usar Lombok cuando recordse puede usar Java ?
Ivo Mori
Les estoy dando una de las opciones de cómo lograrlo. No todo el mundo usa Java 15, la mayoría de las aplicaciones actuales se ejecutan en versiones anteriores, por lo que decir por qué usar Lombok no tiene mucho sentido. En mi proyecto actual, recientemente migramos a Java 11 y estamos usando Lombok para lograr clases inmutables. Además, mi respuesta es una adición a las respuestas que ya estaban presentes. Lo que es inmutable ya ha sido respondido, supongo que esperas que lo reescriba solo para completarlo.
Anubhav
Lo suficientemente justo. No voté a favor ni en contra. Traté de señalar las razones por las que otras dos personas rechazaron esta respuesta. Depende de usted si desea editar su respuesta y cómo.
Ivo Mori