No pude entender la razón de esto. Siempre uso la clase String como otros desarrolladores, pero cuando modifico el valor de la misma, se crea una nueva instancia de String.
¿Cuál podría ser la razón de la inmutabilidad de la clase String en Java?
Sé que hay algunas alternativas como StringBuffer o StringBuilder. Es solo curiosidad.
Respuestas:
Concurrencia
Java se definió desde el principio con consideraciones de concurrencia. Como se ha mencionado a menudo, las mutables compartidas son problemáticas. Una cosa puede cambiar a otra detrás de otro hilo sin que ese hilo sea consciente de ello.
Hay una gran cantidad de errores de C ++ multiproceso que se han creado debido a una cadena compartida, donde un módulo pensó que era seguro cambiar cuando otro módulo en el código había guardado un puntero y esperaba que permaneciera igual.
La 'solución' a esto es que cada clase hace una copia defensiva de los objetos mutables que se le pasan. Para cadenas mutables, esto es O (n) para hacer la copia. Para cadenas inmutables, hacer una copia es O (1) porque no es una copia, es el mismo objeto que no puede cambiar.
En un entorno multiproceso, los objetos inmutables siempre se pueden compartir de forma segura entre sí. Esto conduce a una reducción general en el uso de memoria y mejora el almacenamiento en memoria caché.
Seguridad
Muchas veces las cadenas se pasan como argumentos a los constructores: las conexiones de red y los protocolos son los dos que más fácilmente se nos ocurren. Ser capaz de cambiar esto en un momento indeterminado más adelante en la ejecución puede conducir a problemas de seguridad (la función pensó que se estaba conectando a una máquina, pero se desvió a otra, pero todo en el objeto parece estar conectado a la primera ... es incluso la misma cadena).
Java permite usar la reflexión, y los parámetros para esto son cadenas. El peligro de que alguien pase una cadena que puede modificarse a otro método que refleja. Esto es muy malo.
Claves para el hash
La tabla hash es una de las estructuras de datos más utilizadas. Las claves de la estructura de datos suelen ser cadenas. Tener cadenas inmutables significa que (como arriba) la tabla hash no necesita hacer una copia de la clave hash cada vez. Si las cadenas fueran mutables, y la tabla hash no hiciera esto, sería posible que algo cambiara la clave hash a distancia.
La forma en que funciona el Object en java es que todo tiene una clave hash (a la que se accede mediante el método hashCode ()). Tener una cadena inmutable significa que el código hash se puede almacenar en caché. Teniendo en cuenta la frecuencia con la que las cadenas se usan como claves para un hash, esto proporciona un aumento significativo del rendimiento (en lugar de tener que volver a calcular el código hash cada vez).
Subcadenas
Al hacer que la cadena sea inmutable, la matriz de caracteres subyacente que respalda la estructura de datos también es inmutable. Esto permite ciertas optimizaciones en el
substring
método que se debe hacer (no necesariamente se hace, también presenta la posibilidad de algunas pérdidas de memoria también).Si lo haces:
El valor de
bar
es 'milla'. Sin embargo, tantofoo
ybar
pueda ir acompañado de la misma matriz de caracteres, lo que reduce la creación de instancias de más matrices de caracteres o copiarlo - simplemente utilizando diferentes puntos de inicio y fin dentro de la cadena.Ahora, la desventaja de eso (la pérdida de memoria) es que si uno tuviera una cadena de 1k de largo y tomara la subcadena del primer y segundo carácter, también estaría respaldada por la matriz de caracteres de 1k de largo. Esta matriz permanecería en la memoria incluso si la cadena original que tenía un valor de la matriz de caracteres completa se recolectara basura.
Uno puede ver esto en String from JDK 6b14 (el siguiente código es de una fuente GPL v2 y se usa como ejemplo)
Observe cómo la subcadena utiliza el constructor de cadenas de nivel de paquete que no implica ninguna copia de la matriz y sería mucho más rápido (a expensas de posiblemente mantener algunas matrices grandes, aunque tampoco duplicar matrices grandes).
Tenga en cuenta que el código anterior es para Java 1.6. La forma en que se implementa el constructor de subcadenas se modificó con Java 1.7, tal como se documenta en la representación interna Cambios en la cadena realizada en Java 1.7.0_06 , el problema relacionado con la pérdida de memoria que mencioné anteriormente. Es probable que Java no se haya visto como un lenguaje con mucha manipulación de cadenas, por lo que el aumento de rendimiento para una subcadena fue algo bueno. Ahora, con enormes documentos XML almacenados en cadenas que nunca se recopilan, esto se convierte en un problema ... y, por lo tanto, el cambio a
String
no usar la misma matriz subyacente con una subcadena, para que la matriz de caracteres más grande se pueda recopilar más rápidamente.No abuses de la pila
Se podría pasar el valor de la cadena en lugar de la referencia a la cadena inmutable para evitar problemas con la mutabilidad. Sin embargo, con cadenas grandes, pasar esto en la pila sería ... abusivo para el sistema (colocar documentos xml completos como cadenas en la pila y luego quitarlos o continuar pasándolos ...).
La posibilidad de deduplicación
De acuerdo, esto no fue una motivación inicial de por qué las cadenas deberían ser inmutables, pero cuando uno está mirando la razón de por qué las cadenas inmutables son algo bueno, esto es ciertamente algo a considerar.
Cualquiera que haya trabajado un poco con Strings sabe que puede succionar memoria. Esto es especialmente cierto cuando estás haciendo cosas como extraer datos de bases de datos que se quedan por un tiempo. Muchas veces con estas picaduras, son la misma cadena una y otra vez (una vez para cada fila).
Con Java 8 actualización 20, JEP 192 (motivación citada anteriormente) se está implementando para abordar esto. Sin entrar en detalles sobre cómo funciona la deduplicación de cadenas, es esencial que las cadenas mismas sean inmutables. No puede deduplicar StringBuilders porque pueden cambiar y no desea que alguien cambie algo debajo de usted. Las cadenas inmutables (relacionadas con ese grupo de cadenas) significa que puede pasar y si encuentra dos cadenas que son iguales, puede apuntar una referencia de cadena a la otra y dejar que el recolector de basura consuma el nuevo no utilizado.
Otros idiomas
El objetivo C (que precede a Java) tiene
NSString
yNSMutableString
.C # y .NET tomaron las mismas opciones de diseño de la cadena predeterminada, que es inmutable.
Las cuerdas de Lua también son inmutables.
Python también.
Históricamente, Lisp, Scheme, Smalltalk todos internan la cadena y, por lo tanto, hacen que sea inmutable. Los lenguajes dinámicos más modernos a menudo usan cadenas de alguna manera que requiere que sean inmutables (puede que no sea una cadena , pero es inmutable).
Conclusión
Estas consideraciones de diseño se han hecho una y otra vez en una multitud de idiomas. Es el consenso general de que las cadenas inmutables, a pesar de su incomodidad, son mejores que las alternativas y conducen a un mejor código (menos errores) y ejecutables más rápidos en general.
fuente
Razones que puedo recordar:
La instalación del conjunto de cadenas sin hacer que la cadena sea inmutable no es posible en absoluto porque en el caso del grupo de cadenas, un objeto de cadena / literal, por ejemplo, "XYZ" será referenciado por muchas variables de referencia, por lo que si alguno de ellos cambia el valor, otros se verán afectados automáticamente. .
La cadena se ha utilizado ampliamente como parámetro para muchas clases de Java, por ejemplo, para abrir una conexión de red, para abrir una conexión de base de datos, abrir archivos. Si String no es inmutable, esto conduciría a una seria amenaza de seguridad.
La inmutabilidad permite a String almacenar en caché su código hash.
Lo hace a prueba de hilos.
fuente
1) Grupo de cadenas
El diseñador Java sabe que String será el tipo de datos más utilizado en todo tipo de aplicaciones Java y por eso querían optimizar desde el principio. Uno de los pasos clave en esa dirección fue la idea de almacenar literales de cadena en el grupo de cadenas. El objetivo era reducir el objeto String temporal compartiéndolos y para poder compartirlos, deben ser de la clase Inmutable. No puede compartir un objeto mutable con dos partes que son desconocidas entre sí. Tomemos un ejemplo hipotético, donde dos variables de referencia apuntan al mismo objeto String:
Ahora, si s1 cambia el objeto de "Java" a "C ++", la variable de referencia también obtuvo el valor s2 = "C ++", que ni siquiera sabe al respecto. Al hacer que String sea inmutable, este intercambio de String literal fue posible. En resumen, la idea clave del conjunto de cadenas no se puede implementar sin hacer que la secuencia sea definitiva o inmutable en Java.
2) seguridad
Java tiene un objetivo claro en términos de proporcionar un entorno seguro en todos los niveles de servicio y String es fundamental en todas esas cosas de seguridad. String se ha utilizado ampliamente como parámetro para muchas clases de Java, por ejemplo, para abrir la conexión de red, puede pasar el host y el puerto como String, para leer archivos en Java puede pasar la ruta de los archivos y el directorio como String y para abrir la conexión de la base de datos, puede pase la URL de la base de datos como String. Si String no era inmutable, un usuario podría haber otorgado acceso a un archivo en particular en el sistema, pero después de la autenticación puede cambiar la RUTA a otra cosa, esto podría causar serios problemas de seguridad. Del mismo modo, mientras se conecta a la base de datos o cualquier otra máquina en la red, el valor de String mutante puede presentar amenazas de seguridad. Las cadenas mutables también podrían causar problemas de seguridad en Reflection,
3) Uso de la cadena en el mecanismo de carga de clase
Otra razón para hacer que String sea final o inmutable fue el hecho de que se usó mucho en el mecanismo de carga de clase. Como String no ha sido inmutable, un atacante puede aprovechar este hecho y una solicitud para cargar clases estándar de Java, por ejemplo, java.io.Reader, puede cambiarse a una clase maliciosa com.unknown.DataStolenReader. Al mantener String final e inmutable, al menos podemos estar seguros de que JVM está cargando las clases correctas.
4) Beneficios de subprocesos múltiples
Dado que la concurrencia y el subprocesamiento múltiple era la oferta clave de Java, tenía mucho sentido pensar en la seguridad de subprocesos de los objetos String. Como se esperaba que String se usara ampliamente, lo que lo hace inmutable significa que no hay sincronización externa, significa un código mucho más limpio que implica compartir String entre múltiples subprocesos. Esta característica única hace que la codificación de concurrencia complicada, confusa y propensa a errores sea mucho más fácil. Como String es inmutable y solo lo compartimos entre subprocesos, da como resultado un código más legible.
5) Optimización y rendimiento
Ahora, cuando haces una clase Inmutable, sabes de antemano que esta clase no cambiará una vez creada. Esto garantiza una ruta abierta para muchas optimizaciones de rendimiento, por ejemplo, almacenamiento en caché. String sabe que no voy a cambiar, por lo que String almacena en caché su código hash. Incluso calcula el código hash perezosamente y, una vez creado, solo lo almacena en caché. En un mundo simple, cuando llama por primera vez al método hashCode () de cualquier objeto String, calcula el código hash y todas las llamadas posteriores a hashCode () devuelven el valor en caché ya calculado. Esto da como resultado una buena ganancia de rendimiento, dado que String se usa mucho en mapas basados en hash, por ejemplo, Hashtable y HashMap. El almacenamiento en caché de hashcode no fue posible sin hacerlo inmutable y final, ya que depende del contenido de String.
fuente
La máquina virtual Java realiza varias optimizaciones con respecto a las operaciones de cadena que no podrían realizarse de otra manera. Por ejemplo, si tenía una cadena con el valor "Mississippi" y asignó "Mississippi" .substring (0, 4) a otra cadena, hasta donde usted sabe, se hizo una copia de los primeros cuatro caracteres para hacer "Miss" . Lo que no sabe es que ambos comparten la misma cadena original "Mississippi", siendo uno el propietario y el otro una referencia de esa cadena desde la posición 0 a la 4. (La referencia al propietario evita que el propietario sea recogido por el recolector de basura cuando el propietario sale del alcance)
Esto es trivial para una cadena tan pequeña como "Mississippi", pero con cadenas más grandes y operaciones múltiples, ¡no tener que copiar la cadena es un gran ahorro de tiempo! Si las cadenas fueran mutables, entonces no podría hacer esto, porque modificar el original afectaría también a las "copias" de la subcadena.
Además, como menciona Donal, la ventaja se vería enormemente afectada por su desventaja. Imagine que escribe un programa que depende de una biblioteca y usa una función que devuelve una cadena. ¿Cómo podría estar seguro de que ese valor se mantendrá constante? Para asegurarse de que no ocurra tal cosa, siempre tendrá que producir una copia.
¿Qué pasa si tienes dos hilos que comparten la misma cadena? No querrás leer una cadena que está siendo reescrita por otro hilo, ¿verdad? Por lo tanto, la cadena tendría que ser segura para subprocesos, que siendo la clase común que es, haría que cada programa Java sea mucho más lento. De lo contrario, tendría que hacer una copia para cada hilo que requiera esa cadena o tendría que poner el código usando esa cadena en un bloque de sincronización, los cuales solo ralentizan su programa.
Por todas estas razones, esa fue una de las primeras decisiones tomadas para Java con el fin de diferenciarse de C ++.
fuente
La razón de la inmutabilidad de la cadena proviene de la coherencia con otros tipos primitivos en el lenguaje. Si tiene un
int
valor que contiene el 42, y le agrega el valor 1, no cambia el 42. Obtiene un nuevo valor, 43, que no tiene relación alguna con los valores iniciales. Mutar primitivas que no sean cadenas no tiene sentido conceptual; y, como tales, los programas que tratan las cadenas como inmutables a menudo son más fáciles de razonar y comprender.Además, Java realmente proporciona cadenas mutables e inmutables, como puede ver con
StringBuilder
; realmente, solo el valor predeterminado es la cadena inmutable. Si desea pasar referencias aStringBuilder
todas partes, puede hacerlo. Java usa tipos separados (String
yStringBuilder
) para estos conceptos porque no tiene soporte para expresar mutabilidad o falta de ella en su sistema de tipos. En los lenguajes que admiten la inmutabilidad en sus sistemas de tipos (p. Ej., C ++const
), a menudo hay un solo tipo de cadena que sirve para ambos propósitos.Sí, tener una cadena inmutable le permite a uno implementar algunas optimizaciones específicas para cadenas inmutables, como internarse, y permite pasar referencias de cadena sin sincronización entre hilos. Sin embargo, esto confunde el mecanismo con el objetivo previsto de un lenguaje con un sistema de tipos simple y consistente. Comparo esto con la forma en que todos piensan que la recolección de basura es incorrecta; la recolección de basura no es "recuperación de memoria no utilizada"; es "simular una computadora con memoria ilimitada" . Las optimizaciones de rendimiento discutidas son cosas que se hacen para lograr que el objetivo de las cadenas inmutables funcione bien en máquinas reales; no es la razón de que tales cadenas sean inmutables en primer lugar.
fuente
43 = 6
y esperar que el número 43 signifique lo mismo que el número 6.i
, no 42. Considerastring s = "Hello "; s += "World";
. Mutaste el valor de la variables
. Pero las cadenas"Hello "
,"World"
y"Hello World"
son inmutables.La inmutabilidad significa que las constantes mantenidas por clases que no son de su propiedad no pueden modificarse. Las clases que no son de su propiedad incluyen aquellas que están en el núcleo de la implementación de Java, y las cadenas que no deben modificarse incluyen elementos como tokens de seguridad, direcciones de servicio, etc. Realmente no debería poder modificar esos tipos de cosas (y esto se aplica doblemente cuando se opera en modo de espacio aislado).
Si String no era inmutable, cada vez que lo recuperaste de algún contexto que no quería que el contenido de la cadena cambiara bajo sus pies, deberías tomar una copia "por si acaso". Eso se pone muy caro.
fuente
String
. Pero, por ejemplo, losArray
s son mutables, no obstante. Entonces, ¿por quéString
s son inmutables yArray
no s Y si la inmutabilidad es tan importante, ¿por qué Java hace que sea tan difícil crear y trabajar con objetos inmutables?Imagine un sistema en el que acepta algunos datos, verifica su corrección y luego los pasa (para almacenarlos en una base de datos, por ejemplo).
Suponiendo que los datos son a
String
y deben tener al menos 5 caracteres de longitud. Su método se parece a esto:Ahora podemos estar de acuerdo en que cuando
storeInDatabase
se llama aquí,input
se ajustará al requisito. Pero siString
fueran mutables, la persona que llama podría alterar elinput
objeto (de otro hilo) justo después de que se haya verificado y antes de que se haya almacenado en la base de datos . Esto requeriría un buen momento y probablemente no iría bien cada vez, pero ocasionalmente, podría lograr que almacene valores no válidos en la base de datos.Los tipos de datos inmutables son una solución muy simple para este (y muchos otros problemas relacionados): cada vez que verifica algún valor, puede depender del hecho de que la condición verificada sigue siendo cierta más adelante.
fuente
input
delhandle
método ya es demasiado largo (no importa lo que el original,input
es decir), sería simplemente una excepción. Estás creando una nueva entrada antes de llamar al método. Eso no es un problema.En general, encontrará tipos de valores y tipos de referencia . Con un tipo de valor, no le importa el objeto que lo representa, le importa el valor. Si le doy un valor, espera que ese valor permanezca igual. No quieres que cambie de repente. El número 5 es un valor. No esperas que cambie a 6 de repente. La cadena "Hola" es un valor. No espera que cambie a "P *** off" de repente.
Con los tipos de referencia, le importa el objeto y espera que cambie. Por ejemplo, a menudo esperará que cambie una matriz. Si le doy una matriz y desea mantenerla exactamente como está, debe confiar en mí para que no la cambie o hacer una copia.
Con la clase de cadena de Java, los diseñadores tuvieron que tomar una decisión: ¿es mejor si las cadenas se comportan como un tipo de valor, o deberían comportarse como un tipo de referencia? En el caso de las cadenas Java, se tomó la decisión de que deberían ser tipos de valor, lo que significa que, dado que son objetos, deben ser objetos inmutables.
Se podría haber tomado la decisión opuesta, pero en mi opinión habría causado muchos dolores de cabeza. Como se dijo en otra parte, muchos idiomas tomaron la misma decisión y llegaron a la misma conclusión. Una excepción es C ++, que tiene una clase de cadena, y las cadenas pueden ser constantes o no constantes, pero en C ++, a diferencia de Java, los parámetros de los objetos se pueden pasar como valores y no como referencias.
fuente
Estoy realmente sorprendido de que nadie haya señalado esto.
Respuesta: No te beneficiaría significativamente, incluso si fuera mutable. No te beneficiaría tanto como eso causa problemas adicionales. Examinemos los dos casos más comunes de mutación:
Cambiar un caracter de una cadena
Dado que cada carácter en una cadena Java toma 2 o 4 bytes, pregúntese, ¿ganaría algo si pudiera mutar la copia existente?
En el caso de que esté reemplazando un carácter de 2 bytes por 4 bytes uno (o viceversa), debe desplazar la parte restante de la cadena por 2 bytes hacia la izquierda o hacia la derecha. Lo cual no es tan diferente de copiar toda la cadena desde el punto de vista computacional.
Este también es un comportamiento realmente irregular que generalmente no es deseado. Imagine a alguien probando una aplicación con texto en inglés, y cuando la aplicación se adopta a países extranjeros, como China, todo comienza a funcionar de manera extraña.
Agregar otra cadena (o carácter) a la existente
Si tiene dos cadenas arbitrarias, esas se ubican en dos ubicaciones de memoria distintas. Si desea cambiar el primero agregando el segundo, no puede simplemente solicitar memoria adicional al final de la primera cadena, ya que probablemente ya esté ocupada.
Debe copiar la cadena concatenada en una ubicación completamente nueva, que es exactamente lo mismo que si ambas cadenas fueran inmutables.
Si desea hacer anexos de manera eficiente, es posible que desee usar
StringBuilder
, que reserva una cantidad considerable de espacio al final de una cadena, solo para este propósito de un posible anexo futuro.fuente
son caros y mantenerlos inmutables permite cosas como subcadenas que comparten la matriz de bytes de la cadena principal. (aumento de velocidad también ya que no es necesario hacer una nueva matriz de bytes y copiar)
seguridad: no querría que se cambiara el nombre de su paquete o código de clase
[eliminado 3 viejos miró StrrBuilder src - no comparte memoria con cadena (hasta que se modifique) Creo que estaba en 1.3 o 1.4]
código hash de caché
para cadenas mutables, use SB (generador o buffer según sea necesario)
fuente
Las cadenas deberían haber sido un tipo de datos primitivo en Java. Si lo hubieran sido, las cadenas pasarían a ser mutables y la palabra clave final generaría cadenas inmutables. Las cadenas mutables son útiles y, por lo tanto, existen múltiples hacks para cadenas mutables en las clases stringbuffer, stringbuilder y charsequence.
fuente