O que são objetos imutáveis?
Um objeto String é imutável, mas isso significa apenas que você não pode alterar seu valor chamando seus métodos públicos.
Como todos sabemos, em Java, a classe String é imutável. Então, o que exatamente são objetos imutáveis? Você pode pensar desta forma: se um objeto não pode mudar seu estado após ser criado, então o objeto é imutável. O estado não pode ser alterado, o que significa que as variáveis-membro dentro do objeto não podem ser alteradas, incluindo os valores dos tipos de dados básicos. As variáveis dos tipos de referência não podem apontar para outros objetos, e o estado dos objetos apontados pelos tipos de referência não pode. ser alterado.
Distinguir entre objetos e referências de objetos
Para iniciantes em Java, sempre há dúvidas sobre String ser um objeto imutável. Veja o código abaixo:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);O resultado impresso é:
s = ABCabc s = 123456
Primeiro crie um objeto String s, depois deixe o valor de s ser "ABCabc" e, em seguida, deixe o valor de s ser "123456". Como pode ser visto nos resultados impressos, o valor de s realmente mudou. Então, por que você ainda diz que objetos String são imutáveis? Na verdade, há um mal-entendido aqui: s é apenas uma referência a um objeto String, não ao objeto em si. O objeto é uma área de memória na memória. Quanto mais variáveis-membro, maior será o espaço que essa área de memória ocupa. Uma referência é apenas um dado de 4 bytes que armazena o endereço do objeto para o qual aponta. O objeto pode ser acessado através deste endereço.
Em outras palavras, s é apenas uma referência, que aponta para um objeto específico Quando s="123456"; após a execução deste código, um novo objeto "123456" é criado, e a referência s aponta para este objeto coração novamente. , o objeto original "ABCabc" ainda existe na memória e não foi alterado. A estrutura da memória é mostrada na figura abaixo:
Uma diferença entre Java e C++ é que em Java é impossível operar diretamente o próprio objeto. Todos os objetos são apontados por uma referência. Você deve usar esta referência para acessar o próprio objeto, incluindo obter o valor da variável membro e alterá-lo. a variável de membro do objeto. Chame métodos de objeto, etc. Em C++, existem três coisas: referências, objetos e ponteiros, todos os quais podem acessar objetos. Na verdade, as referências em Java e os ponteiros em C++ são conceitualmente semelhantes. Eles são os valores de endereço de objetos armazenados na memória. No entanto, em Java, as referências perdem alguma flexibilidade. são executados como ponteiros em C++.
Por que os objetos String são imutáveis?
Para entender a imutabilidade de String, primeiro dê uma olhada nas variáveis de membro da classe String. No JDK1.6, as variáveis de membro de String incluem o seguinte:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** O valor é usado para armazenamento de caracteres */ private final char value[]; que é usado. */ private final int offset; /** A contagem é o número de caracteres na String. */ private final int count /** Armazena em cache o código hash da string */ private int hash; Padrão para 0No JDK1.7, a classe String fez algumas alterações, principalmente alterando o comportamento do método substring durante a execução, o que não está relacionado ao tópico deste artigo. Existem apenas duas variáveis de membro principais da classe String no JDK1.7:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** O valor é usado para armazenamento de caracteres */ private final char value[]; hash interno privado; // Padrão é 0Como pode ser visto no código acima, a classe String em Java é na verdade um encapsulamento de uma matriz de caracteres. No JDK6, valor é um array encapsulado por String, offset é a posição inicial de String no array de valor e contagem é o número de caracteres ocupados por String. No JDK7, existe apenas uma variável de valor, ou seja, todos os caracteres de valor pertencem ao objeto String. Esta alteração não afeta a discussão deste artigo. Além disso, há uma variável de membro hash, que é um cache do valor hash do objeto String. Essa variável de membro também é irrelevante para a discussão deste artigo. Em Java, arrays também são objetos (consulte meu artigo anterior Características de arrays em Java). Portanto, value é apenas uma referência, que aponta para um objeto array real. Na verdade, após executar o código String s = “ABCabc”;, o layout real da memória deverá ficar assim:
As três variáveis valor, deslocamento e contagem são todas privadas e nenhum método público como setValue, setOffset e setCount é fornecido para modificar esses valores, portanto String não pode ser modificado fora da classe String. Ou seja, uma vez inicializado, não pode ser modificado e esses três membros não podem ser acessados fora da classe String. Além disso, as três variáveis valor, deslocamento e contagem são todas finais, o que significa que dentro da classe String, uma vez inicializados esses três valores, eles não podem ser alterados. Portanto, o objeto String pode ser considerado imutável.
Portanto, em String, obviamente existem alguns métodos, e chamá-los pode obter o valor alterado. Esses métodos incluem substring, substituir, substituirAll, toLowerCase, etc. Por exemplo, o seguinte código:
String a = "ABCabc"; System.out.println("a = " + a);O resultado impresso é:
a = ABCabca = aBCabc
Então o valor de a parece ter mudado, mas na verdade é o mesmo mal-entendido. Novamente, a é apenas uma referência, não um objeto string real. Ao chamar a.replace('A', 'a'), o método cria internamente um novo objeto String e reatribui o novo objeto ao Cited a. O código fonte do método replace em String pode ilustrar o problema:
Os leitores podem verificar outros métodos por si próprios. Um novo objeto String é recriado dentro do método e o novo objeto é retornado. É por isso que métodos como replace, substring, toLowerCase, etc. têm valores de retorno. É também por isso que chamar da seguinte forma não altera o valor do objeto:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Imprima o resultado:
ss = 123456 ss = 123456
Os objetos String são realmente imutáveis?
Como pode ser visto acima, as variáveis membro de String são privadas finais, ou seja, não podem ser alteradas após a inicialização. Entre esses membros, value é especial porque é uma variável de referência, e não um objeto real. value é modificado por final, o que significa que final não pode mais apontar para outros objetos de array, então posso alterar o array para o qual value aponta? Por exemplo, altere o caractere em uma determinada posição na matriz para um sublinhado "_". Pelo menos não podemos fazer isso no código comum que escrevemos, porque não podemos acessar essa referência de valor, muito menos modificar o array por meio dessa referência.
Então, como podemos acessar membros privados? Isso mesmo, usando reflexão, você pode refletir o atributo de valor no objeto String e então alterar a estrutura do array através da referência de valor obtida. Aqui está o código de exemplo:
public static void testReflection() throws Exception { //Crie a string "Hello World" e atribua-a à referência s String s = "Hello World"; World //Obter o campo de valor na classe String Field valueFieldOfString = String.class.getDeclaredField("value"); //Alterar a permissão de acesso do atributo de valor valueFieldOfString.setAccessible(true); //Obter o valor do atributo value no objeto s char[] value = (char[]) valueFieldOfString.get(s); //Alterar o quinto caractere no array referenciado pelo valor value[5] = '_' ;System.out.println("s = " + s);O resultado impresso é:
s = Olá Mundo s = Olá_Mundo
Neste processo, s sempre se refere ao mesmo objeto String, mas antes e depois da reflexão, o objeto String muda. Em outras palavras, o chamado objeto "imutável" pode ser modificado por meio da reflexão. Mas geralmente não fazemos isso. Este exemplo de reflexão também pode ilustrar um problema: se o estado de um objeto e de outros objetos dos quais ele é composto pode mudar, então esse objeto provavelmente não é um objeto imutável. Por exemplo, um objeto Car é combinado com um objeto Wheel Embora o objeto Wheel seja declarado privado final, o estado interno do objeto Wheel pode mudar, portanto, não é possível garantir que o objeto Car seja imutável.