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.

Comparação de Performance
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.