¿Cómo se puede hacer que una clase de Java sea inmutable, cuál es la necesidad de inmutabilidad y hay alguna ventaja en usar esto?
@Immutable
anotación de jcabi-aspectos
¿Cómo se puede hacer que una clase de Java sea inmutable, cuál es la necesidad de inmutabilidad y hay alguna ventaja en usar esto?
@Immutable
anotación de jcabi-aspectos
Respuestas:
¿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 ImmutableInt
solo 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 ArrayList
que 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 ArrayList
se puede obtener getList
y 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á.
return Collections.unmodifiableList(list);
devolver una vista de solo lectura en la lista.
final
. De lo contrario, puede ampliarse con un método de establecimiento (u otros tipos de métodos de mutación y campos mutables).
final
si la clase solo tiene private
campos, ya que no se puede acceder a ellos desde las subclases.
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.
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:
final
(para que no se puedan crear clases secundarias)final
(para que no podamos cambiar su valor después de la creación del objeto)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.
La inmutabilidad se puede lograr principalmente de dos formas:
final
atributos de instancia para evitar la reasignaciónLas ventajas de la inmutabilidad son las suposiciones que puede hacer sobre estos objetos:
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,
/**
* 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.
Las clases inmutables son aquellas cuyos objetos no se pueden cambiar después de la creación.
Las clases inmutables son útiles para
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;
}
}
¿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 components
que proporciona una state
descripción para el registro. Cada uno component
da lugar a un final
campo que contiene el valor proporcionado y un accessor
mé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.
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)
}
}
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:
No proporcione métodos que modifiquen el estado del objeto
Asegúrese de que la clase no se pueda extender.
Haga que todos los campos sean definitivos.
Haga que todos los campos sean privados.
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.
@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.
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.
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.
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'?
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.
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í.
record
se puede usar Java ?