¿Cómo crear objetos inmutables en Java?

83

¿Cómo crear objetos inmutables en Java?

¿Qué objetos deberían llamarse inmutables?

Si tengo una clase con todos los miembros estáticos, ¿es inmutable?

Neel Salpe
fuente
posible duplicado de ¿Qué se entiende por inmutable?
Joachim Sauer
1
La pregunta vinculada anteriormente no es la misma, pero las respuestas de esa pregunta deben responder a todas sus preguntas.
Joachim Sauer
Si su clase son todos miembros estáticos, no tiene estado (ninguna instancia tiene un estado individual) y la cuestión de mutable o inmutable se vuelve discutible.
Sebastian Redl
¿Hay alguna otra forma de inicializar campos que no sea el constructor? Tengo más de 20 campos en mi clase. Es muy difícil inicializar todos los campos usando el constructor, algunos campos también son opcionales.
Nikhil Mishra

Respuestas:

88

A continuación se presentan las duras exigencias de un objeto inmutable.

  1. Haz que la clase sea final
  2. hacer que todos los miembros sean finales, establecerlos explícitamente, en un bloque estático, o en el constructor
  3. Hacer que todos los miembros sean privados
  4. Sin métodos que modifiquen el estado
  5. Tenga mucho cuidado de limitar el acceso a miembros mutables (recuerde que el campo puede serlo, finalpero el objeto aún puede ser mutable private final Date imStillMutable. Deberías hacer defensive copiesen estos casos.

El razonamiento detrás de hacer la clase finales muy sutil y a menudo se pasa por alto. Si no es el final, las personas pueden extender libremente su clase, anular publico protectedcomportarse, agregar propiedades mutables y luego proporcionar su subclase como sustituto. Al declarar la clase final, puede asegurarse de que esto no suceda.

Para ver el problema en acción, considere el siguiente ejemplo:

public class MyApp{

    /**
     * @param args
     */
    public static void main(String[] args){

        System.out.println("Hello World!");

        OhNoMutable mutable = new OhNoMutable(1, 2);
        ImSoImmutable immutable = mutable;

        /*
         * Ahhhh Prints out 3 just like I always wanted
         * and I can rely on this super immutable class 
         * never changing. So its thread safe and perfect
         */
        System.out.println(immutable.add());

        /* Some sneak programmer changes a mutable field on the subclass */
        mutable.field3=4;

        /*
         * Ahhh let me just print my immutable 
         * reference again because I can trust it 
         * so much.
         * 
         */
        System.out.println(immutable.add());

        /* Why is this buggy piece of crap printing 7 and not 3
           It couldn't have changed its IMMUTABLE!!!! 
         */
    }

}

/* This class adheres to all the principles of 
*  good immutable classes. All the members are private final
*  the add() method doesn't modify any state. This class is 
*  just a thing of beauty. Its only missing one thing
*  I didn't declare the class final. Let the chaos ensue
*/ 
public class ImSoImmutable{
    private final int field1;
    private final int field2;

    public ImSoImmutable(int field1, int field2){
        this.field1 = field1;
        this.field2 = field2;
    }

    public int add(){
        return field1+field2;
    }
}

/*
This class is the problem. The problem is the 
overridden method add(). Because it uses a mutable 
member it means that I can't  guarantee that all instances
of ImSoImmutable are actually immutable.
*/ 
public class OhNoMutable extends ImSoImmutable{   

    public int field3 = 0;

    public OhNoMutable(int field1, int field2){
        super(field1, field2);          
    }

    public int add(){
       return super.add()+field3;  
    }

}

En la práctica, es muy común encontrar el problema anterior en entornos de inyección de dependencia. No está instanciando cosas explícitamente y la referencia de superclase que se le da puede ser en realidad una subclase.

La conclusión es que para ofrecer garantías estrictas sobre la inmutabilidad, debes marcar la clase como final. Esto se trata en profundidad en Effective Java de Joshua Bloch y se hace referencia explícitamente en la especificación del modelo de memoria de Java .

nsfyn55
fuente
¿qué pasa con todos los miembros estáticos?
Neel Salpe
1
la clase no necesita ser definitiva para eso.
Angel O'Sphere
12
@Nilesh: la inmutabilidad es propiedad de las instancias . Los miembros estáticos no suelen relacionarse con una sola instancia, por lo que aquí no entran en escena.
Joachim Sauer
4
Artículo 15 de Joshua Bloch sobre inmutabilidad: sin métodos que modifiquen el estado, todos los campos finales, todos los campos privados, asegúrese de que la clase no se pueda extender, garantice el acceso exclusivo a cualquier componente mutable.
nsfyn55
2
@Jaochim: son absolutamente parte de la ecuación: tome el ejemplo anterior si agrego un miembro estático mutable y lo uso en la función de adición de ImSoImmutable, tiene el mismo problema. Si una clase es inmutable, todos los aspectos deben ser inmutables.
nsfyn55
14

Simplemente no agregue métodos públicos mutadores (establecedores) a la clase.

BalusC
fuente
¿qué pasa con todos los miembros estáticos? ¿Cambia la referencia o el estado del objeto para este tipo de objetos?
Neel Salpe
7
No importa. Si no puede cambiarlos externamente por algún método, es inmutable.
BalusC
eso no se puede responder ya que no sabemos qué hacen los miembros estáticos ... ofc podrían modificar campos privados. Si lo hacen, la clase no es imbatible.
Angel O'Sphere
Y el constructor predeterminado de la clase debería ser privateo la clase debería serlo final. Solo para evitar la herencia. Porque la herencia viola la encapsulación.
Talha Ahmed Khan
¿Qué hay de pasar un objeto mutable, por ejemplo, List al objeto inmutable y luego cambiarlo desde el exterior? Esto es posible y debe tratarse utilizando copias defensivas durante la creación del objeto
Yassin Hajaj
14

Las clases no son inmutables, los objetos sí.

Medios inmutables: mi estado visible público no puede cambiar después de la inicialización.

Los campos no tienen que declararse definitivos, aunque puede ayudar enormemente a garantizar la seguridad de los subprocesos.

Si su clase solo tiene miembros estáticos, entonces los objetos de esta clase son inmutables, porque no puede cambiar el estado de ese objeto (probablemente tampoco pueda crearlo :))

Alexander Pogrebnyak
fuente
3
hacer que todos los campos sean estáticos limita todas las intenciones para compartir el mismo estado, lo cual no es realmente útil.
aviad el
6

Para hacer que una clase sea inmutable en Java, puede tomar nota de los siguientes puntos:

1. No proporcione métodos de establecimiento para modificar valores de ninguna de las variables de instancia de la clase.

2. Declare la clase como 'final' . Esto evitaría que cualquier otra clase la extienda y, por lo tanto, anule cualquier método que pueda modificar los valores de las variables de instancia.

3. Declare las variables de instancia como privadas y finales .

4. También puede declarar el constructor de la clase como privado y agregar un método de fábrica para crear una instancia de la clase cuando sea necesario.

¡Estos puntos deberían ayudar!

VishEntusiasta
fuente
3
WRT # 4 ¿Cómo afecta la visibilidad del constructor a la mutabilidad? String es inmutable pero tiene varios constructores públicos.
Ryan
Como dijo @Ryan, lo mismo se aplica a las variables de ejemplo: ¿por qué deberían declararse private?
MC Emperor
No estoy seguro de por qué esta respuesta tiene votos a favor. Esta respuesta es incompleta. No habla de objetos mutables que es importante abordar. Lea la explicación de @ nsfyn55 para una mejor comprensión.
Ketan R
4

De oráculo sitio, cómo crear objetos inmutables en Java.

  1. No proporcione métodos "establecedores": métodos que modifican campos u objetos a los que se hace referencia por campos.
  2. Haga que todos los campos sean finales y privados.
  3. No permita que las subclases anulen métodos. La forma más sencilla de hacer esto es declarar la clase como final. Un enfoque más sofisticado es hacer que el constructor sea privado y construir instancias en métodos de fábrica.
  4. Si los campos de instancia incluyen referencias a objetos mutables, no permita que esos objetos se cambien:
    I. No proporcione métodos que modifiquen los objetos mutables.
    II. No comparta referencias a los objetos mutables. Nunca almacene referencias a objetos externos mutables pasados ​​al constructor; si es necesario, cree copias y almacene las referencias a las copias. Del mismo modo, cree copias de sus objetos mutables internos cuando sea necesario para evitar devolver los originales en sus métodos.
e11438
fuente
3

Un objeto inmutable es un objeto que no cambiará su estado interno después de la creación. Son muy útiles en aplicaciones multiproceso porque se pueden compartir entre subprocesos sin sincronización.

Para crear un objeto inmutable, debe seguir algunas reglas simples:

1. No agregue ningún método setter

Si está construyendo un objeto inmutable, su estado interno nunca cambiará. La tarea de un método de establecimiento es cambiar el valor interno de un campo, por lo que no puede agregarlo.

2. Declarar todos los campos definitivos y privados.

Un campo privado no es visible desde fuera de la clase, por lo que no se le pueden aplicar cambios manuales.

Declarar un campo final garantizará que si hace referencia a un valor primitivo, el valor nunca cambiará si hace referencia a un objeto, la referencia no se puede cambiar. Esto no es suficiente para garantizar que un objeto con solo campos finales privados no sea mutable.

3. Si un campo es un objeto mutable, cree copias defensivas de él para los métodos getter

Hemos visto antes que definir un campo final y privado no es suficiente porque es posible cambiar su estado interno. Para resolver este problema, necesitamos crear una copia defensiva de ese campo y devolver ese campo cada vez que se solicite.

4. Si un objeto mutable pasado al constructor debe asignarse a un campo, cree una copia defensiva del mismo.

El mismo problema ocurre si mantiene una referencia pasada al constructor porque es posible cambiarla. Por lo tanto, mantener una referencia a un objeto pasado al constructor puede crear objetos mutables. Para solucionar este problema es necesario crear una copia defensiva del parámetro si son objetos mutables.

Nótese que si un campo es una referencia a un objeto inmutable no es necesario crear copias defensivas del mismo en el constructor y en los métodos getter basta con definir el campo como final y privado.

5. No permita que las subclases anulen métodos

Si una subclase anula un método, puede devolver el valor original de un campo mutable en lugar de una copia defensiva del mismo.

Para resolver este problema, es posible realizar una de las siguientes acciones:

  1. Declare la clase inmutable como final para que no se pueda extender
  2. Declare todos los métodos de la clase inmutable final para que no se puedan anular
  3. Cree un constructor privado y una fábrica para crear instancias de la clase inmutable porque una clase con constructores privados no se puede extender

Si sigue esas reglas simples, puede compartir libremente sus objetos inmutables entre subprocesos porque son seguros para subprocesos.

A continuación se presentan algunos puntos notables:

  • Los objetos inmutables simplifican la vida en muchos casos. Son especialmente aplicables para tipos de valor, donde los objetos no tienen una identidad, por lo que pueden ser reemplazados fácilmente y pueden hacer que la programación concurrente sea más segura y limpia (la mayoría de los errores de concurrencia notoriamente difíciles de encontrar son causados ​​en última instancia por un estado mutable compartido entre hilos). Sin embargo, para objetos grandes y / o complejos, crear una nueva copia del objeto para cada cambio puede resultar muy costoso y / o tedioso . Y para los objetos con una identidad distinta, cambiar un objeto existente es mucho más simple e intuitivo que crear una copia nueva y modificada del mismo.
  • Hay algunas cosas que simplemente no puedes hacer con objetos inmutables, como tener relaciones bidireccionales . Una vez que establece un valor de asociación en un objeto, su identidad cambia. Entonces, establece el nuevo valor en el otro objeto y también cambia. El problema es que la referencia del primer objeto ya no es válida, porque se ha creado una nueva instancia para representar el objeto con la referencia. Continuar con esto solo resultaría en regresiones infinitas.
  • Para implementar un árbol de búsqueda binaria , debes devolver un árbol nuevo cada vez: Tu nuevo árbol habrá tenido que hacer una copia de cada nodo que haya sido modificado (las ramas no modificadas son compartidas). Para su función de inserción, esto no es tan malo, pero para mí, las cosas se volvieron bastante ineficientes rápidamente cuando comencé a trabajar en eliminar y reequilibrar.
  • Hibernate y JPA esencialmente dictan que su sistema use objetos mutables, porque la premisa de ellos es que detectan y guardan cambios en sus objetos de datos.
  • Dependiendo del lenguaje, un compilador puede realizar un montón de optimizaciones cuando se trata de datos inmutables porque sabe que los datos nunca cambiarán. Se omiten todo tipo de cosas, lo que le brinda enormes beneficios de rendimiento.
  • Si observa otros lenguajes JVM conocidos ( Scala, Clojure ), los objetos mutables rara vez se ven en el código y es por eso que la gente comienza a usarlos en escenarios donde un solo subproceso no es suficiente.

No hay bien o mal, solo depende de lo que prefieras. Solo depende de su preferencia y de lo que quiera lograr (y poder usar fácilmente ambos enfoques sin alienar a los fanáticos acérrimos de un lado u otro es un santo grial que algunos idiomas buscan).

amitkumar12788
fuente
2
  • No proporcione métodos "establecedores": métodos que modifican campos u objetos a los que se hace referencia por campos.
  • Haga que todos los campos sean finales y privados.
  • No permita que las subclases anulen métodos. La forma más sencilla de hacer esto es declarar la clase como final. Un enfoque más sofisticado es hacer que el constructor sea privado y construir instancias en métodos de fábrica.
  • Si los campos de instancia incluyen referencias a objetos mutables, no permita que esos objetos se modifiquen:
    • No proporcione métodos que modifiquen los objetos mutables.
    • No comparta referencias a los objetos mutables. Nunca almacene referencias a objetos externos mutables pasados ​​al constructor; si es necesario, cree copias y almacene las referencias a las copias. Del mismo modo, cree copias de sus objetos mutables internos cuando sea necesario para evitar devolver los originales en sus métodos.
Ajay Kumar
fuente
2

En primer lugar, sabe por qué necesita crear un objeto inmutable y cuáles son las ventajas del objeto inmutable.

Ventajas de un objeto inmutable

Concurrencia y subprocesos múltiples Es automáticamente seguro para subprocesos, por lo que se produce un problema de sincronización ... etc.

No es necesario copiar el constructor. No es necesario implementar el clon. La clase no se puede anular. Hacer que el campo sea privado y final. Forzar a los llamadores a construir un objeto completamente en un solo paso, en lugar de usar un constructor sin argumentos.

Los objetos inmutables son simplemente objetos cuyo estado significa que los datos del objeto no pueden cambiar después de que se construye el objeto inmutable.

por favor vea el código a continuación.

public final class ImmutableReminder{
    private final Date remindingDate;

    public ImmutableReminder (Date remindingDate) {
        if(remindingDate.getTime() < System.currentTimeMillis()){
            throw new IllegalArgumentException("Can not set reminder" +
                    " for past time: " + remindingDate);
        }
        this.remindingDate = new Date(remindingDate.getTime());
    }

    public Date getRemindingDate() {
        return (Date) remindingDate.clone();
    }
}
JBVala
fuente
2

Minimizar la mutabilidad

Una clase inmutable es simplemente una clase cuyas instancias no se pueden modificar. Toda la información contenida en cada instancia se proporciona cuando se crea y se fija durante la vida útil del objeto.

Clases inmutables de JDK: String, las clases primitivas en caja (clases contenedoras), BigInteger y BigDecimal, etc.

¿Cómo hacer inmutable una clase?

  1. No proporcione ningún método que modifique el estado del objeto (conocido como mutadores).
  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. Esto evita que los clientes obtengan acceso a objetos mutables a los que hacen referencia los campos y que modifiquen estos objetos directamente.
  5. Haz copias defensivas. Garantice el acceso exclusivo a cualquier componente mutable.

    public List getList () {return Collections.unmodifiableList (lista); <=== copia defensiva del campo mutable antes de devolverlo a la persona que llama}

Si su clase tiene campos que se refieren a objetos mutables, asegúrese de que los clientes de la clase no puedan obtener referencias a estos objetos. Nunca inicialice dicho campo en una referencia de objeto proporcionada por el cliente ni devuelva la referencia de objeto de un descriptor de acceso.

import java.util.Date;
public final class ImmutableClass {

       public ImmutableClass(int id, String name, Date doj) {
              this.id = id;
              this.name = name;
              this.doj = doj;
       }

       private final int id;
       private final String name;
       private final Date doj;

       public int getId() {
              return id;
       }
       public String getName() {
              return name;
       }

     /**
      * Date class is mutable so we need a little care here.
      * We should not return the reference of original instance variable.
      * Instead a new Date object, with content copied to it, should be returned.
      * */
       public Date getDoj() {
              return new Date(doj.getTime()); // For mutable fields
       }
}
import java.util.Date;
public class TestImmutable {
       public static void main(String[] args) {
              String name = "raj";
              int id = 1;
              Date doj = new Date();

              ImmutableClass class1 = new ImmutableClass(id, name, doj);
              ImmutableClass class2 = new ImmutableClass(id, name, doj);
      // every time will get a new reference for same object. Modification in              reference will not affect the immutability because it is temporary reference.
              Date date = class1.getDoj();
              date.setTime(date.getTime()+122435);
              System.out.println(class1.getDoj()==class2.getDoj());
       }
}

Para obtener más información, consulte mi blog:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html

Rajesh Dixit
fuente
@Pang ¿hay alguna otra forma de inicializar campos que no sea el constructor? Tengo más de 20 campos en mi clase. Es muy difícil inicializar todos los campos usando el constructor, algunos campos también son opcionales.
Nikhil Mishra
1
@NikhilMishra, puede usar el patrón de diseño de Builder para inicializar variables durante la construcción del objeto. Puede mantener las variables obligatorias para establecer en el constructor y las variables opcionales restantes para establecer mediante los métodos de establecimiento. Pero estrictamente hablando de esta manera, no creará una clase True Immutable.
sunny_dev
1

un objeto se llama inmutable si su estado no se puede cambiar una vez creado. Una de las formas más simples de crear una clase inmutable en Java es estableciendo que todos sus campos sean finales. Si necesita escribir una clase inmutable que incluya clases mutables como "java.util.Date". Para preservar la inmutabilidad en tales casos, se aconseja devolver copia del objeto original,

Rudra
fuente
No es que se recomiende devolver una copia, es necesario. Pero también es necesario que se haga una copia defensiva en el constructor. De lo contrario, el objeto se puede cambiar entre bastidores.
Ryan
1

Los objetos inmutables son aquellos objetos cuyo estado no se puede cambiar una vez que se crean, por ejemplo, la clase String es una clase inmutable. Los objetos inmutables no se pueden modificar, por lo que también son seguros para subprocesos en ejecución simultánea.

Características de las clases inmutables:

  • simple de construir
  • automáticamente seguro para subprocesos
  • buen candidato para las claves de mapa y establecer, ya que su estado interno no cambiaría durante el procesamiento
  • no necesita la implementación del clon ya que siempre representan el mismo estado

Claves para escribir clase inmutable:

  • asegúrese de que la clase no pueda ser anulada
  • hacer que todas las variables miembro sean privadas y finales
  • no le dé sus métodos de establecimiento
  • la referencia del objeto no debe filtrarse durante la fase de construcción
Manjul
fuente
1

Deben tenerse en cuenta los siguientes pasos cuando desee que cualquier clase sea una clase inmutable.

  1. La clase debe marcarse como final
  2. Todos los campos deben ser privados y definitivos
  3. Reemplazar setters con constructor (para asignar un valor a una variable).

Echemos un vistazo a lo que hemos escrito arriba:

//ImmutableClass
package younus.attari;

public final class ImmutableExample {

    private final String name;
    private final String address;

    public ImmutableExample(String name,String address){
        this.name=name;
        this.address=address;
    }


    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

}

//MainClass from where an ImmutableClass will be called
package younus.attari;

public class MainClass {

    public static void main(String[] args) {
        ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
        System.out.println(example.getName());

    }
}
user3205589
fuente
0

Propiedades comúnmente ignoradas pero importantes en objetos inmutables

Además de la respuesta proporcionada por @ nsfyn55, los siguientes aspectos también deben tenerse en cuenta para la inmutabilidad del objeto, que son de suma importancia

Considere las siguientes clases:

public final class ImmutableClass {

  private final MutableClass mc;

  public ImmutableClass(MutableClass mc) {
    this.mc = mc;
  }

  public MutableClass getMutClass() {
    return this.mc;
  }
}

public class MutableClass {

  private String name;

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

  public void setName(String name) {
    this.name = name;
  }
}


public class MutabilityCheck {

public static void main(String[] args) {

  MutableClass mc = new MutableClass();

  mc.setName("Foo");

  ImmutableClass iMC = new ImmutableClass(mc);

  System.out.println(iMC.getMutClass().getName());

  mc.setName("Bar");

  System.out.println(iMC.getMutClass().getName());

  }

 }

Lo siguiente será el resultado de MutabilityCheck:

 Foo
 Bar

Es importante observar que,

  1. Construir objetos mutables en un objeto inmutable (a través del constructor), ya sea 'copiando' o 'agrupando' a variables de instancia de lo inmutable descrito por los siguientes cambios:

    public final class ImmutableClass {
    
       private final MutableClass mc;
    
       public ImmutableClass(MutableClass mc) {
         this.mc = new MutableClass(mc);
       }
    
       public MutableClass getMutClass() {
         return this.mc;
       }
    
     }
    
     public class MutableClass {
    
      private String name;
    
      public MutableClass() {
    
      }
      //copy constructor
      public MutableClass(MutableClass mc) {
        this.name = mc.getName();
      }
    
      public String getName() {
        return this.name;
      }
    
      public void setName(String name) {
       this.name = name;
      } 
     }
    

todavía no asegura la inmutabilidad completa ya que lo siguiente sigue siendo válido de la clase MutabilityCheck:

  iMC.getMutClass().setName("Blaa");
  1. Sin embargo, ejecutar MutabilityCheck con los cambios realizados en 1. dará como resultado que el resultado sea:

    Foo
    Foo
    
  2. Para lograr una inmutabilidad completa en un objeto, todos sus objetos dependientes también deben ser inmutables.

KJ Sudarshan
fuente
0

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 equals& hashCodemétodos. 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");
  }
}
Bhanu Hoysala
fuente