Java - Strings, memória e imutabilidade

O entendimento sobre pilha e heap pode facilitar em muuito a compreensão de temas como passagem de parâmetros, polimorfismo, garbage collector, etc. Mas neste post darei apenas uma rápida visão para poder falar o assunto principal que é a forma como Java trata os objetos String.
Em geral, variáveis de instância, objetos e arrays - que também são objetos - residem no heap. Já as variáveis locais, tanto de tipos primitivos quanto as referências, residem na pilha.
Sabemos que uma String pode ser instanciada de várias maneiras, como nestes exemplos:

String s = new String("abcd");   // ou
s = "abcd";

Para que o uso da memória seja mais eficiente em Java, a JVM mantém uma área especial de memória chamada pool de Strings constantes. Quando o compilador encontra um literal String, ele verifica se existe uma String idêntica no pool. Se existir, a referência ao novo literal é direcionada à String existente e não é criado novo objeto String. Mas quando um objeto String é criado usando new, por exemplo:

String s = new String("abc"));

Esse novo objeto é criado no heap - a memória onde residem todos os objetos em Java - e "abc" é inserida no pool de Strings constantes, se não existir.. Além disso, as Strings são imutáveis. Por exemplo:

String s = "abc";
s.concat("def"); // concatena o argumento ("def") à String "abc"

Nesse momento Java cria uma nova String com o valor "abcdef", mas ela fica perdida na memória, pois a variável s não muda seu valor e continua valendo "abc". No entanto se a segunda linha for alterada para:

s = s.concat("def");

Então s passará a valer "abcdef". A variável s será uma nova String. A String "abc" fica perdida na memória. Ou seja, se o resultado da expressão não for atribuido a uma variável seu valor será perdido. Analise esse código:

String s1 = "abc";
String s2 = "abc";
String s3 = "abc";
s3 = s3.concat("def");
System.out.println(s1 + " " + s2 + " " + s3);

Inicialmente as Strings s1, s2, s3 referem-se ao mesmo objeto no pool, pelo que foi exposto acima.  No entanto, diferente do que possamos pensar, quando criamos um novo objeto e o referenciamos com s3, os valores de s1 e s2 não se alteram pelo princípio da imutabilidade.
A classe StringBuffer se comporta de forma diferente. Ela não é imutável.

String sb1 = new StringBuffer("abc");
String sb2 = new StringBuffer("abc");
String sb3 = new StringBuffer("abc");
sb3.concat("def");
System.out.println(sb1 + " " + sb2 + " " + sb3);


Mesmo o código acima produzindo o mesmo resultado do código usando String, a explicação é que sb1, sb2 e sb3 são três objetos diferentes. Observe que este código produz algo diferente, pois agora só existe um objeto e três referências:

StringBuffer sb1 = new StringBuffer("abc");
StringBuffer sb2 = sb1;
StringBuffer sb3 = sb2;
sb3.append("def");
System.out.println(sb1 + " " + sb2 + " " + sb3);

Espero que o comportamento de Strings em Java tenha ficado mais claro para você. Leia mais sobre strings no tutorial sobre strings no site da Sun.

Nenhum comentário:

Django - Composição de queries brutas (raw queries)

Django Rest Framework (DRF) é uma poderosa ferramenta para criar Web APIs. Ele requer o uso de Python e do framework Django. Mesmo o Django...