¿Por qué decidieron hacer String
inmutables en Java y .NET (y algunos otros lenguajes)? ¿Por qué no lo hicieron mutable?
String
es realmente mutable internamente. StringBuilder
en .NET 2.0 muta una cadena . Lo dejaré aquí.
¿Por qué decidieron hacer String
inmutables en Java y .NET (y algunos otros lenguajes)? ¿Por qué no lo hicieron mutable?
String
es realmente mutable internamente. StringBuilder
en .NET 2.0 muta una cadena . Lo dejaré aquí.
Respuestas:
Según Effective Java , capítulo 4, página 73, 2a 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 estos invariantes permanecerán verdaderos todo el tiempo, con sin esfuerzo de tu parte.
[...]
Los objetos inmutables son inherentemente seguros para subprocesos; No requieren sincronización. No pueden ser corrompidos por múltiples hilos que acceden a ellos simultáneamente. Este es, de lejos, el enfoque más fácil para lograr la seguridad del hilo. 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 puede compartir objetos inmutables, sino que también puede compartir sus elementos internos.
[...]
Los objetos inmutables son grandes 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.
report2.Text = report1.Text;
. Luego, en otro lugar, modificar el texto: report2.Text.Replace(someWord, someOtherWord);
. Esto cambiaría el primer informe y el segundo.
Hay al menos dos razones.
Primero: seguridad http://www.javafaq.nu/java-article1060.html
La razón principal por la que String se volvió inmutable fue la seguridad. Mira este ejemplo: tenemos un método para abrir archivos 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 String era mutable, de alguna manera era posible modificar su contenido después de la verificación de autenticación antes de que el sistema operativo reciba la solicitud del programa, entonces es posible solicitar cualquier archivo. Por lo tanto, si tiene derecho a abrir un archivo de texto en el directorio de usuarios pero luego sobre la marcha cuando de alguna manera logra cambiar el nombre del archivo, puede solicitar 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 "conjunto de cadenas". Para lograr la eficiencia de la memoria, JVM referirá el objeto String del grupo. No creará los nuevos objetos de cadena. Entonces, cada vez que cree un nuevo literal de cadena, JVM verificará en el grupo si ya existe o no. Si ya está presente en el grupo, solo 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á a todas las referencias. Entonces, el sol 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:
Las cadenas son un tipo de objeto extremadamente utilizado. Por lo tanto, está más o menos garantizado para ser utilizado en un entorno de subprocesos múltiples. Las cadenas son inmutables para garantizar que sea seguro compartir cadenas entre subprocesos. Tener cadenas inmutables asegura 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 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 desde múltiples hilos, para asegurarse de que un hilo no intente leer el valor de su objeto mientras otro hilo lo está modificando. La sincronización adecuada es difícil de hacer 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.
Si bien se ha mencionado el internamiento de cadenas, solo representa una pequeña ganancia en la eficiencia de la memoria para los programas 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 programa 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 desea 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 que comienza en el índice 0 y termina en el índice 5 de ese carácter []. Para poner esto en forma gráfica, terminaría 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 sea extremadamente barato, 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 [].
char[]
es una decisión de diseño bastante cuestionable. Si lee un archivo completo en una sola cadena y mantiene una referencia a solo una subcadena de 1 carácter, todo el archivo deberá mantenerse en la memoria.
String.substring()
realiza una copia completa, para evitar los problemas mencionados en los comentarios anteriores. En Java 8, los dos campos que permiten char[]
compartir, a saber , count
y offset
, se eliminan, lo que reduce la huella de memoria de las instancias de String.
Seguridad y rendimiento del hilo. Si una cadena no se puede modificar, es seguro y rápido pasar una referencia entre múltiples hilos. 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 sobre inmutabilidad .
Uno realmente debería preguntarse, "¿por qué X debería ser mutable?" Es mejor pasar por defecto a la inmutabilidad, debido a los beneficios ya mencionados por Princess Fluff . 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 inmutabilidad (consulte la Lista de deseos para el próximo lenguaje de programación principal ).
¡Guauu! No puedo creer la información errónea aquí.String
S ser inmutables no tienen nada con seguridad. Si alguien ya tiene acceso a los objetos en una aplicación en ejecución (lo que debería suponerse si está tratando de protegerse contra alguien que `` piratea '' String
en su aplicación), seguramente habrá muchas otras oportunidades disponibles para piratear.
Es una idea bastante novedosa que la inmutabilidad de String
abordar problemas de subprocesos. 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 en absoluto, ¡eso solucionará todos nuestros problemas de concurrencia desordenados! De hecho, hagamos que todos los objetos sean inmutables, y luego podemos eliminar la construcción sincronizada del lenguaje Java.
La verdadera razón (señalada por otros arriba) es la optimización de la memoria. Es bastante común en cualquier aplicación que el mismo literal de cadena se use repetidamente. Es tan común, de hecho, que hace décadas, muchos compiladores hicieron la optimización de almacenar solo una instancia de un String
literal. El inconveniente de esta optimización es que el código de tiempo de ejecución que modifica un String
literal introduce un problema porque está modificando la instancia para todos los demás códigos que lo comparten. Por ejemplo, no sería bueno que una función en algún lugar de una aplicación cambie el String
literal "dog"
a "cat"
. A printf("dog")
daría como resultado literales (es decir, los haría inmutables). Algunos compiladores (con soporte del sistema operativo) lo lograrían colocando"cat"
ser escrito en stdout. Por esa razón, tenía que haber una forma de protegerse contra el código que intenta cambiarString
String
literal en un segmento especial de memoria de solo lectura que causaría un error de memoria si se realizara un intento de escritura.
En Java esto se conoce como pasantía. El compilador de Java aquí solo sigue una optimización de memoria estándar realizada por los compiladores durante décadas. Y para abordar el mismo problema de que estos String
literales se modifiquen en tiempo de ejecución, Java simplemente hace que la String
clase sea inmutable (es decir, no le proporciona configuradores que le permitan cambiar el String
contenido). String
s no tendría que ser inmutable si String
no ocurriera la internación de literales.
String
y StringBuffer
, pero desafortunadamente pocos tipos siguen ese modelo.
String
no es un tipo primitivo, pero normalmente desea usarlo con semántica de valores, es decir, como un valor.
Un valor es algo en lo que puede confiar no cambiará a sus espaldas. Si escribe: String str = someExpr();
No quiere que cambie a menos que USTED haga algo con str
.
String
como Object
tiene una semántica de puntero natural, para obtener una semántica de valor también debe ser inmutable.
Un factor es que, si los String
s fueran mutables, los objetos que los almacenan String
deberían tener cuidado de almacenar copias, para que sus datos internos no cambien sin previo aviso. Dado que los String
s son un tipo bastante primitivo como los números, es bueno cuando uno puede tratarlos como si fueran pasados por valor, incluso si son pasados por referencia (lo que también ayuda a ahorrar en memoria).
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ía 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 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 compilador. Por supuesto, lo anterior solo 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 utilizar los punteros, si tienen un uso práctico o si se usan de manera 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 forma. Hay una manera de evitar esto al no permitir instancias de puntero de cadenas o hacer una copia cuando se apunta a una cadena, pero tampoco 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 / asumida como) una unidad atómica significativa , como un número .
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 contextualizado.
Realizamos cálculos y comparaciones con "cadenas" similares a las que 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 para 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 "cuerda", todos pensamos en cosas diferentes. Aquellos que lo consideran simplemente como un conjunto de personajes, sin un propósito particular en mente, por supuesto se horrorizarán de que alguien simplemente haya decidido que no deberían poder manipular a esos personajes. Pero la clase "string" no es solo un conjunto de caracteres. Es un STRING
, no un char[]
. Hay algunos supuestos básicos 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 personajes para construir cadenas , y un StringBuilder es excelente para eso.
Considere por un momento cómo sería si las cuerdas fueran mutables. La siguiente función API podría ser engañada para que devuelva información para un usuario diferente si la cadena de nombre de usuario mutable es modificada intencionalmente o no por otro hilo mientras esta función la está utilizando:
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', también se trata de 'seguridad' y 'garantía de corrección'. Si un método no puede escribirse fácilmente y depender 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 programación en sí.
unsafe
) o simplemente por reflexión (puede obtener el campo subyacente fácilmente). Esto hace que el punto de seguridad sea nulo, ya que cualquiera que intencionalmente quiera cambiar una cadena, puede hacerlo con bastante facilidad. Sin embargo, proporciona seguridad a los programadores: a menos que haga algo especial, la cadena se garantiza inmutable (¡pero no es segura para subprocesos!).
La inmutabilidad no está tan ligada a la seguridad. Para eso, al menos en .NET, obtienes elSecureString
clase.
Edición posterior: en Java encontrarás GuardedString
una implementación similar.
La decisión de tener una cadena mutable en C ++ causa muchos problemas, vea este excelente artículo de Kelvin Henney sobre Mad COW Disease .
VACA = Copiar al escribir.
Es una compensación. String
s ir a la String
piscina y cuando crea múltiples idénticosString
s 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 en las mismas cadenas.
La desventaja es que las concatenaciones generan muchos String
s adicionales que solo son transitorios y simplemente se convierten en basura, lo que en realidad perjudica el rendimiento de la memoria. Tiene StringBuffer
y StringBuilder
(en Java, StringBuilder
también está en .NET) para preservar la memoria en estos casos.
String
s en Java no son realmente inmutables, puede cambiar sus valores utilizando la reflexión y / o la carga de clases. No debe depender de esa propiedad por seguridad. Para ver ejemplos, ver: Truco de magia en Java
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 qué referencias. De la misma manera que Integer debe ser inmutable para comportarse como int, las cadenas deben comportarse como inmutables para actuar como primitivas. En C ++, pasar cadenas por valor hace esto 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();
}
}
}
}
Es en gran parte por razones de seguridad. Es mucho más difícil asegurar un sistema si no puede confiar en que sus String
s son a prueba de manipulaciones.