Já vimos muitos desenvolvedores usar StringBuilder ou StringBuffer sem nem ao menos saber qual o real impacto de sua utilização em detrimento da String “normal”. Neste artigo explicaremos com detalhes e exemplos a diferença entre String, StringBuilder e StringBuffer. Assim você será capaz de julgar com eficácia qual melhor recurso para fazer uso em um momento adequado.
Muitos preferem o uso de concatenação de strings usando o operador “+”, porém você verá neste arquivo o quão problemático pode ser o uso deste operador com frequência em toda a aplicação, e provavelmente a partir deste artigo você dará mais importância ao uso do StringBuilder e StringBuffer, quando necessário.
Strings são imutáveis
Você já deve ter lido ou ouvido em algum lugar algo sobre: “String são imutáveis”, ou seja, você não pode mudar seu valor após a primeira atribuição. Então você se pergunta: se Strings são imutáveis, então porque eu consigo concatenar vários valores a uma String, tornando-a diferente da original?
Acontece que na verdade você não está “concatenando” nada, e sim criando um novo objeto em memória. Cada vez que você acha que está concatenando uma String com outra, você está criando diversos objetos distintos em memória, e as Strings “antigas” perdem referência, mas continuam lá. Vamos ver um exemplo na Listagem 1 para entender o que acontece por trás dos panos.
public class ConcatenaString {
public static void main (String[] args){
//Cria um objeto em memória
String str = "hello";
/*
* Sabemos que nossa string agora será: "hello world".
*
* Ocorre que é criada a String "world" em memória,
* depois a JVM cria um outro objeto "hello world".
* No total vão ser 3 objetos para que essa 'concatenação'
* ocorra.
* */
str.concat(" world");
/*
* O mesmo conceito é aplicado acima. É criada uma string
* "from java" em memória, depois é criada uma
* nova juntando "hello world from java".
* No total temos agora 5 objetos em memória,
* sendo que apenas 1 estamos utilizando,
* veja que desperdício.
* */
str += " from Java";
}
}
Caso você não esteja convencido que usar concatenação dessa forma é muito prejudicial a performance da aplicação, vamos ver o teste da Listagem 2.
public class ConcatenaString {
public static void main (String[] args){
String strFinal = "";
/*
* Vamos concatenar 65536 vezes o caractere 'a',
* então entenda que cada vez que passarmos no laço
* a JVM irá criar um novo objeto em memória.
* */
for(int i = 0; i < 65536; i ++){
strFinal += "a";
}
}
}
Veja quanto tempo demora em executar o código acima na sua máquina.
StringBuilder vs String
Como você já deve ter percebido, a String não deve ser usada para concatenação de outras Strings ou caracteres. Na seção acima apenas falamos sobre o quão prejudicial pode ser o seu uso, mas agora vamos comparar tal uso com o StringBuilder, que é a maneira correta de concatenar Strings ou caracteres.
Vamos usar o mesmo exemplo da Listagem 2 com algumas modificações para vermos de fato a velocidade de execução do StringBuilder e do String em concatenar valores, agora sim ficará nítido a diferença entre estes. Observe a Listagem 3.
public class ConcatenaString {
public static void main (String[] args){
/*
* ###########################################
* INICIO BLOCO CONCATENAÇÃO COM OPERADOR '+'
* ###########################################
* */
String strFinal = "";
long tStart = System.currentTimeMillis();
/*
* Vamos concatenar 65536 vezes o caractere 'a',
* então entenda que cada vez que passarmos no laço
* a JVM irá criar um novo objeto em memória.
* */
for(int i = 0; i < 100000; i ++){
strFinal += "a";
}
long tEnd = System.currentTimeMillis();
long tResult = tEnd - tStart;
System.out.println("Tempo de Execução com operador
'+' = "+tResult+" ms");
/*
* ###########################################
* FIM BLOCO CONCATENAÇÃO COM OPERADOR '+'
* ###########################################
* */
/*
* ###########################################
* INICIO BLOCO CONCATENAÇÃO COM StringBuilder
* ###########################################
* */
StringBuilder strBuilder = new StringBuilder();
tStart = System.currentTimeMillis();
for(int i = 0; i < 100000; i ++){
strBuilder.append("a");
}
tEnd = System.currentTimeMillis();
tResult = tEnd - tStart;
System.out.println("Tempo de Execução com StringBuilder
= "+tResult+" ms");
}
}
Saída provável:
Tempo de Execução com operador '+' = 3753 ms
Tempo de Execução com StringBuilder = 4 ms
Na máquina em que executamos o código acima, a saída foi 3753ms para o String com operador '+' e 4ms para o StringBuilder. Usamos apenas 100mil caracteres, mas imagine se fossem 1 milhão de caracteres? Faça você mesmo esse teste.
Perceba que para não tornar o testo tendencioso, colocar o tStart (tempo inicial) e o tEnd (tempo final) bem no início e no fim do laço for, desconsiderando qualquer outro código que venha antes ou depois, como atribuições e escritas no console, assim avaliamos puramente a concatenação de ambos.
Sem dúvida a performance do StringBuilder em detrimento do String comum é exponencialmente melhor quando precisamos concatenar valores. Isso foi provado com a Listagem 3. Acontece que o StringBuilder é mutável, ou seja, a cada “append(valor)” que fizemos no laço concatenamos de fato um novo valor a String já existente, sem a necessidade da criação de um novo objeto em memória.
Quando usar o operador '+'
Bom, se falamos que o operador '+' não deve ser usado para concatenação de Strings, pois cria novos objetos em memória, então em que situação deve-se usá-lo? Há uma situação ou ele nunca deve ser usado?
Sim, há situações em que ele deve ser usado, que são aquelas em que você irá juntar uma enorme String para facilitar a legibilidade do código, sem a necessidade de criação de um novo objeto, ou mais conhecido por “multi-line Strings”. Veja o exemplo da Listagem 4.
public class ConcatenaString {
public static void main (String[] args){
/*
* A nossa string abaixo é um uso ideal para o operador '+',
* pois não estamos criando nenhum novo objeto em memória,
* apenas melhorando a
* legibilidade do código.
* */
String strFinal = "Feliz " +
"Natal " +
"Aos Leitores "+
"da DEVMEDIA "+
"hohoho...";
//Também poderiamos usar desta forma sem
//prejudicar a performance do programa
int x = 10;
int y = 20;
System.out.println("x:"+x+" y:"+y);
}
}
StringBuilder vs StringBuffer
Ambos são bem mais rápidos para concatenação de valores do que a String comum e fazem exatamente a mesma função. A principal diferença é que o StringBuffer é sincronizado, enquanto que o outro não. Assim, você garante a consistência do seu código quando há diversas threads lendo ou modificando a mesma String. Para esses casos, o ideal é usar o StringBuffer.
Porém o StringBuilder ainda é mais rápido do que o StringBuffer: veja o teste feito através da Listagem 5.
public class ConcatenaString {
public static void main (String[] args){
long tStart, tEnd, tResult;
/*
* ###########################################
* INICIO BLOCO CONCATENAÇÃO COM StringBuilder
* ###########################################
* */
StringBuilder strBuilder = new StringBuilder();
tStart = System.currentTimeMillis();
for(int i = 0; i < 100000; i ++){
strBuilder.append("a");
}
tEnd = System.currentTimeMillis();
tResult = tEnd - tStart;
System.out.println("Tempo de Execução com StringBuilder =
"+tResult+" ms");
/*
* ###########################################
* FIM BLOCO CONCATENAÇÃO COM StringBuilder
* ###########################################
* */
/*
* ###########################################
* INICIO BLOCO CONCATENAÇÃO COM StringBuffer
* ###########################################
* */
StringBuffer strBuffer = new StringBuffer();
tStart = System.currentTimeMillis();
for(int i = 0; i < 100000; i ++){
strBuffer.append("a");
}
tEnd = System.currentTimeMillis();
tResult = tEnd - tStart;
System.out.println("Tempo de Execução com StringBuffer =
"+tResult+" ms");
/*
* ###########################################
* FIM BLOCO CONCATENAÇÃO COM StringBuffer
* ###########################################
* */
}
}
Saída Provável:
Tempo de Execução com StringBuilder = 7 ms
Tempo de Execução com StringBuffer = 11 ms
Veja também o gráfico da Figura 1 ilustrando outro exemplo: perceba que a diferença é mínima, tendo alguns picos em momentos específicos da execução.
Finalizamos esse artigo com um conceito bem ilustrado e exemplificado sobre as diferenças entre String, StringBuilder e StringBuffer.