A classe java.lang.String

Paulo Silveira

Como tirar o melhor proveito da classe String, e saber como funciona a imutabilidade e o seu pool interno.



Introdução


Engana-se aquele que acha este um assunto simples e para principiantes.

Dominar a classe String, e saber seus pontos fortes e fracos, é muito importante para uma economia de memória e processamento. A classe String pode ser o grande gargalo de uma aplicação sua, e você nem sabe.

Pontos lentos da String
Vamos começar pelas coisas que são realmente extremamente lentas no API da String:

1 String temp = "lowercase";
2 temp = temp.toUpperCase();


Por incrível que pareça, os métodos String.toLowerCase() e String.toUpperCase() verificam, todas as vezes, se você se encontra na Turquia, pois lá UpperCase e o LowerCase são diferentes. Além disso, esses métodos não checam se a variável já está em lower/upper case, e para piorar, criam um StringBuffer de tamanho default (ao invés de criar com o string.lenght() respectivo), o que causará a repetitiva alocação de memória e cópia de arrays no caso de Strings grandes.

No jdk 1.4, isto já não ocorre mais, e o método ficou muito mais rápido. Apenas a verificação "turca" ainda ocorre. Outro ponto lento da String, conhecido por todos, é a concatenação.

1 String temp = "";
2 for (int i = 0; i < 10; i++) {
3     temp += "string";
4 }


Este já é um clássico. Concatenação através do operador "+" é muito lenta. E o motivo é simples, a JVM cria uma nova String para cada concatenação, o que acumula memória e estressa o Garbage Collector muito facilmente. O caso acima alocou espaço para 12 Strings! Como todos sabem, alocação de memória e chamada de "new" são relativamente lentas. A solução também é conhecida por todos:

1 StringBuffer buffer = new StringBuffer();
2 for (int i = 0; i < 10; i++) {
3     buffer.append("string");
4 }
5 String temp = buffer.toString();


O java.lang.StringBuffer não cria uma nova instância da classe a cada concatenação. Este tipo de código é assustadoramente mais rápido. Se a sua aplicação ou loop for muito intensiva em concatenação, é altamente recomendável utilizar StringBuffers.



O Pool interno do java
Este é um tema muito discutido, e muitas pessoas não entendem perfeitamente o porquê das Strings serem "imutáveis" em java. Algumas pessoas confundem isso com o problema de passar por referência ou por valor.

O que vamos estudar aqui?

1 String teste1 = new String("guj.com.br");
2 String teste1 = new String("guj.com.br");
3 
4 System.out.println(teste1 == teste2);
5 System.out.println(teste1.equals(teste2));


Não devemos utilizar operador == para comparar o valor de Strings, já que eles são objetos, e não tipos primitivos. Então o resultado deste programa não deve assustar ninguém:

false
true


Já este, pode assustar algumas pessoas, especialmente as que já tem consolidado o fundamento anterior:

1 // repare que estas duas linhas são as únicas com mudanças
2 String teste1 = "guj.com.br";
3 String teste2 = "guj.com.br";
4 
5 System.out.println(teste1 == teste2);
6 System.out.println(teste1.equals(teste2));


E a macabra saída:

true
true


Isto tudo acontece devido ao Pool (tipo um cache) de Strings que a JVM utiliza para otimizar o uso de Strings. A seguir veremos como isto funciona, e o porquê de uma String ser imutável.


A imutabilidade da String
1 String temp = "lowercase";
2 temp.toUpperCase();
3 System.out.println(temp);


Testando este código, a saída é "lowercase", e não "LOWERCASE", isto você já sabe.

Todos os métodos que efetuariam mudanças na String, não realizam isto sobre a String em questão, e sim sobre uma cópia dela, retornando esta cópia com a modificação pedida. Mas porque?

Aí que o Pool e a imutabilidade se encontram. Com o objetivo de gastar menos memória com as Strings, a JVM possui privativamente um lugar com referências para algumas Strings. Qando a JVM é inicializada, todas as Strings literais são inclusas neste pool, de tal maneira que caso ela apareça denovo (no mesmo pacote), ela será a referência para o mesmo objeto. isto é, o operador == vai funcionar. Desta maneira a JVM economiza memória, e muita.

Você pode também se beneficiar deste pool. Se você sabe que a sua aplicação toda hora utiliza muitas Strings iguais, você pode pedir para que elas sejam sempre adicionadas ao pool, e que seja verificada se ela já não esta lá, se estiver, pega uma referência para a que já está. Para isso, você usa o método intern():

1 String teste1 = new String("guj.com.br");
2 String teste1 = new String("guj.com.br");
3 
4 // continua falso:
5 System.out.println(teste1 == teste2);
6 // mas este é verdadeiro!
7 System.out.println(teste1.intern() == teste2.intern())


Obs: Apesar de você poder utilizar o operador == após o intern(), realmente você não deve utilizar o == se o que você quer é comparar o conteúdo da String, mesmo se todas estiverem "internizadas".

Por isso, se as Strings fossem mutáveis, você poderia estar modificando mais do que apenas o Objeto que você quer modificar, já que existe este pool! Os resultados seriam catastróficos.

Você pode ler mais sobre o pool, imutabilidade e o intern dentro da especificação java, no tópico 3.10.5.



Copyright © 2002-2006 GUJ | Todas as marcas e marcas registradas que aparecem no GUJ são de propriedade de seus respectivos donos