Atenção: esse artigo tem um vídeo complementar. Clique e assista!

[lead]De que se trata o artigo:

O artigo retrata sobre a manipulação de texto em Java utilizando as classes String, StringBuffer e StringBuilder, e como melhorar a utilização de memória, o desempenho do código e facilitar a sua manutenção trabalhando com objetos dessas três classes.

Para que serve:

Os métodos disponíveis na classe String podem servir para uma simples junção de texto, comparação de conteúdo, busca de determinados dados em mensagens advindas de sistemas externos, dentre muitas outras funcionalidades. As classes StringBuffer e StringBuilder, por sua vez, podem ser utilizadas para reduzir a criação de objetos desnecessários na memória, possibilitando que uma mesma String seja modificada diversas vezes.

Em que situação o tema é útil:

Saber como funciona a criação de objetos String no pool e na memória, bem como a utilização e o resultado da execução dos métodos desta classe melhora o desempenho do código. Esta melhoria também é conseguida através da utilização das classes StringBuffer e StringBuilder, pois elas trabalham com objetos String mutáveis que ocupam menos espaço em memória.[/lead]

Neste artigo serão demonstradas algumas maneiras de como trabalhar com “texto” na linguagem de programação Java. Das opções disponíveis, String, StringBuffer e StringBuilder são certamente as classes mais utilizadas. Cada uma delas apresenta particularidades com relação à criação de objetos na memória e no relacionamento com as variáveis de referência, assim como apresentam também métodos específicos para a manipulação de seus conteúdos.

Por isso, abordaremos como fazer uso destas classes de maneira eficiente e as melhores situações onde utilizar cada uma delas. Um passo inicial para que o leitor possa trabalhar de modo a atender aos requisitos do sistema/código em que estiver trabalho e adquirir o conhecimento necessário para se aprofundar mais no tema futuramente.

[subtitulo]O que são Strings?[/subtitulo]

Em Java, String é uma sequência de caracteres utilizada para representação e manipulação de texto. Quando é necessário representar informações textuais em uma aplicação, seja ela para Desktop ou Web, como nome de pessoas, informações sobre endereço ou comentários em uma matéria no jornal, instâncias da classe String serão criadas invariavelmente. Isto é, sempre que precisarmos mostrar alguma informação em um sistema, dificilmente vamos conseguir isso sem o auxílio de Strings.

Em uma visão mais técnica, String é uma classe presente no Java desde a versão 1.0 do JDK que implementa a interface CharSequence (além de Serializable e Comparable, mas essas duas não são interessantes para este artigo). Assim, podemos dizer que Strings nada mais são do que uma cadeia ordenada de caracteres.

[subtitulo]Como utilizar Strings em Java[/subtitulo]

A classe String permite a criação de suas instâncias de diversas maneiras. Ela possui vários construtores que recebem diversos tipos de parâmetros. Pelo fato de ser uma classe amplamente utilizada, ela também fornece um “atalho” para a criação de forma mais rápida de seus objetos, como pode ser observado no código da Listagem 1.

Listagem 1. Maneiras de declarar e inicializar Strings.

  1  package principal;
  2  
  3  public class ClassePrincipal {
  4  
  5    public static void main(String[] args) {
  6      String minhaString1 = new String();
  7      minhaString1 = "Java";
  8      String minhaString2 = "Java";
  9      String minhaString3 = new String("Java");
  10     String minhaString4 = "";
  11     
  12     char[] meusChars = new char[] {'J','a','v','a'};
  13     String minhaString5 = new String(meusChars);
  14   }
  15 }

Vamos à explicação dos pontos pertinentes:

• Linha 6: A classe String, assim como qualquer classe em Java, pode ser instanciada com a utilização da palavra chave new. Neste caso, está sendo criado um objeto String vazio que é atribuído à variável de referência minhaString1;

• Linha 7: A variável minhaString1 recebe o valor “Java”. Como a variável já foi declarada na linha anterior, neste momento acontece apenas a atribuição do conteúdo à variável;

• Linha 8: Representa a maneira mais rápida e fácil de criar um novo objeto desta classe e atribuí-lo a uma variável (no caso, minhaString2);

• Linha 9: Há diversos construtores na classe String. Esta linha demonstra a utilização de um deles: passar um objeto String no construtor de modo a criar um novo objeto String. Com relação à criação de objetos na memória, há uma diferença considerável no modo como as linhas 8 e 9 o fazem. Essa diferença será explicada mais adiante, na seção “Como são criados objetos da classe String”;

• Linha 10: Aqui uma String vazia é criada utilizando-se o mesmo mecanismo de “atalho” que foi demonstrado na linha 8, só que o conteúdo para o qual aponta a variável minhaString4 é uma String vazia. Pode-se dizer que, com relação ao conteúdo, as linhas 6 e 10 fazem a mesma coisa: criam uma String vazia;

• Linhas 12 e 13: Como já foi comentado neste artigo, a classe String possui diversos construtores. Não cabe aqui tentarmos demonstrar a utilização de cada um deles. Estas duas linhas demonstram mais um exemplo de como podemos criar objetos String a partir de outros objetos passados como parâmetro no construtor. A variável minhaString5 tem o mesmo conteúdo das variáveis minhaString1, minhaString2 e minhaString3, porém foi inicializada de uma maneira diferente das demais, recebendo um array de char.

Estes são alguns modos de se declarar e atribuir valor às variáveis do tipo String. Agora veremos alguns dos métodos utilizados para se manipular o conteúdo destas variáveis.

[subtitulo]Métodos úteis da classe String[/subtitulo]

Para se trabalhar com texto, a classe String oferece diversos métodos utilitários. Por questões de espaço, não serão descritos todos aqui. Vamos apenas comentar aqueles que são mais utilizados no dia-a-dia do programador. Logicamente, dependendo do sistema que esteja sendo desenvolvido, alguns métodos serão mais utilizados que outros, mas independente do que seja necessário, a classe String oferece o suporte necessário para operações envolvendo texto: quebrá-lo em várias partes, fazer busca de determinado conteúdo, converter a String em bytes, verificar se o texto começa ou termina com um dado conjunto de caracteres, comparar se uma String é igual à outra (o método equals() é definido na classe Object), dentre outros.

public String replace(CharSequence target, CharSequence replacement)

O método replace() faz a substituição de um determinado conjunto de caracteres por outro, retornando uma nova String. Um exemplo de sua utilização pode ser visto em fóruns de discussão, onde os administradores criam uma lista de palavras de baixo calão que não podem ser publicadas. Caso alguém escreva tais palavras, elas serão automaticamente substituídas por outras, após uma busca adequada (que pode ser feita utilizando o método contains(), explicado mais adiante). Vejamos o código abaixo:

  1      String s = "Este comentário foi péssimo!";
  2      s = s.replace("péssimo","ruim");
  3      System.out.println(s);

A saída na linha 3 será “Este comentário foi ruim!”. O método replace() é Case Sensitive, ou seja, faz diferenciação entre maiúsculas e minúsculas. Portanto, se na linha 2 estivesse escrito s = s.replace(“Péssimo”,“ruim”) nada seria alterado e a frase inicial seria impressa na saída padrão. Também é válido lembrar que o método replace() busca todas as ocorrências do primeiro parâmetro e realiza a substituição pelo segundo. Perceba o que acontece com a busca na próxima sentença:

 
  1      String s = "Gosto de comer frango com batata. Não gosto de batata frita.";
  2      s = s.replace("batata","polenta");
  3      System.out.println(s);

Aqui a impressão será “Gosto de comer frango com polenta. Não gosto de polenta frita.”.

Por fim, o método replace() é sobrecarregado e pode receber apenas um caractere (do tipo primitivo char) como parâmetro para que seja feita a substituição. Exemplo:

  1      String s = "arara";
  2      s = s.replace(‘a’,’e’); // recebendo um char como parâmetro
  3      System.out.println(s);

A saída na linha 3 será “erere”.

public String trim()

O método trim() retorna uma String sem os espaços em branco no começo e no final da mesma. É útil quando se trabalha com preenchimento de formulários. Antes de salvar no banco de dados as informações preenchidas pelo usuário na tela, pode-se chamar o método trim() em todos os campos texto preenchidos pelo operador, de modo a evitar que espaços em branco desnecessários sejam gravados, economizando espaço no banco de dados. O código abaixo exemplifica a utilização do método trim():

 
  1      String s = " Java Magazine ";
  2      s = s.trim();

Após a execução do comando na linha 2, a variável de referência s apontará para a nova String criada sem os espaços que existiam na linha 1 (“Java Magazine”). Todavia, como pode ser observado, o espaço em branco do meio da palavra não foi removido.

public boolean contains(CharSequence s)

O método contains() não tem como retorno uma nova String, mas sim um boolean. Ele avalia se a String original contém a String passada como parâmetro para o método. Este método pode ser utilizado para verificar, por exemplo, se determinado nome está na lista de aprovados de um concurso público ou vestibular. Ou ainda, se um dado específico foi recebido em uma mensagem enviada por outro sistema para realizar validação. As linhas abaixo demonstram uma utilização do método contains():

  1      String s = "001MARCOS PAULO     M19803112";
  2      System.out.println(s.contains("MARCOS"));

Um detalhe interessante é que o método contains() também é Case Sensitive. No caso do código acima, o resultado será true, porém, se a String passada como parâmetro estivesse escrita “Marcos”, o resultado seria false.

public int length()

O método lengh() retorna um int indicando o tamanho da String, ou seja, a quantidade de caracteres que há na String atual. É comum ser utilizado em loops for e blocos de condição if. O trecho a seguir demonstra a aplicação deste método:

1      System.out.println(" Java ".length());

A linha 1 imprimirá “6”, que é a quantidade de caracteres contida na String “ Java ” (aqui o método foi invocado diretamente na instância da classe, sem a utilização de uma variável de referência). Perceba que os espaços em branco também são considerados como caracteres. Se fizéssemos uma chamada ao método trim() encadeado com length(), da forma “ Java ”.trim().length(), a linha 1 exibiria o valor “4” na saída padrão.

public String toUpperCase()

O método toUpperCase() retorna uma nova String com o mesmo conteúdo da original, só que com todos os caracteres em letras maiúsculas. Assim como o método trim(), toUpperCase() também pode ser utilizado antes de salvar dados de um formulário na base de dados. Como as pessoas não possuem o mesmo padrão de digitação (algumas digitam todo o texto em minúsculo, Outras Começam Todas As Palavras De Uma Frase Com Letras Maiúsculas, E HÁ TAMBÉM AQUELAS QUE ESQUECEM O CAPS LOCK LIGADO), o método toUpperCase() pode ser a solução para este problema, salvando todos os dados do cliente em letra maiúscula. Veja um exemplo abaixo:

  1      String s1 = "rodolfo rodrigues";
  2      String s2 = "Alfredo Augusto";
  3      String s3 = "JONNY DEBRUCE";
  4      System.out.println(s1.toUpperCase());
  5      System.out.println(s2.toUpperCase());
  6      System.out.println(s3.toUpperCase());

A saída será, respectivamente:

RODOLFO RODRIGUES

ALFREDO AUGUSTO

JONNY DEBRUCE

Existe ainda o método toLowerCase(), o “irmão menor” do método toUpperCase(), porém menos utilizado. Ele também retorna uma nova String com o mesmo conteúdo da original, mas com todos os caracteres em letras minúsculas. Se na linha 6 estivesse escrito System.out.println(s3.toLowerCase()) a saída seria “jonny debruce”.

public String substring(int beginIndex)

O método substring() retorna uma parte do texto original a partir do índice especificado. Ele recebe como parâmetro um int que indica o ponto de partida (iniciado em zero) pelo qual se deve iniciar a pesquisa. A partir deste índice o método copia tudo que estiver até o final da palavra. Há outra implementação deste método que recebe um índice para indicar o término da pesquisa, mas ele não será abordado aqui. O método substring() pode ser útil para fazer buscas em Strings e capturar determinadas informações, por exemplo:

  1      String s = "Universidade";
  2      System.out.println(s.substring(7));

Como o índice da String inicia em zero, a saída do comando será “idade”. Note que o caractere que estiver ocupando a posição do índice inicial de pesquisa também será incluído na String de resultado. A Tabela 1 demonstra o funcionamento do método para a palavra “Universidade”.

Palavra

U

N

I

V

E

R

S

I

D

A

D

E

Índice

0

1

2

3

4

5

6

7

8

9

10

11

Tabela 1. Caracteres da String “Universidade” copiados pelo método substring().

Os métodos apresentados aqui são apenas alguns dos muitos presentes na classe String. Porém, sabendo-se trabalhar com os que já foram citados é possível realizar muitas tarefas do cotidiano da programação em Java.

A Tabela 2 apresenta uma descrição sucinta de outros métodos úteis presentes na classe String.

Método

Descrição

public char charAt(int index)

Retorna o caractere que estiver na posição do índice passado como parâmetro (iniciado em zero).

public String concat(String str)

Retorna a junção da String na qual o método está sendo chamado adicionando ao seu final a String passada por parâmetro (“str”).

public int indexOf(int ch)

Tem função inversa ao charAt(): retorna a posição (índice) em que se encontra na String o caractere pesquisado.

public boolean startsWith(String prefix)

Retorna true se a String iniciar com o valor passado como parâmetro (Case Sensitive). Caso contrário, retorna false.

public boolean endsWith(String suffix)

Mesmo funcionamento do startsWith(), só que verificando o final da String na qual o método foi invocado.

public boolean equalsIgnoreCase(String anotherString)

Compara se duas Strings são iguais. Para que isso ocorra, é necessário que elas tenham o mesmo comprimento e os caracteres correspondentes nas duas sequências sejam os mesmos, independente se maiúsculos ou minúsculos.

Tabela 2. Outros métodos úteis presentes na classe String.

Até o momento vimos o que fazer para criar Strings e também como manipular seu conteúdo, agora vamos analisar uma das particularidades das Strings: a imutabilidade.

[subtitulo]Como são criados objetos da classe String[/subtitulo]

Certamente você já ouviu uma frase parecida com esta: “Strings são imutáveis, seu valor nunca poderá ser alterado”. Certo... vários artigos e livros falam da imutabilidade das Strings, mas o que isso significa na prática? O que isso influencia no meu código e como eu posso tirar proveito disso? Vamos tentar responder a estas questões. Por exemplo, quando você escreve uma linha de código parecida com esta:

String s = "Java";

O que acontece na prática é:

1. Uma nova String com o valor “Java” é criada no pool de Strings (em breve falaremos sobre pool de Strings);

2. A variável de referência s aponta para o valor criado.

Uma vez criada no pool, a String “Java” não poderá ser alterada. Mas qual é a vantagem disso? Simples: ela será reutilizada em outras partes do código que façam referência à mesma String (outras variáveis de referência apontarão para a mesma String no pool). É aí que entra o chamado “pool de Strings”. O pool é uma espécie de “lista” que armazena todas as Strings criadas no programa. Dessa forma, toda vez que atribuirmos uma String a uma variável de referência, antes da JVM (Java Virtual Machine) criar uma nova String na memória, ela verifica se este valor já está contido no pool. Caso positivo, não é necessário criar uma nova, fazendo com que a variável de referência apenas “aponte” para a String existente. Então, na verdade, temos um passo anterior aos dois descritos previamente:

1. A JVM verifica se a String “Java” já existe no pool;

a. Se já existe, a variável de referência s apontará para este valor;

b. Senão, uma nova String com o valor “Java” será criada no pool de Strings e a variável de referência s apontará para este valor.

Vejamos outro exemplo:

  1      String s1 = "Brasil";
  2      String s2 = "Brasil";
  3      s1 = "Argentina";

Eis o que aconteceu no código durante a sua execução (vamos assumir que este código está inserido em um método main() que contém somente estas três instruções):

• Na linha 1, a JVM verificou que a String “Brasil” (Case Sensitive) ainda não havia sido criada no pool de Strings, então, criou uma nova String no pool e a variável de referência s1 apontou para a String recém-criada;

• Na linha 2, antes de fazer a atribuição de valor para s2, a JVM foi novamente ao pool e verificou que já existe uma String chamada “Brasil”. Por isso, não foi criada uma nova String no pool;

• A variável s2 passa a fazer referência à mesma String criada anteriormente;

• Na linha 3 a JVM faz o mesmo procedimento da linha 1. Como não existe a String “Argentina”, ela cria uma nova String no pool;

• Por fim, a variável s1 aponta para a “Argentina” criada no pool e deixa de apontar para a String “Brasil”.

A Figura 1 demonstra visualmente como todo esse procedimento acontece por trás dos panos.

Figura 1. Como acontece a criação de Strings no pool.

Ok, mas o que tudo isso tem a ver com a imutabilidade das Strings?

Imagine que em outros pontos do seu código você precise alterar a String “Brasil”. Como essa String é compartilhada por outras variáveis de referência (s1 e s2), se não houvesse a imutabilidade, cada alteração feita em uma das variáveis que apontam para este conteúdo seria refletida nos demais pontos onde são feitas referências ao mesmo texto, o que poderia nos trazer problemas. Por isso que grande parte dos métodos da classe String não tem retorno void, mas sim String. Dessa forma, eles retornam sempre uma nova String modificada. A original estará lá, intacta no pool, enquanto novas Strings estarão sendo criadas. Vejamos um exemplo para ilustrar o conceito:

  1      String s1 = "Meu nome era ";
  2      String s2 = "Michel";
  3      String s3 = s1;
  4      s1.replace("era","é");
  5      String s4 = s3.replace("era","sempre foi ");
  6      s3 = s4 + s2;
  7      System.out.println(s3);

Analisando cada uma das linhas:

• Linha 1: Criada no pool de Strings, a String “Meu nome era ” é associada à variável de referência s1;

• Linha 2: Criada no pool de Strings, a String “Michel” é associada à variável de referência s2;

• Linha 3: A variável de referência s3 passa a fazer referência à String definida na linha 1. Deste modo nenhuma nova String é criada no pool;

• Linha 4: Neste ponto temos uma das grandes “pegadinhas” dos exames de certificação. O resultado da execução desta linha é a criação de uma nova String, com o conteúdo “Meu nome é ”. Porém, o retorno do método não é atribuído a nenhuma variável de referência, deixando a nova String “perdida” no pool, pois não há ninguém que faça referência a ela. Dessa forma, a String referenciada por s1 não é alterada. As Strings “era” e “é” também são adicionadas ao pool de Strings nesta linha;

• Linha 5: Novamente uma String é criada no pool, como resultado da execução do método replace(): “Meu nome sempre foi ”. Esta nova String é associada à variável de referência s4. Vale a pena ressaltar que as variáveis s1 e s3 ainda fazem referência à String “Meu nome era ”. Se a String referenciada por s1 tivesse sido alterada na linha 9, o resultado da linha 10 não seria o esperado, pois como s3 também referencia a mesma String, o método replace() não teria feito nenhuma alteração. Há também a criação da String “sempre foi” no pool;

• Linha 6: Vemos aqui um exemplo de como o Java oferece a sobrecarga do operador “+” para manipulação de Strings. A variável s3 recebe a concatenação (junção) das Strings referenciadas por s4 e s2. Esta é uma das maneiras mais utilizadas para unir duas ou mais Strings. O método concat() também faz concatenação de Strings, porém é menos utilizado pelo fato do operador “+” ser uma maneira mais rápida de fazê-lo;

• Linha 7: É impresso na saída padrão “Meu nome sempre foi Michel”.

Agora vamos entender quantas Strings foram criadas no pool de Strings:

• Linha 1: Uma String “Meu nome era ”;

• Linha 2: Uma String “Michel”;

• Linha 3: Nenhuma String é criada no pool, pois a String na linha 1 é reutilizada;

• Linha 4: Atenção! Aqui são criadas três novas Strings: “era”, “é” e “Meu nome é ”. Porém, nenhuma delas é referenciada por qualquer variável;

• Linha 5: Duas Strings novas são criadas: “sempre foi ” e “Meu nome sempre foi ”. Esta última é atribuída à variável s4. A String “era” já existe no pool e não será criada novamente;

• Linha 6: Mais uma String é criada no pool, “Meu nome sempre foi Michel”.

Como pudemos observar, um total de oito Strings foram criadas no pool, entretanto algumas delas não são referenciadas por nenhuma variável e são elegíveis para serem coletadas pelo Garbage Collector (o funcionamento do Garbage Collector é assunto para outro artigo).

Para aperfeiçoar a utilização das Strings, uma boa prática é valer-se de um artifício bastante comum que é a criação de constantes (variáveis declaradas como public static final). Com as constantes, as Strings já são criadas no pool e podem ser reutilizadas em várias partes do código, diminuindo assim a criação de objetos desnecessários. Elas dificilmente serão elegíveis para serem coletadas pelo Garbage Collector, pois sempre haverá uma variável referenciando-a. Segue um exemplo de declaração de uma constante:

public static final String nomeDaEmpresa = "Info Telecom LTDA.";

Foi comentado na seção “Como utilizar Strings em Java” que a classe String apresenta uma particularidade interessante na criação de instâncias da sua classe. Durante a explicação dos métodos desta classe, os exemplos demonstram a criação de Strings sem a utilização do construtor e da palavra chave new. Este é um detalhe na criação de objetos que pode fazer grande diferença com relação ao consumo de memória da aplicação. Analisemos o código abaixo:

String s = new String("Java");

Nesta linha, acontece a criação de uma String “Java” no pool de Strings e também um novo objeto String na memória comum, que será referenciado por s. Ou seja, sempre que é criada uma String através da palavra chave new, um novo objeto será criado na memória. A não ser que seja fundamental para o seu código, procure evitar a criação de Strings desta forma.

Durante a codificação, porém, pode ser necessária uma constante alteração de objetos String (para a criação de sentenças SQL, por exemplo). Com isso, se optar por manipular as sentenças utilizando objetos String, você poderá ter vários deles abandonados ao final da execução da consulta no pool. Para resolver este problema, há outras duas classes que podem ser usadas para se trabalhar com Strings sem que sejam criados vários objetos String desnecessários e ocupar menos memória: StringBuffer e StringBuilder.

[subtitulo]StringBuffer e StringBuilder[/subtitulo]

Conforme já descrevemos, Strings são imutáveis: elas não têm seus valores alterados após sua criação. Entretanto, em alguns momentos necessitamos fazer várias modificações em uma mesma String. Para estes casos, as classes StringBuffer e StringBuilder auxiliam na manipulação de Strings sem a criação de vários objetos na memória e, agora sim, alterando o mesmo objeto diversas vezes. Ambas as classes têm o mesmo propósito: trabalhar com objetos de texto que podem ser alterados. No entanto, há uma diferença importante entre elas (além da classe StringBuilder estar disponível apenas a partir da versão 1.5 do JDK), a sincronização.

A classe StringBuffer é considerada uma “classe sincronizada”, ou seja, possui seus métodos sincronizados (somente uma Thread pode acessar por vez). Sabemos que não é possível declarar uma classe com o modificador de acesso syncronized, mas pode-se fazê-lo nos métodos, e é isso que a classe StringBuffer faz.

A explicação técnica sobre sincronização seria assunto para um artigo sobre “Threads”, mas basicamente podemos dizer que métodos sincronizados são mais “lentos” do que métodos não sincronizados, visto que somente uma Thread pode executar cada método por vez. Assim, a classe StringBuilder seria mais indicada por ter um melhor desempenho do que sua predecessora, para aplicações nas quais a velocidade da execução é importante. A classe StringBuffer, por sua vez, é indicada para aplicações que envolvem utilização de várias linhas de execução simultâneas (threads), como sistemas Web.

Analisemos o trecho de código que utiliza a classe StringBuilder:

  1      String tabela = "funcionario";
  2      String orderBy = " ORDER BY nome";
  3      StringBuilder sql = new StringBuilder("SELECT cpf, nome ");
  4      sql.append("FROM ");
  5      sql.append(tabela);
  6      sql.append(" WHERE sexo = 'M'").append(orderBy);

Vejamos o que acontece a partir da linha 3. É criado um objeto StringBuilder e atribuído à variável de referência sql. Nas linhas seguintes, são feitas várias chamadas ao método append(), de modo que novos dados são adicionados ao objeto criado. Perceba que, ao contrário dos objetos String, o StringBuilder não necessita que o resultado da execução do método append() seja atribuído novamente à variável sql que está referenciando o objeto (por exemplo, na linha 4 não é feito: sql = sql.append(“FROM”)). O retorno do método append() é o mesmo objeto que foi criado na linha 3, modificado de acordo com os parâmetros passados. Dessa forma, apenas um objeto StringBuilder foi criado e alterado durante a execução do código.

Um detalhe a ser notado é que, assim como a classe String, os métodos das duas outras classes têm como retorno um objeto StringBuilder/StringBuffer. Assim, eles podem ser encadeados em várias chamadas sequenciais. Apesar de dificultar um pouco a manutenção do código, é um artifício válido. Verifiquemos as seguintes instruções:

  1      StringBuffer buffer = new StringBuffer("2468");
  2      buffer.reverse().insert(1,"xxx").append("999");
  3      System.out.println(buffer.toString());

A saída deste código é “8xxx642999”. Para entender o que aconteceu, vejamos linha por linha:

• Linha 1: É criado um novo objeto StringBuffer com o valor “2468” e atribuído à variável buffer;

• Linha 2: Aqui acontece a execução de três métodos, sendo que o resultado da chamada do método mais à esquerda será o objeto que irá chamar o método seguinte. No caso, o primeiro método a ser executado será o reverse(). Como resultado desse método, temos o mesmo objeto StringBuffer invertido, com o valor “8642”. Este objeto, por sua vez, faz uma chamada ao método insert(), que irá inserir no índice 1 (lembrando que os índices iniciam em zero) o valor “xxx”, retornando assim o objeto StringBuffer “8xxx642”. Por fim, o método append() adiciona o conteúdo “999” ao final do objeto;

• Linha 3: Acontece a impressão na saída padrão do sistema do valor “8xxx642999”.

O trecho de código demonstrado acima é equivalente a:

 
  1      StringBuffer buffer = new StringBuffer("2468");
  2      buffer.reverse();
  3      buffer.insert(1,"xxx");
  4      buffer.append("999");
  5      System.out.println(buffer.toString());

A Figura 2 mostra visualmente como acontece a modificação do objeto StringBuffer na memória. Perceba que a variável buffer aponta sempre para o mesmo objeto.

Figura 2. Modificações ocorridas em um único objeto StringBuffer na memória.

Além dos outros métodos que possibilitam a manipulação de texto (insert(), delete(), reverse(), substring(), trimToSize(), replace() e demais), “Builders” e “Buffers” são comumente usadas para concatenar mensagens (de erro, warning ou sucesso) a serem exibidas ao operador e também na criação de mensagens que devem ser enviadas a outros sistemas (geralmente sistemas legados, escritos em outras linguagens, como Cobol e RPG/AS400) via fila MQ series ou RTP (Real Time Protocol), para transferência de dados. Estas mensagens geralmente possuem um layout pré-definido, contendo as informações esperadas e os respectivos tamanhos dos campos a serem enviados.

Na obra de Katy Sierra e Bert Bates (vide seção Livros) é descrita outra utilização das classes StringBuffer e StringBuilder: “Um emprego comum dos objetos StringBuffer é na E/S (entrada e saída) de arquivos quando fluxos grandes de entrada e com alteração constante estão sendo manipulados pelo programa. Nesses casos, grandes blocos de caracteres são manipulados como unidade, e os objetos StringBuffer é (sic) a maneira ideal de manipular um bloco de dados, passá-lo adiante e, em seguida, reutilizar o mesmo espaço na memória para manipular o bloco seguinte.”

[subtitulo]Conclusões[/subtitulo]

Este artigo demonstrou a diferença entre criar um objeto String utilizando ou não o operador new e o que isso representa com relação aos objetos na memória. Também vimos alguns dos métodos mais usados da classe String, que podem ser usados desde uma simples verificação de senha em uma tela de login até tratamentos complexos em mensagens recebidas por sistemas externos. A imutabilidade das Strings também foi abordada, demonstrando como podemos evitar a criação de objetos desnecessários e o desperdício de memória com a utilização de constantes.

Por fim, comentamos sobre as classes String mutáveis: StringBuffer e StringBuilder. Elas auxiliam o trabalho com texto fornecendo objetos que podem ser modificados poupando espaço na memória. Quando necessitamos trabalhar com blocos de texto que precisam ser modificados frequentemente, estas duas classes são as mais indicadas.

Certamente não abordamos tudo o que envolve a manipulação de texto. Todas as três classes dispõem de inúmeros métodos para manipular texto, e o JavaDoc da Sun fornece um grande auxílio para investigações sobre seus funcionamentos.

[nota]Links

JavaDoc da Sun para a versão 1.4 da linguagem.
java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html

Livros

Guia completo de estudos para certificação em Java, Philip Heller e Simon Roberts, Ed. Ciência Moderna, 2004

Livro voltado para os estudos das cerificações SCJP e SCJD. Possui linguagem simples e acompanha um CD.

Certificação Sun para programador & desenvolvedor Java 2 (3ª Edição), Kathy Sierra e Bert Bates, Ed. Alta Books, 2003

O livro mais indicado para estudos de certificação para a versão 1.4 do Java. Também aborda SCJP e SCJD. Contém vários exercícios desafiadores e um conteúdo bem explicado. O livro é bastante divertido de ler e não há problemas com tradução.[/nota]