Objeto inmutable
En la programación funcional y orientada a objetos, un objeto inmutable (objeto inmutable) es un objeto cuyo estado no se puede modificar después de su creación. Esto contrasta con un objeto mutable (objeto modificable), que se puede modificar después de crearlo. En algunos casos, un objeto se considera inmutable incluso si cambian algunos atributos utilizados internamente, pero el estado del objeto parece inalterable desde un punto de vista externo. Por ejemplo, un objeto que utiliza la memorización para almacenar en caché los resultados de cálculos costosos aún podría considerarse un objeto inmutable.
Las cadenas y otros objetos concretos suelen expresarse como objetos inmutables para mejorar la legibilidad y la eficiencia del tiempo de ejecución en la programación orientada a objetos. Los objetos inmutables también son útiles porque son intrínsecamente seguros para subprocesos. Otros beneficios son que son más simples de entender y razonar y ofrecen mayor seguridad que los objetos mutables.
Conceptos
Variables inmutables
En la programación imperativa, los valores contenidos en las variables del programa cuyo contenido nunca cambia se conocen como constantes para diferenciarlos de las variables que podrían modificarse durante la ejecución. Los ejemplos incluyen factores de conversión de metros a pies, o el valor de pi a varios lugares decimales.
Los campos de solo lectura se pueden calcular cuando se ejecuta el programa (a diferencia de las constantes, que se conocen de antemano), pero nunca cambian después de que se inicializan.
Inmutabilidad débil vs fuerte
A veces, se habla de que ciertos campos de un objeto son inmutables. Esto significa que no hay forma de cambiar esas partes del estado del objeto, aunque otras partes del objeto pueden cambiarse (débilmente inmutables). Si todos los campos son inmutables, entonces el objeto es inmutable. Si el objeto completo no puede ser extendido por otra clase, el objeto se llama fuertemente inmutable. Esto podría, por ejemplo, ayudar a hacer cumplir explícitamente ciertas invariantes sobre ciertos datos en el objeto que permanecen iguales durante la vida útil del objeto. En algunos idiomas, esto se hace con una palabra clave (por ejemplo, const
en C++, final
en Java) que designa el campo como inmutable. Algunos idiomas lo invierten: en OCaml, los campos de un objeto o registro son inmutables de forma predeterminada y deben marcarse explícitamente con mutable
para que así sea.
Referencias a objetos
En la mayoría de los lenguajes orientados a objetos, se puede hacer referencia a los objetos mediante referencias. Algunos ejemplos de dichos lenguajes son Java, C++, C#, VB.NET y muchos lenguajes de secuencias de comandos, como Perl, Python y Ruby. En este caso, importa si el estado de un objeto puede variar cuando los objetos se comparten a través de referencias.
Referenciar vs copiar objetos
Si se sabe que un objeto es inmutable, se prefiere crear una referencia de él en lugar de copiar todo el objeto. Esto se hace para conservar la memoria evitando la duplicación de datos y evitando llamadas a constructores y destructores; también da como resultado un aumento potencial en la velocidad de ejecución.
La técnica de copia de referencia es mucho más difícil de usar para objetos mutables, porque si cualquier usuario de una referencia de objeto mutable la cambia, todos los demás usuarios de esa referencia ven el cambio. Si este no es el efecto deseado, puede ser difícil notificar a los otros usuarios para que respondan correctamente. En estas situaciones, la copia defensiva de todo el objeto en lugar de la referencia suele ser una solución fácil pero costosa. El patrón de observador es una técnica alternativa para manejar cambios en objetos mutables.
Copia en escritura
Una técnica que combina las ventajas de los objetos mutables e inmutables, y es compatible directamente con casi todo el hardware moderno, es la copia en escritura (COW). Usando esta técnica, cuando un usuario le pide al sistema que copie un objeto, simplemente crea una nueva referencia que aún apunta al mismo objeto. Tan pronto como un usuario intenta modificar el objeto a través de una referencia particular, el sistema hace una copia real, aplica la modificación y establece la referencia para referirse a la nueva copia. Los otros usuarios no se ven afectados, porque todavía se refieren al objeto original. Por lo tanto, bajo COW, todos los usuarios parecen tener una versión mutable de sus objetos, aunque en el caso de que los usuarios no modifiquen sus objetos, se conservan las ventajas de ahorro de espacio y velocidad de los objetos inmutables. La copia en escritura es popular en los sistemas de memoria virtual porque les permite ahorrar espacio en la memoria y al mismo tiempo manejar correctamente cualquier cosa que pueda hacer un programa de aplicación.
Pasantía
La práctica de usar siempre referencias en lugar de copias de objetos iguales se conoce como interning. Si se usa interning, dos objetos se consideran iguales si y solo si sus referencias, normalmente representadas como punteros o números enteros, son iguales. Algunos lenguajes hacen esto automáticamente: por ejemplo, Python interna automáticamente cadenas cortas. Si se garantiza que el algoritmo que implementa la internación lo hará en todos los casos posibles, entonces la comparación de objetos por igualdad se reduce a comparar sus punteros, una ganancia sustancial en velocidad en la mayoría de las aplicaciones. (Incluso si no se garantiza que el algoritmo sea completo, aún existe la posibilidad de una mejora de caso de ruta rápida cuando los objetos son iguales y usan la misma referencia). La internación generalmente solo es útil para objetos inmutables.
Seguridad de subprocesos
Los objetos inmutables pueden ser útiles en aplicaciones de subprocesos múltiples. Múltiples subprocesos pueden actuar sobre los datos representados por objetos inmutables sin preocuparse de que otros subprocesos cambien los datos. Por lo tanto, los objetos inmutables se consideran más seguros para subprocesos que los objetos mutables.
Violando la inmutabilidad
La inmutabilidad no implica que el objeto almacenado en la memoria de la computadora no se pueda escribir. Más bien, la inmutabilidad es una construcción en tiempo de compilación que indica lo que un programador puede hacer a través de la interfaz normal del objeto, no necesariamente lo que puede hacer absolutamente (por ejemplo, eludiendo el sistema de tipos o violando la corrección constante en C o C++).
Detalles específicos del idioma
En Python, Java y.NET Framework, las cadenas son objetos inmutables. Tanto Java como.NET Framework tienen versiones mutables de string. En Java estos son StringBuffer
y StringBuilder
(versiones mutables de Java String
) y en.NET esto es StringBuilder
(versión mutable de.Net String
). Python 3 tiene una variante de cadena mutable (bytes), llamada bytearray
.
Además, todas las clases contenedoras primitivas en Java son inmutables.
Patrones similares son la interfaz inmutable y el envoltorio inmutable.
En lenguajes de programación funcionales puros, no es posible crear objetos mutables sin ampliar el lenguaje (por ejemplo, a través de una biblioteca de referencias mutables o una interfaz de función externa), por lo que todos los objetos son inmutables.
Ada
En Ada, cualquier objeto se declara como variable (es decir, mutable; generalmente el valor predeterminado implícito) o constante
(es decir, inmutable) a través de constant palabra clave.
Tipo Algún tipo es nuevo Integer; - podría ser algo más complicado x: constante Algún tipo:= 1; - inmutable Sí.: Algún tipo; - mutable
Los parámetros del subprograma son inmutables en el modo in y mutables en los modos in out y out.
procedimiento Do_it()a: dentro Integer; b: dentro Fuera. Integer; c: Fuera. Integer) es comenzar - un es inmutable b:= b + a; c:= a; final Do_it;
C#
En C# puede imponer la inmutabilidad de los campos de una clase con la declaración readonly
.
Al hacer cumplir todos los campos como inmutables, obtiene un tipo inmutable.
clase AnImmutable Tipo{} público solo doble _value; público AnImmutable Tipo()doble x) {} _value = x; } público AnImmutable Tipo Plaza() {} retorno nuevo AnImmutable Tipo()_value * _value); }}
C++
En C++, una implementación constante correcta de Cart
permitiría al usuario crear instancias de la clase y luego usarlas como const
(inmutable) o mutable, como se desee, proporcionando dos versiones diferentes del método items()
. (Tenga en cuenta que en C++ no es necesario, y de hecho imposible, proporcionar un constructor especializado para las instancias de const
).
clase Carrito {} público: Carrito()std::vector.Tema■ Temas): items_()Temas) {} std::vector.Tema■ Temas() {} retorno items_; } const std::vector.Tema■ Temas() const {} retorno items_; } int ComputeTotal Costo() const {} /* restitución suma de los precios */ } privado: std::vector.Tema■ items_;};
Tenga en cuenta que, cuando hay un miembro de datos que es un puntero o una referencia a otro objeto, entonces es posible mutar el objeto al que se apunta o al que se hace referencia solo dentro de un método no constante.
C++ también proporciona inmutabilidad abstracta (a diferencia de bit a bit) a través de la palabra clave mutable
, que permite cambiar una variable miembro desde dentro de un método const
.
clase Carrito {} público: Carrito()std::vector.Tema■ Temas): items_()Temas) {} const std::vector.Tema■ Temas() const {} retorno items_; } int ComputeTotal Costo() const {} si ()total_cost) {} retorno *total_cost; } int total_cost = 0; para ()const auto" Tema : items_) {} total_cost += Tema.Costo(); } total_cost = total_cost; retorno total_cost; } privado: std::vector.Tema■ items_; mutable std::opcional.int■ total_cost;};
D
En D, existen dos calificadores de tipo, const
y immutable
, para variables que no se pueden cambiar. A diferencia de const
de C++, final
de Java y readonly
de C#, son transitivos y aplicar recursivamente a cualquier cosa accesible a través de referencias de dicha variable. La diferencia entre const
y immutable
es a lo que se aplican: const
es una propiedad de la variable: pueden existir legalmente referencias mutables al valor referido, es decir, el valor realmente puede cambiar. Por el contrario, immutable
es una propiedad del valor al que se hace referencia: el valor y cualquier cosa transitivamente accesible desde él no puede cambiar (sin romper el sistema de tipos, lo que lleva a un comportamiento indefinido). Cualquier referencia de ese valor debe marcarse como const
o immutable
. Básicamente, para cualquier tipo no calificado T
, const(T)
es la unión separada de T
(mutable) y immutable(T).
clase C {} /*mutable*/ Objeto m Campo; const Objeto c Campo; inmutable Objeto iField;}
Para un objeto C
mutable, se puede escribir en su mField
. Para un objeto const(C)
, mField
no se puede modificar, hereda const
; iField
sigue siendo inmutable ya que es la garantía más fuerte. Para un immutable(C)
, todos los campos son inmutables.
En una función como esta:
vacío func()C m, const C c, inmutable C i){} /* dentro de los frenos */ }
Dentro de las llaves, c
podría referirse al mismo objeto que m
, por lo que las mutaciones a m
podrían cambiar indirectamente c también. También,
c
podría referirse al mismo objeto que i
, pero dado que el valor es inmutable, no hay cambios. Sin embargo, m
y i
no pueden referirse legalmente al mismo objeto.
En el lenguaje de las garantías, mutable no tiene garantías (la función podría cambiar el objeto), const
es una garantía externa de que la función no cambiará nada, y
immutable
es una garantía bidireccional (la función no cambiará el valor y la persona que llama no debe cambiarlo).
Los valores que son const
o inmutable
deben inicializarse mediante asignación directa en el punto de declaración o mediante un constructor.
Debido a que los parámetros const
olvidan si el valor era mutable o no, una construcción similar, inout
, actúa, en cierto sentido, como una variable para la información de mutabilidad.
Una función de tipo const(S) function(const(T))
devuelve valores escritos const(S)
para argumentos mutables, constantes e inmutables. Por el contrario, una función de tipo inout(S) function(inout(T))
devuelve S
para argumentos mutables T
, const (S)
para valores const(T)
y immutable(S)
para valores immutable(T)
.
Convertir valores inmutables en mutables inflige un comportamiento indefinido al cambiar, incluso si el valor original proviene de un origen mutable. Convertir valores mutables en inmutables puede ser legal cuando no quedan referencias mutables después. "Una expresión se puede convertir de mutable (...) a inmutable si la expresión es única y todas las expresiones a las que se refiere transitivamente son únicas o inmutables." Si el compilador no puede probar la unicidad, la conversión se puede realizar explícitamente y depende del programador asegurarse de que no existan referencias mutables.
El tipo string
es un alias para immutable(char)[]
, es decir, una porción de memoria escrita de caracteres inmutables. Crear subcadenas es económico, ya que solo copia y modifica un puntero y un archivo de longitud, y es seguro, ya que los datos subyacentes no se pueden cambiar. Los objetos de tipo const(char)[]
pueden hacer referencia a cadenas, pero también a búferes mutables.
Hacer una copia superficial de un valor constante o inmutable elimina la capa externa de inmutabilidad: Copiar una cadena inmutable (immutable(char[])
) devuelve una cadena (immutable(char) []
). El puntero inmutable y la longitud se copian y las copias son mutables. El dato referido no ha sido copiado y mantiene su calificador, en el ejemplo immutable
. Se puede eliminar haciendo una copia más profunda, p. utilizando la función dup
.
Java
Un ejemplo clásico de un objeto inmutable es una instancia de la clase Java String
String s = "ABC";s.aLowerCase(); // ¡Esto no logra nada!
El método toLowerCase()
no cambia los datos "ABC" que contiene s
. En su lugar, se crea una instancia de un nuevo objeto String y se le asignan los datos "abc" durante su construcción. El método toLowerCase()
devuelve una referencia a este objeto String. Para hacer que String s
contenga los datos "abc", se necesita un enfoque diferente:
s = s.aLowerCase();
Ahora el String s
hace referencia a un nuevo objeto String que contiene "abc". No hay nada en la sintaxis de la declaración de la clase String que la imponga como inmutable; más bien, ninguno de los métodos de la clase String afecta los datos que contiene un objeto String, lo que lo hace inmutable.
La palabra clave final
(artículo detallado) se usa para implementar tipos primitivos inmutables y referencias a objetos, pero no puede, por sí misma, hacer que los objetos mismos sean inmutables. Vea los siguientes ejemplos:
Las variables de tipo primitivo (int
, long
, short
, etc.) se pueden reasignar después de definirlas. Esto se puede evitar usando final
.
int i = 42; //int es un tipo primitivoi = 43; / OKfinal int j = 42;j = 43; // no compila. j es final así que no se puede reasignar
Los tipos de referencia no se pueden hacer inmutables simplemente usando la palabra clave final
. final
solo evita la reasignación.
final MiObjeto m = nuevo MiObjeto(); //m es de tipo de referenciam.datos = 100; // OK. Podemos cambiar el estado del objeto m (m es mutable y final no cambia este hecho)m = nuevo MiObjeto(); // no compila. m es final así que no se puede reasignar
Contenedores primitivos (Entero
, Largo
, Corto
, Doble
, Flotante
, Character
, Byte
, Boolean
) también son inmutables. Las clases inmutables se pueden implementar siguiendo algunas pautas simples.
Javascript
En JavaScript, todos los tipos primitivos (Undefined, Null, Boolean, Number, BigInt, String, Symbol) son inmutables, pero los objetos personalizados generalmente son mutables.
función algo()x) {} /* cambia x aquí cambiar el original? */ };Var str = Una cuerda ';Var obj = {} an: 'objeto ' };algo()str); // cadenas, números y tipos de bool son inmutables, función obtiene una copiaalgo()obj); // los objetos se pasan por referencia y son mutable función interiorDoother Thing()str, obj); // `str` no ha cambiado, pero `obj` puede tener.
Para simular la inmutabilidad en un objeto, se pueden definir propiedades como de solo lectura (escribible: falso).
Var obj = {}Objeto.definir Propiedad()obj, 'foo ', {} valor: 'bar ', writable: falso });obj.Foo = 'bar2 '; // silenciosamente ignorado
Sin embargo, el enfoque anterior todavía permite agregar nuevas propiedades. Alternativamente, uno puede usar Object.freeze para hacer que los objetos existentes sean inmutables.
Var obj = {} Foo: 'bar ' };Objeto.congelación()obj);obj.Foo = Bares '; // no puede editar la propiedad, ignorado silenciosamenteobj.foo2 = 'bar2 '; // no puede añadir propiedad, ignorado silenciosamente
Con la implementación de ECMA262, JavaScript tiene la capacidad de crear referencias inmutables que no se pueden reasignar. Sin embargo, usar una declaración const
no significa que el valor de la referencia de solo lectura sea inmutable, solo que el nombre no se puede asignar a un nuevo valor.
const ALWAYS_IMMUTABLE = verdadero;Prueba {} ALWAYS_IMMUTABLE = falso;} captura ()err) {} consola.log()"No puedo reasignar una referencia inmutable".);}const arr = [1, 2, 3];arr.empujar()4);consola.log()arr); // [1, 2, 3, 4]
El uso del estado inmutable se ha convertido en una tendencia creciente en JavaScript desde la introducción de React, que favorece los patrones de gestión de estado similares a Flux, como Redux.
Perl
En Perl, se puede crear una clase inmutable con la biblioteca Moo simplemente declarando todos los atributos de solo lectura:
paquete Immutable;uso Moo;tiene valor = () es = 'ro ', # read only por defecto = 'data ', # puede ser superado por el suministro del constructor con # a value: Immutable- Confnew(value = "Something else"););1;
La creación de una clase inmutable solía requerir dos pasos: primero, crear accesores (ya sea de forma automática o manual) que impidieran la modificación de los atributos del objeto y, en segundo lugar, evitar la modificación directa de los datos de instancia de las instancias de esa clase (esto generalmente se almacenaba en una referencia hash, y podría bloquearse con la función lock_hash de Hash::Util):
paquete Immutable;uso estricto;uso advertencias;uso base qw(clase::Accesor);# crear accesores solo lectura__PACKAGE__-mk_ro_accessors()qw(valor));uso Hash: 'lock_hash ';sub nuevo {} # Clase = cambio; retorno Clase si ref()Clase); morir "Los argumentos a los nuevos deben ser claves = pares de valor de propiedadn" a) ()@_ % 2 == 0); # %defaults = () valor = 'data ', ); # $obj = {} %defaults, @_, }; Bendito. $obj, Clase; # Prevención de la modificación de los datos del objeto Lock_hash %$obj;}1;
O, con un descriptor de acceso escrito manualmente:
paquete Immutable;uso estricto;uso advertencias;uso Hash: 'lock_hash ';sub nuevo {} # Clase = cambio; retorno Clase si ref()Clase); morir "Los argumentos a los nuevos deben ser claves = pares de valor de propiedadn" a) ()@_ % 2 == 0); # %defaults = () valor = 'data ', ); # $obj = {} %defaults, @_, }; Bendito. $obj, Clase; # Prevención de la modificación de los datos del objeto Lock_hash %$obj;}# Solo accedente a leersub valor {} # Sí. = cambio; si ()# $new_value = cambio) {} # tratando de establecer un nuevo valor morir "Este objeto no puede ser modificadon"; } más {} retorno Sí.-{}valor} }}1;
Pitón
En Python, algunos tipos integrados (números, booleanos, cadenas, tuplas, conjuntos congelados) son inmutables, pero las clases personalizadas generalmente son mutables. Para simular la inmutabilidad en una clase, se podría anular la configuración y eliminación de atributos para generar excepciones:
clase ImmutablePoint: ""Una clase inmutable con dos atributos 'x' y 'y'."" __slots__ = ['x ', 'y '] def __setattr__()auto, *args): aumento TipoError()"No puede modificar la instancia inmutable".) __delattr__ = __setattr__ def __init_()auto, x, Sí.): # Ya no podemos usarnos. valor = valor para almacenar los datos de instancia # Así que debemos llamar explícitamente a la superclase super().__setattr__()'x ', x) super().__setattr__()'y ', Sí.)
Los asistentes de biblioteca estándar collections.namedtuple y typing.NamedTuple, disponibles desde Python 3.6 en adelante, crean clases inmutables simples. El siguiente ejemplo es más o menos equivalente al anterior, además de algunas características similares a las tuplas:
desde mecanografía importación Nombreimportación coleccionesPunto = colecciones.Nombrado()'Point ', ['x ', 'y '])# the following creates a similar namedtuple to the aboveclase Punto()Nombre): x: int Sí.: int
Introducidas en Python 3.7, las clases de datos permiten a los desarrolladores emular la inmutabilidad con instancias congeladas. Si se crea una clase de datos congelada, dataclasses
anulará __setattr__()
y __delattr__()
para generar FrozenInstanceError
si se invoca.
desde dataclasses importación Dataclass@dataclass()congelado=Cierto.)clase Punto: x: int Sí.: int
Raqueta
Racket difiere sustancialmente de otras implementaciones de Scheme al hacer que su tipo de par central ("celdas de contras") sea inmutable. En su lugar, proporciona un tipo de par mutable paralelo, a través de mcons
, mcar
, set-mcar!
etc. Además, se admiten muchos tipos inmutables, por ejemplo, cadenas y vectores inmutables, y estos se usan ampliamente. Las nuevas estructuras son inmutables de forma predeterminada, a menos que un campo se declare específicamente mutable, o toda la estructura:
()struct Foo1 ()x Sí.) ; todos los campos inmutables()struct foo2 ()x [Sí. #:mutable]) ; un campo mutable()struct Foo3 ()x Sí.) #:mutable) ; todos los campos mutables
El lenguaje también admite tablas hash inmutables, implementadas funcionalmente y diccionarios inmutables.
Óxido
El sistema de propiedad de Rust permite a los desarrolladores declarar variables inmutables y pasar referencias inmutables. Por defecto, todas las variables y referencias son inmutables. Las variables mutables y las referencias se crean explícitamente con la palabra clave mut
.
Los elementos constantes en Rust siempre son inmutables.
// objetos constantes siempre son inmutablesconst ALWAYS_IMMUTABLE: bool = verdadero;struct Objeto {} x: usize, Sí.: usize,}f principal() {} // Declarar explícitamente una variable mutable Deja mut mutable_obj = Objeto {} x: 1, Sí.: 2 }; mutable_obj.x = 3; Está bien. Deja mutable_ref = "mut mutable_obj; mutable_ref.x = 1; Está bien. Deja immutable_ref = "mutable_obj; immutable_ref.x = 3; // error E0594 // por defecto, las variables son inmutables Deja immutable_obj = Objeto {} x: 4, Sí.: 5 }; immutable_obj.x = 6; // error E0596 Deja mutable_ref2 = "mut immutable_obj; // error E0596 Deja immutable_ref2 = "immutable_obj; immutable_ref2.x = 6; // error E0594 }
Escala
En Scala, cualquier entidad (en sentido estricto, un enlace) se puede definir como mutable o inmutable: en la declaración, se puede usar val
(valor) para entidades inmutables y var
(variable) para los mutables. Tenga en cuenta que aunque un enlace inmutable no se puede reasignar, aún puede hacer referencia a un objeto mutable y aún es posible llamar a métodos de mutación en ese objeto: el enlace es inmutable, pero el subyacente objeto puede ser mutable.
Por ejemplo, el siguiente fragmento de código:
val maxValue = 100Var Valor actual = 1
define una entidad inmutable maxValue
(el tipo entero se infiere en tiempo de compilación) y una entidad mutable llamada currentValue
.
De forma predeterminada, las clases de colección como List
y Map
son inmutables, por lo que los métodos de actualización devuelven una nueva instancia en lugar de mutar una existente. Si bien esto puede sonar ineficiente, la implementación de estas clases y sus garantías de inmutabilidad significan que la nueva instancia puede reutilizar los nodos existentes, lo que, especialmente en el caso de crear copias, es muy eficiente.
Contenido relacionado
Partición de espacio binario
Pitón (lenguaje de programación)
Elixir (lenguaje de programación)