En la programación orientada a objetos (OO) y funcional , un objeto inmutable (objeto inmutable [1] ) es un objeto cuyo estado no se puede modificar después de su creación. [2] Esto contrasta con un objeto mutable (objeto modificable), que se puede modificar después de su creación. [3] En algunos casos, un objeto se considera inmutable incluso si cambian algunos atributos utilizados internamente, pero el estado del objeto parece inmutable 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 podría seguir considerándose un objeto inmutable.
Las cadenas y otros objetos concretos se expresan normalmente como objetos inmutables para mejorar la legibilidad y la eficiencia en tiempo de ejecución en la programación orientada a objetos. Los objetos inmutables también son útiles porque son inherentemente seguros para subprocesos . [2] Otros beneficios son que son más simples de entender y razonar y ofrecen mayor seguridad que los objetos mutables. [2]
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. Algunos ejemplos incluyen factores de conversión de metros a pies o el valor de pi a varios 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.
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 puedan ser cambiables ( débilmente inmutable ). 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 . [4] Esto podría, por ejemplo, ayudar a imponer explícitamente ciertas invariantes sobre ciertos datos en el objeto que permanecen iguales durante la vida útil del objeto. En algunos lenguajes, esto se hace con una palabra clave (por ejemplo, const
en C++ , final
en Java ) que designa el campo como inmutable. Algunos lenguajes lo invierten: en OCaml , los campos de un objeto o registro son inmutables por defecto y deben marcarse explícitamente con mutable
para que lo sean.
En la mayoría de los lenguajes orientados a objetos , se puede hacer referencia a los objetos mediante referencias . Algunos ejemplos de estos lenguajes son Java , C++ , C# , VB.NET y muchos lenguajes de programación , como Perl , Python y Ruby . En este caso, importa si el estado de un objeto puede variar cuando se comparten objetos mediante referencias.
Si se sabe que un objeto es inmutable, es preferible crear una referencia a él en lugar de copiar el objeto entero. Esto se hace para conservar memoria al evitar la duplicación de datos y evitar 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 utilizar 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 resultar difícil notificar a los demás 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 gestionar los cambios en objetos mutables.
Una técnica que combina las ventajas de los objetos mutables e inmutables , y que está soportada directamente en casi todo el hardware moderno, es la copia en escritura (COW). Con esta técnica, cuando un usuario pide al sistema que copie un objeto, este simplemente crea una nueva referencia que sigue apuntando 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 a esa copia y establece la referencia para que haga referencia a la nueva copia. Los demás usuarios no se ven afectados, porque siguen haciendo referencia al objeto original. Por lo tanto, con 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 todo lo que un programa de aplicación pueda hacer.
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, típicamente representadas como punteros o números enteros, son iguales. Algunos lenguajes hacen esto automáticamente: por ejemplo, Python interna automáticamente las cadenas cortas . Si se garantiza que el algoritmo que implementa interning lo hará en cada caso en que sea posible, entonces comparar objetos para determinar su 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 integral, aún existe la posibilidad de una mejora rápida en el caso de que los objetos sean iguales y usen la misma referencia). El interning generalmente solo es útil para objetos inmutables.
Los objetos inmutables pueden ser útiles en aplicaciones multiproceso. Varios subprocesos pueden actuar sobre datos representados por objetos inmutables sin preocuparse de que otros subprocesos modifiquen los datos. Por lo tanto, los objetos inmutables se consideran más seguros para subprocesos que los objetos mutables.
La inmutabilidad no implica que el objeto tal como está 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 de manera absoluta (por ejemplo, eludiendo el sistema de tipos o violando la corrección constante en C o C++ ).
En Python , Java [5] : 80 y .NET Framework , las cadenas son objetos inmutables. Tanto Java como .NET Framework tienen versiones mutables de cadena. En Java [5] : 84 son StringBuffer
y StringBuilder
(versiones mutables de Java String
) y en .NET es StringBuilder
(versión mutable de .Net String
). Python 3 tiene una variante de cadena mutable (bytes), denominada bytearray
. [6]
Además, todas las clases contenedoras primitivas en Java son inmutables.
Patrones similares son la Interfaz Inmutable y el Envoltorio Inmutable.
En los lenguajes de programación funcional pura no es posible crear objetos mutables sin extender 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.
En Ada , cualquier objeto se declara variable (es decir, mutable; generalmente el valor predeterminado implícito) o constant
(es decir, inmutable) mediante la constant
palabra clave.
tipo Some_type es nuevo entero ; -- podría ser cualquier cosa más complicada x : constante Some_type := 1 ; -- inmutable y : Some_type ; -- mutable
Los parámetros del subprograma son inmutables en el modo in y mutables en los modos in out y out .
procedimiento Hazlo ( a : in Integer ; b : in out Integer ; c : out Integer ) es begin -- a es inmutable b := b + a ; c := a ; end Hazlo ;
En C# puedes imponer la inmutabilidad de los campos de una clase con la readonly
declaración. [7] : 239
Al imponer todos los campos como inmutables, obtienes un tipo inmutable.
clase AnImmutableType { público de solo lectura doble _valor ; público AnImmutableType ( doble x ) { _valor = x ; } público AnImmutableType Square () { devolver nuevo AnImmutableType ( _valor * _valor ); } }
C# tiene registros que son inmutables. [8] [9]
registrar Persona ( cadena Nombre , cadena Apellido );
En C++, una implementación de constante-correctaCart
permitiría al usuario crear instancias de la clase y luego usarlas como const
(inmutables) o mutables, según se desee, proporcionando dos versiones diferentes del items()
método. (Observe que en C++ no es necesario —y de hecho es imposible— proporcionar un constructor especializado para const
instancias).
clase Carrito { público : Carrito ( std :: vector < Item > artículos ) : artículos_ ( artículos ) {} std :: vector < Item >& items () { devolver items_ ; } const std :: vector < Item >& items () const { devolver items_ ; } int ComputeTotalCost () const { /* devuelve la suma de los precios */ } privado : std :: vector < Item > items_ ; };
Tenga en cuenta que, cuando hay un miembro de datos que es un puntero o referencia a otro objeto, entonces es posible mutar el objeto apuntado o referenciado solo dentro de un método no constante.
C++ también proporciona inmutabilidad abstracta (a diferencia de la inmutabilidad bit a bit) a través de la mutable
palabra clave, que permite cambiar una variable miembroconst
desde dentro de un método.
clase Carrito { público : Carrito ( std :: vector < Item > artículos ) : artículos_ ( artículos ) {} const std :: vector < Item >& items () const { return items_ ; } int ComputeTotalCost () const { if ( costo_total_ ) { return * costo_total_ ; } int costo_total = 0 ; for ( const auto & item : items_ ) { costo_total += item . Cost (); } costo_total_ = costo_total ; return costo_total ; } privado : std :: vector < Item > items_ ; mutable std :: opcional < int > costo_total_ ; };
En D , existen dos calificadores de tipo , const
y immutable
, para variables que no se pueden cambiar. [10] A diferencia de C++ const
, Java final
y C# readonly
, son transitivos y se aplican recursivamente a cualquier cosa alcanzable a través de referencias de dicha variable. La diferencia entre const
y immutable
es a qué se aplican: const
es una propiedad de la variable: pueden existir legalmente referencias mutables al valor al que se hace referencia, es decir, el valor puede cambiar realmente. Por el contrario, immutable
es una propiedad del valor al que se hace referencia: el valor y cualquier cosa transitivamente alcanzable desde él no pueden cambiar (sin romper el sistema de tipos, lo que lleva a un comportamiento indefinido ). Cualquier referencia a ese valor debe estar marcada const
o immutable
. Básicamente, para cualquier tipo no calificado T
, const(T)
es la unión disjunta de T
(mutable) y immutable(T)
.
clase C { /*mutable*/ Objeto mField ; constante Objeto cField ; inmutable Objeto iField ; }
C
En el caso de un objeto mutable , mField
se puede escribir en él. En el caso de un const(C)
objeto, mField
no se puede modificar, hereda const
; iField
sigue siendo inmutable, ya que es la garantía más sólida. En el caso de un immutable(C)
, todos los campos son inmutables.
En una función como esta:
void func ( C m , const C c , immutable C i ) { /* dentro de las llaves */ }
Dentro de las llaves, c
podría hacer referencia al mismo objeto que m
, por lo que las mutaciones a también m
podrían cambiar indirectamente . Además, podría hacer referencia al mismo objeto que , pero como el valor es inmutable, no hay cambios. Sin embargo, y no pueden hacer referencia legal al mismo objeto.c
c
i
m
i
En el lenguaje de las garantías, mutable no tiene garantías (la función puede cambiar el objeto), const
es una garantía solo externa de que la función no cambiará nada y immutable
es una garantía bidireccional (la función no cambiará el valor y quien lo llama no debe cambiarlo).
Valores que se inicializan const
o immutable
deben inicializarse mediante asignación directa en el punto de declaración o mediante un constructor .
Debido a que const
los parámetros 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 const(S)
valores tipificados para argumentos mutables, constantes e inmutables. Por el contrario, una función de tipo inout(S) function(inout(T))
devuelve S
para T
argumentos mutables, const(S)
para const(T)
valores y immutable(S)
para immutable(T)
valores.
La conversión de valores inmutables a mutables genera un comportamiento indefinido al cambiar, incluso si el valor original proviene de un origen mutable. La conversión de valores mutables a inmutables puede ser legal cuando no quedan referencias mutables posteriormente. "Una expresión puede convertirse de mutable (...) a inmutable si la expresión es única y todas las expresiones a las que se refiere transitivamente son únicas o inmutables". [10] Si el compilador no puede probar la unicidad, la conversión se puede realizar explícitamente y es responsabilidad del programador asegurarse de que no existan referencias mutables.
El tipo string
es un alias para immutable(char)[]
, es decir, una porción tipificada de memoria de caracteres inmutables. [11] Crear subcadenas es económico, ya que solo copia y modifica un puntero y un campo de longitud, y 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.
Al hacer una copia superficial de un valor constante o inmutable se elimina la capa exterior de inmutabilidad: al copiar una cadena inmutable ( immutable(char[])
) se devuelve una cadena ( immutable(char)[]
). Se copian el puntero y la longitud inmutables y las copias son mutables. Los datos a los que se hace referencia no se han copiado y mantienen su calificador, en el ejemplo immutable
. Se pueden eliminar haciendo una copia más profunda, por ejemplo, utilizando la dup
función .
Un ejemplo clásico de un objeto inmutable es una instancia de la String
clase Java
String s = "ABC" ; s . toLowerCase (); // ¡Esto no logra nada!
El método toLowerCase()
no cambia los datos "ABC" que s
contiene. 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 devuelve una referencia a este objeto String toLowerCase()
. Para que el String s
contenga los datos "abc", se necesita un enfoque diferente:
s = s .toLowerCase ( );
Ahora, la cadena 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 la hace inmutable.
La palabra clave final
( artículo detallado ) se utiliza para implementar tipos primitivos inmutables y referencias a objetos, [12] pero no puede, por sí sola, hacer que los objetos sean inmutables. Vea los ejemplos a continuación:
Las variables de tipo primitivo ( int
, long
, short
, etc.) se pueden reasignar después de ser definidas. Esto se puede evitar utilizando final
.
int i = 42 ; //int es un tipo primitivo i = 43 ; // OK final int j = 42 ; j = 43 ; // no se compila. j es final, por lo que no se puede reasignar
Los tipos de referencia no se pueden hacer inmutables simplemente usando la final
palabra clave. final
solo evita la reasignación.
final MyObject m = new MyObject (); //m es del tipo de referencia m . data = 100 ; // OK. Podemos cambiar el estado del objeto m (m es mutable y final no cambia este hecho) m = new MyObject (); // no compila. m es final, por lo que no se puede reasignar
Los contenedores primitivos ( Integer
, Long
, Short
, Double
, Float
, Character
, Byte
, Boolean
) también son inmutables. Las clases inmutables se pueden implementar siguiendo algunas pautas simples. [13]
En JavaScript , todos los tipos primitivos (Undefined, Null, Boolean, Number, BigInt, String, Symbol) son inmutables, pero los objetos personalizados generalmente son mutables.
function doSomething ( x ) { /* ¿cambiar x aquí cambia el original? */ }; var str = 'a string' ; var obj = { an : 'object' }; doSomething ( str ); // las cadenas, los números y los tipos bool son inmutables, la función obtiene una copia doSomething ( obj ); // los objetos se pasan por referencia y son mutables dentro de la función doAnotherThing ( str , obj ); // `str` no ha cambiado, pero `obj` puede haberlo hecho.
Para simular la inmutabilidad de un objeto, se pueden definir propiedades como de sólo lectura (escribibles: falso).
var obj = {}; Object . defineProperty ( obj , 'foo' , { value : 'bar' , writable : false }); obj . foo = 'bar2' ; // ignorado silenciosamente
Sin embargo, el método anterior permite agregar nuevas propiedades. Otra opción es usar Object.freeze para hacer que los objetos existentes sean inmutables.
var obj = { foo : 'bar' }; Object . freeze ( obj ); obj . foo = 'bars' ; // no se puede editar la propiedad, se ignora silenciosamente obj . foo2 = 'bar2' ; // no se puede agregar la propiedad, se ignora silenciosamente
Con la implementación de ECMA262, JavaScript tiene la capacidad de crear referencias inmutables que no se pueden reasignar. Sin embargo, el uso de una const
declaración no significa que el valor de la referencia de solo lectura sea inmutable, sino que el nombre no se puede asignar a un nuevo valor.
const SIEMPRE_INMUTABLE = verdadero ; try { ALWAYS_IMMUTABLE = false ; } catch ( err ) { console.log ( "No se puede reasignar una referencia inmutable. " ) ; } constante arr = [ 1 , 2 , 3 ]; arr . push ( 4 ); console . log ( arr ); // [1, 2, 3, 4]
El uso de estados inmutables se ha convertido en una tendencia creciente en JavaScript desde la introducción de React , que favorece patrones de gestión de estados similares a Flux, como Redux . [14]
En Perl , se puede crear una clase inmutable con la biblioteca Moo simplemente declarando todos los atributos como de solo lectura:
paquete Inmutable ; usa Moo ; tiene valor => ( es => 'ro' , # solo lectura predeterminado => 'data' , # se puede anular proporcionando al constructor # un valor: Immutable->new(value => 'something else'); ); 1 ;
La creación de una clase inmutable solía requerir dos pasos: primero, crear accesores (automáticos o manuales) que impidieran la modificación de los atributos del objeto y, segundo, impedir la modificación directa de los datos de instancia de las instancias de esa clase (esto generalmente se almacenaba en una referencia hash y se podía bloquear con la función lock_hash de Hash::Util):
paquete Inmutable ; uso estricto ; uso advertencias ; uso base qw(Class::Accessor) ; # crear accesores de solo lectura __PACKAGE__ -> mk_ro_accessors ( qw(value) ); uso Hash::Util 'lock_hash' ; sub new { my $class = shift ; return $class if ref ( $class ); die "Los argumentos para new deben ser pares clave => valor\n" a menos que ( @_ % 2 == 0 ); my %defaults = ( value => 'data' , ); my $obj = { %defaults , @_ , }; bless $obj , $class ; # evitar la modificación de los datos del objeto lock_hash %$obj ; } 1 ;
O bien, con un accesor escrito manualmente:
paquete Inmutable ; uso estricto ; uso advertencias ; uso Hash::Util 'lock_hash' ; sub new { my $class = shift ; return $class if ref ( $class ); die "Los argumentos para new deben ser pares clave => valor\n" a menos que ( @_ % 2 == 0 ); my %defaults = ( value => 'data' , ); my $obj = { %defaults , @_ , }; bless $obj , $class ; # evitar la modificación de los datos del objeto lock_hash %$obj ; } # accesor de solo lectura sub valor { my $self = shift ; if ( my $new_value = shift ) { # intentando establecer un nuevo valor die "Este objeto no se puede modificar\n" ; } else { return $self -> { valor } } } 1 ;
En Python , algunos tipos integrados (números, valores 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' e 'y'.""" __ranuras__ = [ 'x' , 'y' ] def __setattr__ ( self , * args ): raise TypeError ( "No se puede modificar la instancia inmutable." ) __delattr__ = __setattr__ def __init__ ( self , x , y ): # Ya no podemos usar self.value = value para almacenar los datos de la instancia , # por lo que debemos llamar explícitamente a la superclase super () .__ setattr__ ( 'x' , x ) super () .__ setattr__ ( 'y' , y )
Las bibliotecas estándar de ayuda collections.namedtuple y writing.NamedTuple, disponibles a partir de Python 3.6, crean clases inmutables simples. El siguiente ejemplo es aproximadamente equivalente al anterior, más algunas características similares a las de las tuplas:
desde escribir importar NamedTuple importar coleccionesPunto = colecciones .namedtuple ( 'Punto' , [ ' x' , 'y' ]) # lo siguiente crea una tupla con nombre similar a la clase Point ( NamedTuple ) anterior: x : int y : 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
se anulará __setattr__()
y __delattr__()
se activará FrozenInstanceError
si se invoca.
desde dataclasses importar dataclass@dataclass ( frozen = True ) clase Punto : x : int y : int
Racket se diferencia sustancialmente de otras implementaciones de Scheme al hacer que su par de tipos principal ("cons cells") sea inmutable. En cambio, proporciona un par de tipos mutable paralelo, a través de mcons
, mcar
, set-mcar!
etc. Además, se admiten muchos tipos inmutables, por ejemplo, cadenas y vectores inmutables, que se utilizan ampliamente. Las estructuras nuevas son inmutables de forma predeterminada, a menos que un campo se declare específicamente como mutable, o la estructura completa:
( struct foo1 ( x y )) ; todos los campos son inmutables ( struct foo2 ( x [ y #:mutable ])) ; un campo mutable ( struct foo3 ( x y ) #:mutable ) ; todos los campos son mutables
El lenguaje también admite tablas hash inmutables, implementadas funcionalmente, y diccionarios inmutables.
El sistema de propiedad de Rust permite a los desarrolladores declarar variables inmutables y pasar referencias inmutables. De forma predeterminada, todas las variables y referencias son inmutables. Las variables y referencias mutables se crean explícitamente con la mut
palabra clave.
Los elementos constantes en Rust son siempre inmutables.
// Los elementos constantes son siempre inmutables const ALWAYS_IMMUTABLE : bool = true ; estructura Objeto { x : usize , y : usize , } fn main () { // declara explícitamente una variable mutable let mut mutable_obj = Object { x : 1 , y : 2 }; mutable_obj . x = 3 ; // ok deje que mutable_ref = & mut mutable_obj ; mutable_ref . x = 1 ; // ok deje que inmutable_ref = & mutable_obj ; inmutable_ref . x = 3 ; // error E0594 // por defecto, las variables son inmutables let immutable_obj = Object { x : 4 , y : 5 }; immutable_obj . x = 6 ; // error E0596 deje que mutable_ref2 = & mut inmutable_obj ; // error E0596 deje que inmutable_ref2 = & inmutable_obj ; inmutable_ref2 . x = 6 ; // error E0594 }
En Scala , cualquier entidad (en sentido estricto, un enlace) se puede definir como mutable o inmutable: en la declaración, se puede utilizar val
(valor) para entidades inmutables y var
(variable) para entidades 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 objeto subyacente puede ser mutable.
Por ejemplo, el siguiente fragmento de código:
val valormax = 100 var valoractual = 1
define una entidad inmutable maxValue
(el tipo entero se infiere en tiempo de compilación) y una entidad mutable denominada currentValue
.
De manera 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 parecer ineficiente, la implementación de estas clases y sus garantías de inmutabilidad significan que la nueva instancia puede reutilizar nodos existentes, lo que, especialmente en el caso de crear copias, es muy eficiente. [15] [ se necesita una mejor fuente ]
Este artículo contiene material del libro Patrones de diseño de Perl
La forma preferida es hacer que la clase sea final. Esto a veces se conoce como "inmutabilidad fuerte". Evita que alguien extienda su clase y la haga mutable accidental o deliberadamente.