¿Por qué las cadenas no pueden ser mutables en Java y .NET?

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

De acuerdo con Effective Java , capítulo 4, página 73, 2da edición:

“Hay muchas buenas razones para esto: las clases inmutables son más fáciles de diseñar, implementar y usar que las clases mutables. Son menos propensas a errores y son más seguras.

[…]

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 estas invariantes se mantendrán verdaderas para siempre. sin esfuerzo de tu parte.

[…]

Los objetos inmutables son inherentemente seguros para hilos; no requieren sincronización No pueden ser dañados por múltiples hilos que acceden a ellos simultáneamente. Este es de lejos el enfoque más fácil para lograr la seguridad de la rosca. De hecho, ningún hilo puede observar ningún 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 puedes compartir sus elementos internos.

[…]

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.

Hay al menos dos razones.

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

La razón principal por la cual String hizo inmutable fue la seguridad. Mire este ejemplo: tenemos un método de abrir archivo con verificación de inicio de sesión. Pasamos un String a este método para procesar la autenticación que es necesaria antes de que la llamada pase al sistema operativo. Si String era mutable, de alguna manera era posible modificar su contenido después de la verificación de autenticación antes de que OS obtenga la solicitud del progtwig, luego es posible solicitar cualquier archivo. Por lo tanto, si tiene derecho a abrir el archivo de texto en el directorio de usuario pero luego sobre la marcha cuando de alguna manera logra cambiar el nombre del archivo, puede solicitar abrir el archivo “passwd” o cualquier otro. Entonces se puede modificar un archivo y será posible iniciar sesión directamente en el sistema operativo.

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

JVM mantiene internamente el “Grupo de cadenas”. Para lograr la eficacia de la memoria, JVM derivará el objeto String del conjunto. No creará los nuevos objetos String. Por lo tanto, siempre que cree un nuevo literal de cadena, JVM comprobará en el grupo si ya existe o no. Si ya está presente en el conjunto, simplemente proporcione la referencia al mismo objeto o cree el nuevo objeto en el grupo. Habrá muchas referencias que apuntan a los mismos objetos String, si alguien cambia el valor, afectará todas las referencias. Entonces, el sol decidió hacerlo inmutable.

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

Seguridad de Thead:

Las cadenas son un tipo de objeto extremadamente utilizado. Por lo tanto, está más o menos garantizado que se utilizará en un entorno de subprocesos múltiples. 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 puede modificar inesperadamente la cadena del hilo A.

Esto no solo ayuda a simplificar la tarea ya bastante complicada de la progtwigción de subprocesos múltiples, sino que también ayuda con el rendimiento de las aplicaciones de subprocesos múltiples. El acceso a los objetos mutables se debe sincronizar de alguna manera cuando se puede acceder desde varios subprocesos, para asegurarse de que un subproceso no intente leer el valor de su objeto mientras está siendo modificado por otro subproceso. La correcta sincronización es difícil de realizar correctamente para el progtwigdor 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 el internamiento en cadena, solo representa una pequeña ganancia en la eficiencia de la memoria para los progtwigs de Java. Solo los literales de cadena están internados. Esto significa que solo las cadenas que son iguales en su código fuente compartirán el mismo objeto de cadena. Si su progtwig crea dinámicamente cadenas que son iguales, se representarán en diferentes objetos.

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

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

Esto hace que este tipo de operaciones sea extremadamente barata, 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 char subyacente [].

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

Uno realmente debería preguntarse, “¿por qué X debe ser mutable?” Es mejor cambiar a la inmutabilidad por defecto, debido a los beneficios ya mencionados por Princess Fluff . Debería ser una excepción que algo sea mutable.

Lamentablemente, la mayoría de los lenguajes de progtwigción actuales tienen mutabilidad por defecto, pero es de esperar que en el futuro el valor predeterminado sea más sobre immutablity (consulte la Lista de deseos para el próximo lenguaje de progtwigción Mainstream ).

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

String no es un tipo primitivo, pero normalmente quiere 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 escribe: String str = someExpr(); No quiere que cambie a menos que USTED haga algo con str.

String como un Objeto tiene semántica de puntero naturalmente, para obtener semántica de valor también necesita ser inmutable.

¡Guauu! No puedo creer la desinformación aquí. Las cadenas que son inmutables no tienen nada con la seguridad. Si alguien ya tiene acceso a los objetos en una aplicación en ejecución (lo que se debería suponer si está tratando de evitar que alguien “piratee” un String en su aplicación), seguramente habrá muchas otras oportunidades disponibles para piratear.

Es una idea bastante novedosa que la inmutabilidad de String está abordando problemas de enhebrado. Hmmm … Tengo un objeto que está siendo cambiado por dos hilos diferentes. ¿Cómo resuelvo esto? sincronizar el acceso al objeto? Naawww … no permitamos que nadie cambie el objeto, ¡eso solucionará todos nuestros problemas de simultaneidad! De hecho, hagamos que todos los objetos sean inmutables, y luego podemos eliminar la estructura sincrónica del lenguaje Java.

El verdadero motivo (señalado por otros más arriba) es la optimización de la memoria. Es bastante común en cualquier aplicación que se use el mismo literal de cadena repetidamente. Es tan común, de hecho, que hace décadas, muchos comstackdores hicieron la optimización de almacenar solo una instancia de un literal de cadena. El inconveniente de esta optimización es que el código de tiempo de ejecución que modifica un literal de cadena presenta un problema porque está modificando la instancia para el rest del código que lo comparte. Por ejemplo, no sería bueno para una función en alguna parte de una aplicación cambiar la cadena literal “perro” por “gato”. Un printf (“perro”) daría como resultado que “gato” se escriba en stdout. Por esa razón, debe haber una forma de protegerse contra el código que intenta cambiar los literales de cadena (es decir, hacerlos inmutables). Algunos comstackdores (con soporte del sistema operativo) logran esto colocando el literal de cadena en un segmento especial de memoria de solo lectura que causaría un error de memoria si se realizara un bash de escritura.

En Java esto se conoce como internamiento. El comstackdor de Java aquí solo sigue una optimización de memoria estándar realizada por los comstackdores durante décadas. Y para abordar el mismo problema de estos literales de cadena que se modifican en el tiempo de ejecución, Java simplemente hace que la clase String sea inmutable (es decir, no le da ningún setter que le permita cambiar el contenido de String). Las cadenas no tendrían que ser inmutables si el internamiento de literales de cadena no ocurriera.

Sé que esto es un bache, 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 cual hace que el siguiente trabajo

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

Conclusión: están en un estado inmutable que es conocido por el comstackdor. Por supuesto, lo anterior solo se aplica a cadenas .NET ya que Java no tiene punteros. Sin embargo, una cadena puede ser totalmente mutable usando punteros en C #. No se trata de cómo los punteros están destinados a ser utilizados, tienen un uso práctico o si se usan de forma segura; sin embargo, es posible, doblando así toda la regla “mutable”. Normalmente no 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 punteros o hacer una copia cuando se apunta a una cadena, pero ninguna de ellas se hace, lo que hace que las cadenas en C # no sean completamente inmutables.

Para la mayoría de los propósitos, una “cadena” es (usada / tratada como / pensada / se supone que es) una unidad atómica significativa , como un número .

Preguntar por qué los caracteres individuales de una cadena no son mutables es por lo tanto como preguntar por qué los bits individuales de un entero no son mutables.

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

Odio decirlo, pero desafortunadamente estamos debatiendo esto porque nuestro lenguaje apesta, y estamos tratando de usar una sola palabra, una cuerda , para describir un concepto complejo o una clase de objeto contextual.

Realizamos cálculos y comparaciones con “cadenas” de forma similar a como lo hacemos con los números. Si las cadenas (o enteros) fueran mutables, tendríamos que escribir un código especial para bloquear sus valores en formas locales inmutables con el fin de 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 de longitud.

Cuando alguien dice “cadena”, todos pensamos en cosas diferentes. Aquellos que piensan en ello simplemente como un conjunto de personajes, sin un propósito particular en mente, se horrorizarán de que alguien simplemente haya decidido que no deberían ser capaces de manipular a esos personajes. Pero la clase “cadena” no es solo una matriz de caracteres. Es un STRING , no un char[] . Hay algunas suposiciones básicas sobre el concepto al que nos referimos como una “cadena”, y generalmente 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 realmente están hablando de manipular caracteres para construir cadenas , y un StringBuilder es ideal para eso. Solo piensa un poco sobre lo que realmente significa la palabra “cadena”.

Considera por un momento cómo sería si las cuerdas fueran mutables. La siguiente función de API podría ser engañada para devolver información para un usuario diferente si la cadena de nombre de usuario mutable es modificada intencionalmente o no por otra cadena mientras esta función la está usando:

 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 solo 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 se depende de él para realizar un cálculo simple o una comparación confiable, entonces no es seguro llamarlo, pero sería seguro cuestionar el lenguaje de progtwigción en sí mismo.

Es un intercambio. Las cadenas entran en el grupo de cadenas y cuando crea múltiples 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 progtwigs tienden a pulular sobre las mismas cuerdas mucho.

La desventaja es que las concatenaciones crean muchas cadenas adicionales que solo son transitorias y simplemente se vuelven basura, lo que en realidad daña el rendimiento de la memoria. Tienes StringBuffer y StringBuilder (en Java, StringBuilder también está en .NET) para usar para preservar la memoria en estos casos.

La decisión de tener cadena mutable en C ++ causa muchos problemas, vea este excelente artículo de Kelvin Henney sobre la enfermedad de la vaca loca .

VACA = Copiar al escribir.

Las cadenas en Java no son realmente inmutables, puede cambiar su valor mediante el uso de reflexión y / o carga de clases. No deberías estar dependiendo de esa propiedad por seguridad. Para ver ejemplos, vea: Magic Trick en Java

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

La inmutabilidad es buena. Ver Java efectivo. Si tuviera que copiar una Cadena cada vez que la pasara, sería un 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, las cadenas deben comportarse como inmutables para actuar como primitivas. En C ++, pasar cadenas por valor lo hace sin mención explícita 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(); } } } } 

La cadena es inmutable (una vez creada no se puede cambiar) objeto. El objeto creado como una Cadena se almacena en el Conjunto de Cadenas Constantes. Todos los objetos inmutables en Java son seguros para la ejecución de subprocesos, lo que implica que Cadena también es segura para subprocesos. La cadena no puede ser utilizada por dos subprocesos simultáneamente. La cadena una vez asignada no se puede cambiar.

 String demo = " hello " ; 

// El objeto anterior se almacena en un grupo de cadenas constantes y su valor no se puede modificar.

 demo="Bye" ; 

// la nueva cadena “Bye” se crea en el conjunto constante y se hace referencia a la variable de demostración

// La cadena “hola” todavía existe en el conjunto constante de cadenas y su valor no se sobrescribe, pero hemos perdido la referencia a la cadena “hola”

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

    Intereting Posts