Java Strings
Quase todo programa trabalha com texto: nomes de alunos, mensagens, endereços, senhas. Em Java, um texto é uma String. Nesta aula você vai entender o que ela é, como criar uma, medir o tamanho e, principalmente, como comparar duas Strings do jeito certo, porque o jeito "óbvio" (==) é a pegadinha mais famosa do Java.
- O que é uma String e como criar uma com aspas duplas.
- Como descobrir quantos caracteres ela tem com
length(). - Por que uma String nunca muda (imutabilidade) e o que isso significa na prática.
- A diferença entre
==eequals, com a prova real de que==engana. - Como comparar ignorando maiúsculas (
equalsIgnoreCase) e em ordem alfabética (compareTo). - A diferença entre String vazia (
"") enull, e aNullPointerException.
Todos os exemplos rodaram no computador fictício da Ana (o notebook), com o JDK 25. Você pode repetir cada um no editor do guia, nas caixas Experimente.
A ideia: um colar de letras
Imagine um colar de contas, em que cada conta tem uma letra. O colar Ana tem três contas: A, n e a. Uma String é exatamente isso: uma sequência de caracteres, um atrás do outro, em ordem. O nome vem do inglês string, "cordão", "fileira" (como em a string of pearls, "um colar de pérolas").
Os caracteres podem ser letras, números, espaços, pontuação, acentos, emojis. Na aula char e boolean você viu o tipo char, que guarda um caractere. A String guarda vários (ou nenhum).
| Tipo | Guarda | Escrito com | Exemplo |
|---|---|---|---|
char | Um caractere | Aspas simples '…' | 'A' |
String | Zero ou mais caracteres | Aspas duplas "…" | "Ana", "A", "" |
Criar uma String
Você cria uma String escrevendo o texto entre aspas duplas. Esse texto escrito direto no código se chama literal (ele é "literalmente" o valor). Depois, guarda numa variável do tipo String:
public class Main {
public static void main(String[] args) {
String nome = "Ana";
String fruta = "maçã";
String vazia = "";
System.out.println(nome);
System.out.println(nome.length());
System.out.println(fruta.length());
System.out.println(vazia.length());
System.out.println("Ana Souza".length());
}
}
- String
- O tipo da variável. Repare no S maiúsculo:
Stringnão é um tipo primitivo comoint; é uma classe que vem pronta com o Java (você vai estudar classes no Nível 2). Por enquanto, basta saber que é o tipo dos textos. - nome
- O nome da variável, como em qualquer variável.
- "Ana"
- O literal: o texto entre aspas duplas. As aspas não fazem parte do texto; elas só marcam onde ele começa e termina.
- ""
- Duas aspas coladas: a String vazia, um texto com zero caracteres. É um texto válido, só que sem nada dentro.
- nome.length()
- Pergunta à String quantos caracteres ela tem. O ponto quer dizer "da String
nome, use o métodolength" (length = comprimento). Os parênteses vazios são obrigatórios.
$ java Main.java Ana 3 4 0 9
Linha por linha: Ana é o conteúdo; 3 é o tamanho de "Ana"; 4 é o de "maçã" (o ç e o ã contam como um caractere cada); 0 é o da String vazia; e 9 é o de "Ana Souza", porque o espaço também é um caractere.
Um método é uma ação que a String sabe fazer. Você chama com valor.nome(): o valor, um ponto, o nome do método e parênteses. A String tem dezenas de métodos (deixar em maiúsculas, cortar um pedaço, procurar uma letra); eles têm uma aula só para eles: Métodos de String.
length() com parênteses
Em Strings é length(), um método, com parênteses. Nos arrays, que você vai ver mais adiante, é length sem parênteses. Misturar os dois é um erro muito comum; o javac avisa com cannot find symbol.
Brinque com o código: troque os textos, acrescente espaços ou acentos e veja como o length() muda. Experimente também "".length() e " ".length() (uma String com um espaço).
public class Main {
public static void main(String[] args) {
String nome = "Ana";
String cidade = "São Paulo";
System.out.println(nome + " tem " + nome.length() + " letras");
System.out.println(cidade + " tem " + cidade.length() + " caracteres");
}
}
Strings são imutáveis
Imutável quer dizer "que não pode ser mudado". Depois de criada, uma String nunca muda. Os métodos que parecem "mudar" o texto, como toUpperCase() (deixar em maiúsculas), na verdade criam uma String nova e devolvem essa nova. A original continua igual.
É como uma foto impressa: se você quer a foto em preto e branco, a loja faz outra cópia em preto e branco. A foto colorida que você levou continua colorida. Veja a prova:
public class Main {
public static void main(String[] args) {
String nome = "ana";
nome.toUpperCase();
System.out.println(nome);
String grande = nome.toUpperCase();
System.out.println(grande);
System.out.println(nome);
nome = nome.toUpperCase();
System.out.println(nome);
}
}
$ java Main.java ana « a linha 4 criou "ANA", mas ninguém guardou: o texto se perdeu ANA « desta vez a String nova foi guardada em "grande" ana « e "nome" continua o mesmo ANA « agora "nome" passou a apontar para a String nova
| Linha | O que acontece |
|---|---|
nome.toUpperCase(); | Cria a String "ANA" e a devolve, mas o resultado não foi guardado em lugar nenhum. É jogado fora. nome continua "ana". |
String grande = nome.toUpperCase(); | Cria "ANA" e guarda na variável nova grande. |
nome = nome.toUpperCase(); | Cria "ANA" e guarda na própria variável nome. A String "ana" antiga não mudou: a variável é que passou a guardar outra. |
Repare que o Java não reclama da linha 4: o código compila e roda, só não faz o que um iniciante espera. Esse é um engano muito comum. A regra: todo método de String que "transforma" o texto devolve uma String nova; guarde o resultado.
Uma variável String pode receber outro texto a qualquer momento (nome = "Bruno";). O que não muda é o texto em si. Pense na variável como uma etiqueta presa a um colar: dá para tirar a etiqueta e prender em outro colar, mas não dá para trocar as contas do colar.
Por que o Java faz assim? Porque é mais seguro: se você entrega o seu texto para outra parte do programa, tem certeza de que ninguém vai alterá-lo pelas suas costas. E permite uma economia que você vai ver agora, na comparação.
Comparar Strings: == × equals
Esta é a seção mais importante da aula. Em Java existem duas formas de perguntar "estes dois textos são iguais?", e só uma está certa para Strings.
| Forma | O que pergunta | Comparação do dia a dia |
|---|---|---|
a == b | "a e b são o mesmo objeto na memória?" | "Este livro é o mesmo exemplar que aquele?" |
a.equals(b) | "a e b têm o mesmo conteúdo?" | "Estes dois livros têm o mesmo texto?" |
Duas cópias do mesmo livro, compradas em lojas diferentes, têm o mesmo texto (equals dá true), mas são exemplares diferentes (== dá false). Quase sempre, o que você quer saber é se o texto é igual. Veja a prova, com o JDK de verdade:
public class Main {
public static void main(String[] args) {
String a = "Ana";
String b = "Ana";
String c = new String("Ana");
String parte = "An";
String d = parte + "a";
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a == d);
System.out.println(a.equals(b));
System.out.println(a.equals(c));
System.out.println(a.equals(d));
}
}
$ java Main.java true « a == b: os dois literais são o MESMO objeto false « a == c: new String criou um objeto novo false « a == d: o texto montado durante a execução é outro objeto true « a.equals(b) true « a.equals(c): o conteúdo é igual true « a.equals(d): o conteúdo é igual
As quatro variáveis guardam o texto Ana. O equals disse true para todas. O == disse true para uma e false para as outras. Por quê?
- String a = "Ana"; String b = "Ana";
- Quando o mesmo literal aparece no código, o Java guarda uma única cópia num lugar especial da memória, o pool de Strings (pool = "reservatório").
aebapontam para a mesma cópia. Isso só é seguro porque Strings são imutáveis: ninguém consegue estragar a cópia compartilhada. - new String("Ana")
new(novo) manda o Java criar um objeto novo, fora do pool, com o mesmo texto. Mesmo conteúdo, outro exemplar.- parte + "a"
- Um texto montado durante a execução (a partir de uma variável) também é um objeto novo.
== às vezes funciona
O perigo do == é que ele dá true quando os dois textos são literais escritos no código. Você testa, funciona, e acha que está certo. Mas, quando o texto vem do teclado (Scanner), de um arquivo ou de uma conta, ele é um objeto novo, e o == passa a dar false com o mesmo texto. Para comparar o conteúdo de Strings, use sempre equals.
Um caso de verdade: a senha correta é java25, e a senha "digitada" foi montada juntando "java" com o número 25:
public class Main {
public static void main(String[] args) {
String senha = "java25";
int versao = 25;
String digitada = "java" + versao;
System.out.println(digitada);
if (digitada == senha) {
System.out.println("Acesso liberado");
} else {
System.out.println("Senha errada");
}
if (digitada.equals(senha)) {
System.out.println("Acesso liberado");
}
}
}
$ java Main.java java25 « o texto é exatamente "java25" Senha errada « mas o == recusou: são objetos diferentes Acesso liberado « o equals compara o conteúdo e aceita
O if ("se") executa um bloco só quando a condição é verdadeira; você vai estudá-lo em if, else if, else. Aqui ele mostra bem o estrago: com ==, a usuária digitou a senha certa e foi barrada.
Missão: rode o programa e veja a mensagem Senha errada, mesmo com a senha certa. Depois corrija a comparação para usar equals e fazer aparecer Acesso liberado.
public class Main {
public static void main(String[] args) {
String senha = "java25";
int versao = 25;
String digitada = "java" + versao;
if (digitada == senha) {
System.out.println("Acesso liberado");
} else {
System.out.println("Senha errada");
}
}
}
public class Main {
public static void main(String[] args) {
String senha = "java25";
int versao = 25;
String digitada = "java" + versao;
if (digitada.equals(senha)) {
System.out.println("Acesso liberado");
} else {
System.out.println("Senha errada");
}
}
}
Uma curiosidade: o javac junta literais antes
Se você trocar "java" + versao por "java" + 25 (os dois pedaços escritos direto no código), o == dá true. O javac percebe que "java" + 25 é sempre "java25", faz a conta na hora de compilar e usa a cópia do pool. O mesmo vale para "An" + "a". Isso só piora a pegadinha: o resultado do == depende de detalhes que você não controla. Não tente decorar quando ele funciona; use equals.
No JShell dá para ver o mesmo comportamento, uma linha por vez:
$ jshell
| Welcome to JShell -- Version 25.0.4.1
| For an introduction type: /help intro
jshell> String nome = "Ana"
nome ==> "Ana"
jshell> nome.length()
$2 ==> 3
jshell> nome == new String("Ana")
$3 ==> false
jshell> /exit
| Goodbye
Brinque no JShell: crie String a = "Ana" e compare com a == "Ana", a == new String("Ana"), a.equals(new String("Ana")). Qual delas é confiável?
Ignorar maiúsculas: equalsIgnoreCase
O equals compara caractere por caractere, e S maiúsculo é um caractere diferente de s minúsculo. Quando a diferença entre maiúsculas e minúsculas não importa (uma resposta "sim", o nome de uma cidade), use equalsIgnoreCase (ignore case = "ignorar a caixa", ou seja, maiúsculas/minúsculas):
public class Main {
public static void main(String[] args) {
String resposta = "SIM";
System.out.println(resposta.equals("sim"));
System.out.println(resposta.equalsIgnoreCase("sim"));
System.out.println("Maçã".equalsIgnoreCase("MAÇÃ"));
}
}
$ java Main.java false « equals: "SIM" e "sim" têm caracteres diferentes true « equalsIgnoreCase: só a caixa muda, então é igual true « funciona com acentos também
Ele não ignora outras diferenças: espaços sobrando e acentos diferentes ("maca" × "maçã") continuam contando. Para tirar espaços das pontas antes de comparar, existe o strip(), visto em Métodos de String.
Ordem alfabética: compareTo
O equals responde "igual ou não". Para saber qual vem antes no dicionário (para ordenar uma lista de nomes, por exemplo), use compareTo (compare to = "comparar com"). Ele devolve um número:
a.compareTo(b) devolve | Quer dizer |
|---|---|
| Um número negativo | a vem antes de b |
0 | São iguais |
| Um número positivo | a vem depois de b |
public class Main {
public static void main(String[] args) {
System.out.println("Ana".compareTo("Bruno"));
System.out.println("Bruno".compareTo("Ana"));
System.out.println("Ana".compareTo("Ana"));
System.out.println("Ana".compareTo("Ana Souza"));
System.out.println("ana".compareTo("Ana"));
System.out.println("Zeca".compareTo("ana"));
System.out.println("Zeca".compareToIgnoreCase("ana"));
}
}
$ java Main.java -1 « "Ana" vem antes de "Bruno" 1 « "Bruno" vem depois de "Ana" 0 « iguais -6 « "Ana" é o começo de "Ana Souza": o mais curto vem antes 32 « "ana" vem DEPOIS de "Ana": minúsculas vêm depois das maiúsculas -7 « "Zeca" vem ANTES de "ana"! 25 « ignorando a caixa, "Zeca" volta a vir depois de "ana"
De onde vêm esses números? O compareTo anda pelas duas Strings até achar a primeira letra diferente e devolve a diferença entre os códigos dessas letras (lembre que, em Java, cada char é um número; veja char e boolean). 'A' é 65 e 'B' é 66: 65 − 66 = -1. 'a' é 97: 97 − 65 = 32. Se uma String é o começo da outra, devolve a diferença de tamanho: 3 − 9 = -6.
Não use o valor exato, só o sinal (negativo, zero, positivo). O que importa é "antes, igual ou depois".
Como a ordem segue os códigos dos caracteres, todas as maiúsculas (65 a 90) vêm antes de todas as minúsculas (97 a 122). Por isso "Zeca" fica antes de "ana". Para uma ordem "de dicionário", use compareToIgnoreCase. Acentos também ficam fora do lugar ("Ágata" vem depois de "Zeca"); a ordenação correta para português usa o Collator, assunto de Comparable e Comparator.
String vazia × null
Duas coisas parecem "nada", mas são bem diferentes:
String vazia "" | null | |
|---|---|---|
| O que é | Uma String de verdade, com zero caracteres | Nenhuma String: a variável não aponta para objeto nenhum |
| Comparação | Um copo vazio | Nenhum copo |
length() | 0 | Erro: NullPointerException |
| Ao imprimir | Nada aparece | Aparece a palavra null |
Você pode perguntar o tamanho de um copo vazio (zero). Mas não dá para perguntar o tamanho de um copo que não existe. null (em inglês, "nulo") é uma palavra do Java que quer dizer "esta variável não aponta para nada". Veja:
public class Main {
public static void main(String[] args) {
String vazia = "";
String nada = null;
System.out.println("[" + vazia + "]");
System.out.println(vazia.length());
System.out.println(vazia.isEmpty());
System.out.println("[" + nada + "]");
System.out.println(nada.length());
}
}
$ javac Main.java $ java Main [] 0 true [null] Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local2>" is null at Main.main(Main.java:9) $ echo $? 1
Os colchetes mostram onde o texto começa e termina. A String vazia aparece como [], tem tamanho 0 e o isEmpty() ("está vazia?") responde true. Já o null, juntado com texto, vira a palavra null. E, na linha 9, pedir o length() de nada derrubou o programa.
Lendo a NullPointerException
Uma exceção é o jeito do Java avisar que algo deu errado durante a execução (o código compilou, mas falhou ao rodar). Você vai estudá-las em O que são Exceções; por ora, leia a mensagem por partes:
- Exception in thread "main"
- "Exceção na linha de execução principal": o seu programa.
- java.lang.NullPointerException
- O tipo do erro: "exceção de ponteiro nulo". Você tentou usar algo que é
null. - Cannot invoke "String.length()"
- "Não foi possível chamar o método
length()de String"… - because "<local2>" is null
- …"porque a variável local número 2 é nula". Contando a partir de zero, a variável 0 é o
args, a 1 évaziae a 2 énada. - at Main.main(Main.java:9)
- Onde aconteceu: no método
mainda classeMain, arquivoMain.java, linha 9.
Essa mensagem detalhada ("because … is null") existe desde o Java 14 (as versões antigas diziam só java.lang.NullPointerException, sem explicação). Por que <local2> e não nada? Porque o javac, por padrão, não guarda os nomes das variáveis locais dentro do .class. Compilando com a opção -g (de debug, "depuração"), os nomes ficam guardados e a mensagem fica ainda melhor:
$ javac -g Main.java $ java Main [] 0 true [null] Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "nada" is null at Main.main(Main.java:9)
As IDEs (veja Editores e IDEs) já compilam com -g, por isso nelas você vê o nome da variável.
Comparar com segurança quando pode haver null
Chamar equals numa variável null também quebra. O truque é colocar o literal na frente: o literal nunca é null.
public class Main {
public static void main(String[] args) {
String nome = null;
System.out.println("Ana".equals(nome));
System.out.println(nome.equals("Ana"));
}
}
$ java Main.java false Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "<local1>" is null at Main.main(Main.java:5)
"Ana".equals(nome) responde false com calma ("Ana" não é igual a nada). nome.equals("Ana") tenta chamar um método em null e derruba o programa. Para saber se uma variável é null, aí sim se usa ==: nome == null, porque a pergunta é "aponta para algum objeto?", e não sobre o conteúdo.
Missão: o programa quebra com NullPointerException. Corrija a linha 4 para comparar de forma segura (o literal na frente) e acrescente uma linha que mostre se apelido é null (apelido == null). A saída deve ser false e depois true.
public class Main {
public static void main(String[] args) {
String apelido = null;
System.out.println(apelido.equals("Ana"));
}
}
public class Main {
public static void main(String[] args) {
String apelido = null;
System.out.println("Ana".equals(apelido));
System.out.println(apelido == null);
}
}
Qual comparação usar?
| Você quer saber… | Use | Exemplo |
|---|---|---|
| Se dois textos são iguais | equals | senha.equals(digitada) |
| Se são iguais, sem ligar para maiúsculas | equalsIgnoreCase | resposta.equalsIgnoreCase("sim") |
| Qual vem antes em ordem alfabética | compareTo / compareToIgnoreCase | a.compareTo(b) < 0 |
Se a variável pode ser null e vai comparar com um texto fixo | Literal na frente | "Ana".equals(nome) |
Se a variável é null | == | nome == null |
| Se a String está vazia | isEmpty() | nome.isEmpty() |
| Se são o mesmo objeto (raro) | == | quase nunca é o que você quer |
Quando dá errado
error: cannot find symbol … symbol: class string
Este código tem um erro de propósito.
public class Main {
public static void main(String[] args) {
string nome = "Ana";
System.out.println(nome);
}
}
$ javac Main.java
Main.java:3: error: cannot find symbol
string nome = "Ana";
^
symbol: class string
location: class Main
1 error
O que aconteceu: Java diferencia maiúsculas de minúsculas. string, com s minúsculo, não existe; o javac procurou uma classe com esse nome (symbol: class string, "símbolo: classe string") e não achou.
Como resolver: escreva String, com S maiúsculo.
error: unclosed character literal
Este código tem um erro de propósito.
public class Main {
public static void main(String[] args) {
String nome = 'Ana';
System.out.println(nome);
}
}
$ javac Main.java
Main.java:3: error: unclosed character literal
String nome = 'Ana';
^
Main.java:3: error: unclosed character literal
String nome = 'Ana';
^
Main.java:3: error: not a statement
String nome = 'Ana';
^
3 errors
O que aconteceu: aspas simples são para um char. O javac leu 'A, esperava a aspa de fechamento logo depois de uma letra e não achou: "literal de caractere não fechado". Um único engano gerou três erros em cascata; conserte sempre o primeiro.
Como resolver: texto vai entre aspas duplas: "Ana".
if diz que são diferentes
O que aconteceu: a comparação foi feita com ==, que compara objetos e não o conteúdo. Não aparece mensagem de erro nenhuma: o programa só toma a decisão errada (veja o exemplo da senha, acima).
Como resolver: troque a == b por a.equals(b) e a != b por !a.equals(b).
NullPointerException: Cannot invoke "String…" because … is null
O que aconteceu: você chamou um método (length(), equals, toUpperCase()…) numa variável que vale null. A linha está no final da mensagem (Main.java:9).
Como resolver: descubra por que a variável ficou null e dê um valor a ela; se null for possível, teste antes com if (nome != null) ou ponha o literal na frente: "Ana".equals(nome).
toUpperCase()
O que aconteceu: Strings são imutáveis. nome.toUpperCase(); sozinho cria uma String nova e a joga fora. Não há erro nem aviso.
Como resolver: guarde o resultado: nome = nome.toUpperCase();.
Referência
| Escrita | O que faz | Exemplo → resultado |
|---|---|---|
String s = "texto"; | Cria uma String com um literal | String nome = "Ana"; |
"" | String vazia | "".length() → 0 |
s.length() | Quantidade de caracteres | "Ana Souza".length() → 9 |
s.isEmpty() | Está vazia? | "".isEmpty() → true |
a.equals(b) | Mesmo conteúdo? | "Ana".equals("ana") → false |
a.equalsIgnoreCase(b) | Mesmo conteúdo, ignorando a caixa? | "Ana".equalsIgnoreCase("ANA") → true |
a.compareTo(b) | Ordem: negativo, 0 ou positivo | "Ana".compareTo("Bruno") → -1 |
a.compareToIgnoreCase(b) | Ordem, ignorando a caixa | "Zeca".compareToIgnoreCase("ana") → 25 |
a == b | Mesmo objeto? (não use para conteúdo) | "Ana" == new String("Ana") → false |
s == null | A variável aponta para nada? | null == null → true |
A lista completa de métodos está em Referência: Métodos de String.
Exercícios
Missão final: num jogo de perguntas, a cidade certa é Recife e a jogadora respondeu RECIFE. Complete o programa para mostrar Acertou! quando a resposta for igual à cidade ignorando maiúsculas, e depois a linha A palavra tem 6 letras usando length() (não escreva o 6 à mão).
public class Main {
public static void main(String[] args) {
String cidade = "Recife";
String resposta = "RECIFE";
// escreva aqui
}
}
public class Main {
public static void main(String[] args) {
String cidade = "Recife";
String resposta = "RECIFE";
if (resposta.equalsIgnoreCase(cidade)) {
System.out.println("Acertou!");
}
System.out.println("A palavra tem " + cidade.length() + " letras");
}
}
1. Tamanho
Complete para mostrar quantos caracteres tem a variável nome:
2. Comparar o conteúdo
Complete para comparar o conteúdo de a e b:
3. Sem quebrar com null
A variável cor pode ser null. Complete para comparar com "azul" sem risco de NullPointerException:
4. O que System.out.println("Ana" == new String("Ana")); mostra?
5. Depois de String s = "oi"; s.toUpperCase(); System.out.println(s);, o que aparece?
6. "banana".compareTo("abacaxi") devolve um número…
7. Qual é a diferença entre "" e null?
Resumo
- Uma
Stringé uma sequência de caracteres, escrita entre aspas duplas.Stringtem S maiúsculo. length()devolve a quantidade de caracteres (espaços contam);""tem tamanho 0.- Strings são imutáveis: métodos como
toUpperCase()devolvem uma String nova. Guarde o resultado. ==compara se é o mesmo objeto;equalscompara o conteúdo. Para texto, useequals, mesmo quando o=="parece funcionar".equalsIgnoreCaseignora maiúsculas;compareTodiz a ordem (negativo, 0, positivo).nullé "nenhuma String". Chamar método emnulldáNullPointerException; compare com"literal".equals(variavel)e teste comvariavel == null.