|
|
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:
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:
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.
|
|
|