¿Qué se entiende por inmutable?
¿Qué significa exactamente inmutable ? Es decir, ¿cuáles son las consecuencias de que un objeto sea mutable o inmutable? En particular, ¿por qué JavaString
los s de Java son inmutables?
Tengo entendido que el StringBuilder
tipo es algo así como un equivalente mutable a String
. ¿Cuándo usaría StringBuilder
en lugar de String
y viceversa?
Inmutable significa que una vez que el constructor de un objeto ha completado la ejecución, esa instancia no se puede modificar.
Esto es útil porque significa que puedes pasar referencias al objeto sin preocuparte de que alguien más vaya a cambiar su contenido. Especialmente cuando se trata de concurrencia, no hay problemas de bloqueo con objetos que nunca cambian.
p.ej
class Foo
{
private final String myvar;
public Foo(final String initialValue)
{
this.myvar = initialValue;
}
public String getValue()
{
return this.myvar;
}
}
Foo
no tiene que preocuparse de que la persona que llama getValue()
pueda cambiar el texto en la cadena.
Si imagina una clase similar a Foo
, pero con a StringBuilder
en lugar de a String
como miembro, puede ver que una persona que llama a getValue()
podría alterar el StringBuilder
atributo de una Foo
instancia.
También tenga cuidado con los diferentes tipos de inmutabilidad que puede encontrar: Eric Lippert escribió un artículo de blog sobre esto. Básicamente, puede tener objetos cuya interfaz sea inmutable, pero detrás de escena son estados privados mutables reales (y, por lo tanto, no se pueden compartir de forma segura entre subprocesos).
Un objeto inmutable es un objeto donde los campos internos (o al menos, todos los campos internos que afectan su comportamiento externo) no se pueden cambiar.
Las cadenas inmutables tienen muchas ventajas:
Rendimiento: Realice la siguiente operación:
String substring = fullstring.substring(x,y);
La C subyacente para el método substring() es probablemente algo como esto:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
¡ Tenga en cuenta que no es necesario copiar ninguno de los caracteres! Si el objeto String fuera mutable (los caracteres podrían cambiar más adelante), entonces tendría que copiar todos los caracteres; de lo contrario, los cambios en los caracteres de la subcadena se reflejarían en la otra cadena más adelante.
Concurrencia: si la estructura interna de un objeto inmutable es válida, siempre será válida. No hay posibilidad de que diferentes subprocesos puedan crear un estado no válido dentro de ese objeto. Por lo tanto, los objetos inmutables son Thread Safe .
Recolección de basura: es mucho más fácil para el recolector de basura tomar decisiones lógicas sobre objetos inmutables.
Sin embargo, la inmutabilidad también tiene desventajas:
Rendimiento: Espera, ¡pensé que habías dicho que el rendimiento era una ventaja de la inmutabilidad! Bueno, a veces lo es, pero no siempre. Tome el siguiente código:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
Las dos líneas reemplazan el cuarto carácter con la letra "a". El segundo fragmento de código no sólo es más legible, sino que también es más rápido. Mira cómo tendrías que hacer el código subyacente para foo. Las subcadenas son fáciles, pero ahora como ya hay un carácter en el espacio cinco y algo más podría estar haciendo referencia a foo, no puedes simplemente cambiarlo; tienes que copiar la cadena completa (por supuesto, parte de esta funcionalidad se abstrae en funciones en el C subyacente real, pero el punto aquí es mostrar el código que se ejecuta en un solo lugar).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
Tenga en cuenta que se llama a concatenate dos veces , lo que significa que se debe recorrer toda la cadena. Compare esto con el código C para la bar
operación:
bar->characters[4] = 'a';
La operación de cadena mutable es obviamente mucho más rápida.
En conclusión: en la mayoría de los casos, desea una cadena inmutable. Pero si necesita agregar e insertar mucho en una cadena, necesita mutabilidad para lograr velocidad. Si desea obtener los beneficios de seguridad de concurrencia y recolección de basura, la clave es mantener sus objetos mutables locales en un método:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
Dado que el mutable
objeto es una referencia local, no tiene que preocuparse por la seguridad de la concurrencia (solo un hilo lo toca). Y como no se hace referencia a él en ningún otro lugar, solo se asigna en la pila, por lo que se desasigna tan pronto como finaliza la llamada a la función (no tiene que preocuparse por la recolección de basura). Y obtendrá todos los beneficios de rendimiento tanto de la mutabilidad como de la inmutabilidad.
En realidad, String no es inmutable si utiliza la definición de Wikipedia sugerida anteriormente.
El estado de la cadena cambia la construcción de la publicación. Eche un vistazo al método hashcode(). String almacena en caché el valor del código hash en un campo local pero no lo calcula hasta la primera llamada de hashcode(). Esta evaluación perezosa del código hash coloca a String en una posición interesante como un objeto inmutable cuyo estado cambia, pero no se puede observar que haya cambiado sin usar la reflexión.
Entonces, tal vez la definición de inmutable debería ser un objeto cuyo cambio no se pueda observar.
Si el estado cambia en un objeto inmutable después de haber sido creado pero nadie puede verlo (sin reflexión), ¿el objeto sigue siendo inmutable?