¿Qué son los objetos inmutables?
Un objeto String es inmutable, pero eso sólo significa que no puedes cambiar su valor llamando a sus métodos públicos.
Como todos sabemos, en Java, la clase String es inmutable. Entonces, ¿qué son exactamente los objetos inmutables? Puedes pensarlo de esta manera: si un objeto no puede cambiar su estado después de su creación, entonces el objeto es inmutable. El estado no se puede cambiar, lo que significa que las variables miembro dentro del objeto no se pueden cambiar, incluidos los valores de los tipos de datos básicos. Las variables de tipos de referencia no pueden apuntar a otros objetos y el estado de los objetos a los que apuntan los tipos de referencia no. ser cambiado.
Distinguir entre objetos y referencias a objetos.
Para los principiantes de Java, siempre hay dudas sobre si String es un objeto inmutable. Mira el código a continuación:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);El resultado impreso es:
s = ABCabc s = 123456
Primero cree un objeto String s, luego deje que el valor de s sea "ABCabc" y luego deje que el valor de s sea "123456". Como puede verse en los resultados impresos, el valor de s efectivamente ha cambiado. Entonces, ¿por qué sigues diciendo que los objetos String son inmutables? De hecho, aquí hay un malentendido: s es solo una referencia a un objeto String, no el objeto en sí. El objeto es un área de memoria en la memoria. Cuantas más variables miembro, mayor será el espacio que ocupa esta área de memoria. Una referencia es solo un dato de 4 bytes que almacena la dirección del objeto al que apunta. Se puede acceder al objeto a través de esta dirección.
En otras palabras, s es solo una referencia, que apunta a un objeto específico. Cuando s="123456"; después de ejecutar este código, se crea un nuevo objeto "123456" y la referencia s apunta nuevamente a este objeto central. , el objeto original "ABCabc" todavía existe en la memoria y no ha cambiado. La estructura de la memoria se muestra en la siguiente figura:
Una diferencia entre Java y C++ es que en Java es imposible operar directamente el objeto en sí. Todos los objetos están apuntados por una referencia. Debe utilizar esta referencia para acceder al objeto en sí, incluida la obtención del valor de la variable miembro y el cambio. la variable miembro del objeto. Llamar a los métodos del objeto, etc. En C++, hay tres cosas: referencias, objetos y punteros, todos los cuales pueden acceder a objetos. De hecho, las referencias en Java y los punteros en C ++ son conceptualmente similares. Son los valores de dirección de los objetos almacenados en la memoria. Sin embargo, en Java, las referencias pierden cierta flexibilidad. Por ejemplo, las referencias en Java no se pueden usar como sumas y restas. se realizan como punteros en C++.
¿Por qué los objetos String son inmutables?
Para comprender la inmutabilidad de String, primero observe las variables miembro de la clase String. En JDK1.6, las variables miembro de String incluyen lo siguiente:
public final class String implementa java.io.Serializable, Comparable<String>, CharSequence{ /** El valor se utiliza para el almacenamiento de caracteres */ private final char value[]; que se utiliza. */ private final int offset; /** El recuento es el número de caracteres en la cadena. */ private final int count /** Almacena en caché el código hash para la cadena */ private int hash; Predeterminado a 0En JDK1.7, la clase String realizó algunos cambios, principalmente cambiando el comportamiento cuando se ejecuta el método de subcadena, lo cual no está relacionado con el tema de este artículo. Solo hay dos variables miembro principales de la clase String en JDK1.7:
public final class String implementa java.io.Serializable, Comparable<String>, CharSequence { /** El valor se utiliza para el almacenamiento de caracteres */ private final char value[] /** Almacena en caché el código hash para la cadena */ hash int privado // Por defecto es 0Como se puede ver en el código anterior, la clase String en Java es en realidad una encapsulación de una matriz de caracteres. En JDK6, el valor es una matriz encapsulada por String, el desplazamiento es la posición inicial de String en la matriz de valores y el recuento es el número de caracteres ocupados por String. En JDK7, solo hay una variable de valor, es decir, todos los caracteres del valor pertenecen al objeto String. Este cambio no afecta la discusión de este artículo. Además, hay una variable miembro hash, que es un caché del valor hash del objeto String. Esta variable miembro también es irrelevante para la discusión de este artículo. En Java, las matrices también son objetos (consulte mi artículo anterior Características de las matrices en Java). Entonces el valor es solo una referencia, que apunta a un objeto de matriz real. De hecho, después de ejecutar el código String s = “ABCabc”;, el diseño de la memoria real debería ser así:
Las tres variables valor, compensación y recuento son privadas y no se proporcionan métodos públicos como setValue, setOffset y setCount para modificar estos valores, por lo que String no se puede modificar fuera de la clase String. Es decir, una vez inicializado, no se puede modificar y no se puede acceder a estos tres miembros fuera de la clase String. Además, las tres variables valor, desplazamiento y recuento son todas finales, lo que significa que dentro de la clase String, una vez que estos tres valores se inicializan, no se pueden cambiar. Por tanto, el objeto String puede considerarse inmutable.
Entonces, en String, obviamente hay algunos métodos, y llamarlos puede obtener el valor modificado. Estos métodos incluyen subcadena, reemplazar, reemplazar todo, toLowerCase, etc. Por ejemplo, el siguiente código:
Cadena a = "ABCabc"; System.out.println("a = " + a); a = a.replace('A', 'a');El resultado impreso es:
a = ABCabca = aBCabc
Entonces el valor de a parece haber cambiado, pero en realidad es el mismo malentendido. Nuevamente, a es solo una referencia, no un objeto de cadena real. Al llamar a a.replace('A', 'a'), el método crea internamente un nuevo objeto String y reasigna el nuevo objeto a Cited a. El código fuente del método de reemplazo en String puede ilustrar el problema:
Los lectores pueden verificar otros métodos por sí mismos. Todos recrean un nuevo objeto String dentro del método y devuelven este nuevo objeto. El objeto original no se modificará. Es por eso que métodos como reemplazar, subcadena, toLowerCase, etc. tienen valores de retorno. Esta es también la razón por la que llamar como el siguiente no cambia el valor del objeto:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Imprime el resultado:
SS = 123456 SS = 123456
¿Son los objetos String realmente inmutables?
Como se puede ver en lo anterior, las variables miembro de String son privadas finales, es decir, no se pueden cambiar después de la inicialización. Entre estos miembros, el valor es especial porque es una variable de referencia, no un objeto real. El valor es modificado por final, lo que significa que final ya no puede apuntar a otros objetos de la matriz, entonces, ¿puedo cambiar la matriz a la que apunta el valor? Por ejemplo, cambie el carácter en una determinada posición de la matriz a un guión bajo "_". Al menos no podemos hacerlo en el código ordinario que escribimos nosotros mismos, porque no podemos acceder a esta referencia de valor en absoluto, y mucho menos modificar la matriz a través de esta referencia.
Entonces, ¿cómo podemos acceder a miembros privados? Así es, al usar la reflexión, puede reflejar el atributo de valor en el objeto String y luego cambiar la estructura de la matriz a través de la referencia del valor obtenido. Aquí está el código de ejemplo:
public static void testReflection() throws Exception { //Crea la cadena "Hola mundo" y asígnala a la referencia s String s = "Hola mundo"; World //Obtener el campo de valor en el campo de clase String valueFieldOfString = String.class.getDeclaredField("value"); //Cambiar el permiso de acceso del atributo de valor valueFieldOfString.setAccessible(true); //Obtener el valor del atributo de valor en el objeto s char[] value = (char[]) valueFieldOfString.get(s); //Cambiar el quinto carácter en la matriz a la que hace referencia el valor value[5] = '_' ; System.out.println("s = " + s); //Hola_Mundo }El resultado impreso es:
s = Hola mundo s = Hola_mundo
En este proceso, s siempre se refiere al mismo objeto String, pero antes y después de la reflexión, el objeto String cambia. En otras palabras, el llamado objeto "inmutable" se puede modificar mediante la reflexión. Pero generalmente no hacemos esto. Este ejemplo de reflexión también puede ilustrar un problema: si el estado de un objeto y otros objetos que lo componen pueden cambiar, entonces este objeto probablemente no sea un objeto inmutable. Por ejemplo, un objeto Car se combina con un objeto Wheel. Aunque el objeto Wheel se declara privado final, el estado interno del objeto Wheel puede cambiar, por lo que no se puede garantizar que el objeto Car sea inmutable.