Diferenças entre String, StringBuilder e StringBuffer em Java

Veja neste artigo as principais diferenças entre String, StringBuilder e StringBuffer em Java e veja como otimizar a sua aplicação usando-as corretamente.

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"; } }
Listagem 1. Entendendo a “falsa” concatenação com a classe String

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"; } } }
Listagem 2. Concatenando 65536 caracteres (caractere por caractere)

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
Listagem 3. Comparando performance de String e StringBuilder

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); } }
Listagem 4. Usando o operador '+' em momentos oportunos

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
Listagem 5. Comparação performance StringBuilder e StringBuffer

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.

Figura 1. Comparação de Performance

Finalizamos esse artigo com um conceito bem ilustrado e exemplificado sobre as diferenças entre String, StringBuilder e StringBuffer.

Artigos relacionados