Classe String do Java
Uma análise mais profunda da classe String do Java — seu design, estrutura interna e métodos principais.
String é o tipo de referência mais utilizado em Java, de longe. Você o encontrou no primeiro dia — String name = "Ada"; — e ele se inseriu no seu código sem muito alarde. Esta parte do livro vai a fundo por baixo dessa superfície. A classe tem mais profundidade do que aparenta: um layout interno fixo, sintaxe específica da linguagem que outros tipos não possuem, um pool de memória que afeta a identidade, e uma escolha de design deliberada (imutabilidade) que reverbera em threading, hashing e segurança.
Este capítulo é o mapa. O restante da Parte 9 preenche cada região.
O que é uma String?
Uma String é um objeto Java comum — java.lang.String, no mesmo pacote que Object e Integer. É final, portanto não pode ser subclassificada, e todo método que "modifica" uma string na verdade retorna uma nova. O original nunca é alterado.
String greeting = "hello";
greeting.toUpperCase(); // returns "HELLO" — discarded
System.out.println(greeting); // still prints "hello"
greeting = greeting.toUpperCase();
System.out.println(greeting); // now prints "HELLO"Esse hábito de retornar-uma-nova é o fato mais importante sobre a classe. Ele é abordado em profundidade em Imutabilidade da String em Java.
Tratamento no nível da linguagem
Duas partes da sintaxe são reservadas para String e não são extensíveis a outros tipos:
- Literais de string —
"hello"produz um objetoStringdiretamente, semnew. O compilador também deduplica literais idênticos no pool de strings. - O operador
+— sobrecarregado para strings:"a" + "b"é"ab". Até expressões mistas como"score: " + 42funcionam, pois o Java converte o lado direito para umaString.
Por trás das cenas, compiladores Java modernos traduzem cadeias de + usando StringBuilder ou o StringConcatFactory baseado em invokedynamic, portanto raramente você precisa escrever a concatenação manualmente. O compilador sabe o que fazer.
Layout interno
Antes do Java 9, cada String armazenava um char[] — dois bytes por caractere independentemente do conteúdo. O Java 9 introduziu compact strings: o array de apoio agora é um byte[], mais um campo coder de um byte que registra se os bytes são Latin-1 (um byte por caractere) ou UTF-16 (dois bytes). Para texto que cabe em Latin-1 — a maior parte do código, configuração, identificadores, inglês simples — isso reduz aproximadamente pela metade o consumo de memória sem alterar a API.
Você não pode ver o campo, não pode alterá-lo, não precisa pensar sobre ele. Mas é por isso que programas com uso intensivo de strings no JDK 9+ usam consideravelmente menos heap do que usavam no JDK 8.
As famílias de métodos principais
A API de String é grande, mas se organiza em alguns grupos reconhecíveis:
Inspeção. length(), isEmpty(), isBlank(), charAt(i), codePointAt(i), hashCode().
Busca. indexOf, lastIndexOf, contains, startsWith, endsWith, matches.
Extração. substring(start), substring(start, end), chars(), codePoints(), toCharArray().
Transformação. toUpperCase(), toLowerCase(), trim(), strip(), replace, replaceAll, replaceFirst, concat.
Divisão e junção. split, String.join — abordados em split() e join().
Formatação. String.format, o método de instância formatted e saída no estilo printf — abordados em Formatação de strings.
Comparação. equals, equalsIgnoreCase, compareTo, compareToIgnoreCase, contentEquals — abordados em Comparação de strings.
Conversão. valueOf (estático), toString (instância), helpers de parsing em Integer, Double, etc. — abordados em Conversões de strings.
Listagens completas da API estão no Javadoc do JDK. A habilidade está em reconhecer qual família usar, não em memorizar cada sobrecarga.
Strings são sequências de unidades de código UTF-16
charAt(i) e length() contam unidades de código UTF-16, não caracteres Unicode. Para texto dentro do Plano Multilíngue Básico (a maior parte dos scripts comuns), um char = um caractere e a distinção nunca importa. Para caracteres suplementares — a maioria dos emojis, algumas extensões CJK, scripts antigos — um único caractere visível ao usuário ocupa dois chars, um par substituto.
String emoji = "🙂";
System.out.println(emoji.length()); // 2 — two code units
System.out.println(emoji.codePointCount(0, emoji.length())); // 1 — one code pointSe você precisar iterar por ponto de código Unicode, use codePoints() ou codePointAt. Para a maioria dos casos de uso semelhantes ao ASCII — dividir CSVs, formatar linhas de log, comparar identificadores — length() e charAt são exatamente o que você quer.
Primos mutáveis: StringBuilder e StringBuffer
Quando você precisa construir uma string pedaço por pedaço, o uso repetido de += aloca uma nova String a cada passo. A biblioteca padrão fornece dois companheiros mutáveis para esse caso:
StringBuilder— rápido, single-threaded.StringBuffer— mesma API, métodos sincronizados, útil apenas quando mais de uma thread escreve no mesmo buffer.
Eles têm APIs paralelas e capítulos paralelos nesta parte do livro.
Um exemplo prático
Um pequeno exercício que toca nas famílias mais comuns — inspeção, busca, extração, transformação e conversão — na mesma entrada. Leia a saída linha por linha; cada chamada ilustra uma ferramenta da lista acima.
A última linha é a conclusão. Após cada transformação que usamos, line em si é byte-idêntica ao literal com o qual começamos — prova de que o modelo de retornar-uma-nova é real, não apenas uma nota de documentação.
O que vem a seguir
Strings vêm com um modelo de memória único na biblioteca padrão: literais idênticos compartilham armazenamento, e você pode optar por inserir strings arbitrárias nesse pool compartilhado com uma única chamada de método. Continue para Pool de Strings do Java.