Pregunta

¿Por qué decidieron hacer que las cadenas sean inmutables en Java y .NET (y algunos otros lenguajes)?¿Por qué no lo hicieron mutable?

¿Fue útil?

Solución

De acuerdo a Java efectivo, capítulo 4, página 73, 2ª edición:

"Hay muchas buenas razones para ello:Las clases inmutables son más fáciles de diseñar, implementar y usar que las clases mutables.Son menos propensos al error y son más seguros.

[...]

"Los objetos inmutables son simples. Un objeto inmutable puede estar exactamente en un estado, el estado en el que fue creado.Si se asegura de que todos los constructores establezcan invariantes de clase, entonces se garantiza que estos invariantes seguirán siendo ciertos para siempre, sin ningún esfuerzo por su parte.

[...]

Los objetos inmutables son inherentemente seguros para subprocesos;no requieren sincronización. No pueden ser corrompidos por múltiples hilos que acceden al mismo tiempo.Este es de lejos el enfoque más fácil para lograr la seguridad del hilo.De hecho, ningún hilo puede observar cualquier efecto de otro hilo en un objeto inmutable.Por lo tanto, Los objetos inmutables se pueden compartir libremente.

[...]

Otros pequeños puntos del mismo capítulo:

No solo puedes compartir objetos inmutables, sino que también puedes compartir sus partes internas.

[...]

Los objetos inmutables son excelentes bloques de construcción para otros objetos, ya sean mutables o inmutables.

[...]

La única desventaja real de las clases inmutables es que requieren un objeto separado para cada valor distinto.

Otros consejos

Hay al menos dos razones.

Primero - seguridad http://www.javafaq.nu/java-article1060.html

La razón principal por la que la cadena se hizo inmutable fue la seguridad.Mira este ejemplo:Tenemos un método de archivo de archivo con verificación de inicio de sesión.Pasamos una cadena a este método para procesar la autenticación que es necesaria antes de que la llamada se pase al sistema operativo.Si la cadena era mutable, era posible de alguna manera modificar su contenido después de la verificación de autenticación antes de que el sistema operativo reciba el programa, entonces es posible solicitar cualquier archivo.Entonces, si tiene el derecho de abrir el archivo de texto en el directorio de usuario, pero luego en la marcha, cuando de alguna manera se las arregla para cambiar el nombre del archivo que puede solicitar para abrir el archivo "passwd" o cualquier otro.Luego, se puede modificar un archivo y será posible iniciar sesión directamente en el sistema operativo.

Segundo: eficiencia de la memoria http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVM mantiene internamente el "grupo de cuerdas".Para lograr la eficiencia de memoria, JVM referirá el objeto de cadena desde el grupo.No creará los nuevos objetos de cadena.Entonces, cada vez que cree una nueva cadena literal, JVM verificará en la piscina si ya existe o no.Si ya está presente en el grupo, simplemente dé la referencia al mismo objeto o cree el nuevo objeto en el grupo.Habrá muchas referencias a los mismos objetos de cadena, si alguien cambia el valor, afectará todas las referencias.Entonces, Sun decidió hacerlo inmutable.

En realidad, las razones por las que las cadenas son inmutables en Java no tienen mucho que ver con la seguridad.Las dos razones principales son las siguientes:

Seguridad de la cabeza:

Las cadenas son un tipo de objeto muy utilizado.Por lo tanto, está más o menos garantizado su uso en un entorno multiproceso.Las cadenas son inmutables para garantizar que sea seguro compartir cadenas entre subprocesos.Tener cadenas inmutables garantiza que al pasar cadenas del hilo A a otro hilo B, el hilo B no pueda modificar inesperadamente la cadena del hilo A.

Esto no sólo ayuda a simplificar la ya bastante complicada tarea de la programación multiproceso, sino que también ayuda con el rendimiento de las aplicaciones multiproceso.El acceso a objetos mutables debe sincronizarse de alguna manera cuando se puede acceder a ellos desde múltiples subprocesos, para garantizar que un subproceso no intente leer el valor de su objeto mientras otro subproceso lo modifica.La sincronización adecuada es difícil de realizar correctamente para el programador y costosa en tiempo de ejecución.Los objetos inmutables no se pueden modificar y, por lo tanto, no necesitan sincronización.

Actuación:

Si bien se ha mencionado la pasantía de String, solo representa una pequeña ganancia en la eficiencia de la memoria para los programas Java.Sólo se internan cadenas literales.Esto significa que sólo las cadenas que son iguales en su código fuente compartirá el mismo objeto de cadena.Si su programa crea dinámicamente cadenas que son iguales, se representarán en objetos diferentes.

Más importante aún, las cadenas inmutables les permiten compartir sus datos internos.Para muchas operaciones de cadenas, esto significa que no es necesario copiar la matriz de caracteres subyacente.Por ejemplo, digamos que desea tomar los cinco primeros caracteres de String.En Java, llamarías myString.substring(0,5).En este caso, lo que hace el método substring() es simplemente crear un nuevo objeto String que comparte el char[] subyacente de myString, pero quién sabe si comienza en el índice 0 y termina en el índice 5 de ese char[].Para poner esto en forma gráfica, terminarías con lo siguiente:

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

Esto hace que este tipo de operaciones sean extremadamente económicas, y O(1), ya que la operación no depende de la longitud de la cadena original ni de la longitud de la subcadena que necesitamos extraer.Este comportamiento también tiene algunos beneficios de memoria, ya que muchas cadenas pueden compartir su carácter subyacente [].

Seguridad y rendimiento del hilo.Si una cadena no se puede modificar, es seguro y rápido pasar una referencia entre varios subprocesos.Si las cadenas fueran mutables, siempre tendría que copiar todos los bytes de la cadena a una nueva instancia o proporcionar sincronización.Una aplicación típica leerá una cadena 100 veces por cada vez que sea necesario modificarla.Ver wikipedia en inmutabilidad.

Uno realmente debería preguntar: "¿Por qué debería ser X mutable?" Es mejor por defecto a la inmutabilidad, debido a los beneficios ya mencionados por princesa pelusa.Debería ser una excepción que algo sea mutable.

Desafortunadamente, la mayoría de los lenguajes de programación actuales tienen por defecto la mutabilidad, pero es de esperar que en el futuro el valor predeterminado sea más la inmutabilidad (ver Una lista de deseos para el próximo lenguaje de programación convencional).

Un factor es que, si las cadenas fueran mutables, los objetos que almacenan cadenas tendrían que tener cuidado al almacenar copias, para que sus datos internos no cambien sin previo aviso.Dado que las cadenas son un tipo bastante primitivo como los números, es bueno poder tratarlas como si se pasaran por valor, incluso si se pasan por referencia (lo que también ayuda a ahorrar memoria).

¡Guau!No puedo creer la desinformación aquí.Las cadenas que son inmutables no tienen nada que ver con la seguridad.Si alguien ya tiene acceso a los objetos en una aplicación en ejecución (lo que se debe asumir si está tratando de protegerse contra alguien que "hackee" una cadena en su aplicación), ciertamente habrá muchas otras oportunidades disponibles para piratear.

Es una idea bastante novedosa que la inmutabilidad de String resuelva problemas de subprocesamiento.Mmm ...Tengo un objeto que está siendo modificado por dos subprocesos diferentes.¿Cómo resuelvo esto?sincronizar el acceso al objeto?Naawww...No permitamos que nadie cambie el objeto en absoluto: ¡eso solucionará todos nuestros complicados problemas de concurrencia!De hecho, hagamos que todos los objetos sean inmutables y luego podremos eliminar la construcción sincronizada del lenguaje Java.

La verdadera razón (señalada por otros anteriormente) es la optimización de la memoria.Es bastante común en cualquier aplicación que la misma cadena literal se use repetidamente.De hecho, es tan común que hace décadas, muchos compiladores optimizaron el almacenamiento de una sola instancia de una cadena literal.El inconveniente de esta optimización es que el código de tiempo de ejecución que modifica un literal de cadena introduce un problema porque está modificando la instancia de todos los demás códigos que la comparten.Por ejemplo, no sería bueno que una función en algún lugar de una aplicación cambiara la cadena literal "perro" a "gato".Un printf("perro") daría como resultado que se escriba "gato" en la salida estándar.Por esa razón, era necesario que hubiera una forma de protegerse contra el código que intenta cambiar los literales de cadena (es decir,es decir, hacerlos inmutables).Algunos compiladores (con soporte del sistema operativo) lograrían esto colocando una cadena literal en un segmento de memoria especial de solo lectura que causaría una falla de memoria si se intentara escribir.

En Java esto se conoce como pasantía.El compilador de Java aquí simplemente sigue una optimización de memoria estándar realizada por los compiladores durante décadas.Y para abordar el mismo problema de que estos literales de cadena se modifiquen en tiempo de ejecución, Java simplemente hace que la clase String sea inmutable (es decir,e, no le proporciona configuradores que le permitan cambiar el contenido de la cadena).Las cadenas no tendrían que ser inmutables si no se produjera la internación de literales de cadena.

String no es un tipo primitivo, pero normalmente querrás usarlo con semántica de valores, es decir.como un valor.

Un valor es algo en lo que puedes confiar que no cambiará a tus espaldas.Si tú escribes: String str = someExpr();No desea que cambie a menos que USTED haga algo con str.

La cadena como objeto tiene una semántica de puntero natural, para obtener también una semántica de valor debe ser inmutable.

Sé que esto es un golpe, pero...¿Son realmente inmutables?Considera lo siguiente.

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

Incluso podrías convertirlo en un método de extensión.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Lo que hace que lo siguiente funcione

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Conclusión:Están en un estado inmutable que el compilador conoce.Por supuesto, lo anterior sólo se aplica a cadenas .NET ya que Java no tiene punteros.Sin embargo, una cadena puede ser completamente mutable usando punteros en C#.No se trata de cómo se deben usar los punteros, cómo tienen un uso práctico o cómo se usan de manera segura;sin embargo, es posible, invirtiendo así toda la regla "mutable".Normalmente no se puede modificar un índice directamente de una cadena y esta es la única manera.Hay una manera de evitar esto al no permitir instancias de cadenas de puntero o hacer una copia cuando se apunta a una cadena, pero no se hace ninguna de las dos cosas, lo que hace que las cadenas en C# no sean completamente inmutables.

Para la mayoría de los propósitos, una "cadena" es (se usa/trata como/se piensa/se supone que es) un significado. unidad atómica, como un numero.

Por lo tanto, preguntar por qué los caracteres individuales de una cadena no son mutables es como preguntar por qué los bits individuales de un número entero no son mutables.

Deberías saber por qué.Solo piensa en ello.

Odio decirlo, pero desafortunadamente estamos debatiendo esto porque nuestro idioma apesta y estamos tratando de usar una sola palabra, cadena, para describir un concepto o clase de objeto complejo y contextualmente situado.

Realizamos cálculos y comparaciones con "cadenas" de forma similar a como lo hacemos con los números.Si las cadenas (o números enteros) fueran mutables, tendríamos que escribir un código especial para bloquear sus valores en formas locales inmutables para poder realizar cualquier tipo de cálculo de manera confiable.Por lo tanto, es mejor pensar en una cadena como un identificador numérico, pero en lugar de tener 16, 32 o 64 bits de longitud, podría tener cientos de bits.

Cuando alguien dice "cuerda", todos pensamos en cosas diferentes.Aquellos que lo consideran simplemente como un conjunto de personajes, sin ningún propósito particular en mente, se horrorizarán de que alguien acabo de decidir que no deberían poder manipular a esos personajes.Pero la clase "cadena" no es sólo una serie de caracteres.Es un STRING, No un char[].Existen algunas suposiciones básicas sobre el concepto al que nos referimos como "cadena" y, en general, se puede describir como una unidad atómica significativa de datos codificados, como un número.Cuando la gente habla de "manipular cadenas", tal vez en realidad estén hablando de manipular caracteres para construir instrumentos de cuerda, y un StringBuilder es genial para eso.Piense un poco en lo que realmente significa la palabra "cadena".

Consideremos por un momento cómo sería si las cuerdas fueran mutables.Se podría engañar a la siguiente función API para que devuelva información para un usuario diferente si el mudable La cadena de nombre de usuario es modificada intencionalmente o no por otro hilo mientras esta función la usa:

string GetPersonalInfo( string username, string password )
{
    string stored_password = DBQuery.GetPasswordFor( username );
    if (password == stored_password)
    {
        //another thread modifies the mutable 'username' string
        return DBQuery.GetPersonalInfoFor( username );
    }
}

La seguridad no se trata sólo de "control de acceso", sino también de "seguridad" y "garantía de corrección".Si no se puede escribir fácilmente un método y no se puede confiar en él para realizar un cálculo simple o una comparación de manera confiable, entonces no es seguro llamarlo, pero sí sería seguro cuestionar el lenguaje de programación en sí.

La inmutabilidad no está tan estrechamente ligada a la seguridad.Para eso, al menos en .NET, obtienes la clase SecureString.

Es una compensación.Las cadenas van al grupo de cadenas y cuando crea varias cadenas idénticas, comparten la misma memoria.Los diseñadores pensaron que esta técnica de ahorro de memoria funcionaría bien para el caso común, ya que los programas tienden a trabajar mucho sobre las mismas cadenas.

La desventaja es que las concatenaciones generan muchas cadenas adicionales que son solo transicionales y se convierten en basura, lo que en realidad daña el rendimiento de la memoria.Tiene StringBuffer y StringBuilder (en Java, StringBuilder también está en .NET) para conservar la memoria en estos casos.

La decisión de tener cadenas mutables en C++ causa muchos problemas; consulte este excelente artículo de Kelvin Henney sobre Enfermedad de las vacas locas.

VACA = Copiar al escribir.

Las cadenas en Java no son realmente inmutables; puede cambiar sus valores mediante la reflexión o la carga de clases.No debería depender de esa propiedad por seguridad.Para ejemplos ver: Truco de magia en Java

La inmutabilidad es buena.Consulte Java efectivo.Si tuviera que copiar una cadena cada vez que la pasara, entonces sería una gran cantidad de código propenso a errores.También tiene confusión sobre qué modificaciones afectan a qué referencias.De la misma manera que Integer tiene que ser inmutable para comportarse como int, Strings tiene que comportarse como inmutable para actuar como primitivos.En C++, pasar cadenas por valor lo hace sin mencionarlo explícitamente en el código fuente.

Hay una excepción para casi todas las reglas:

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

Es en gran parte por razones de seguridad.Es mucho más difícil proteger un sistema si no puede confiar en que sus cadenas sean a prueba de manipulaciones.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top