Classe Scanner do Java
Analise primitivos e strings de entrada de texto em Java com a classe Scanner — nextInt, nextLine, useDelimiter.
BufferedReader.readLine() do capítulo sobre streams com buffer é a ferramenta certa quando a entrada é orientada a linhas e você quer cada linha como uma String. Scanner é a ferramenta certa quando a entrada é um fluxo de tokens — inteiros, doubles, palavras separadas por espaço em branco, ou campos separados por uma expressão regular que você escolhe. É o parser que o JDK fornece de forma integrada.
Scanner também é a classe que a maioria dos tutoriais introdutórios de Java usa para leitura do teclado. new Scanner(System.in) e você tem um programa interativo funcionando em duas linhas. Essa conveniência vem com uma armadilha bem conhecida — a armadilha do nextInt/nextLine — que é o assunto central deste capítulo.
O que o Scanner analisa
Os métodos de leitura de tokens, combinados com seus predicados hasNext:
boolean hasNext(); String next(); // a whitespace-delimited token
boolean hasNextInt(); int nextInt(); // a token parsed as int
boolean hasNextLong(); long nextLong();
boolean hasNextDouble(); double nextDouble();
boolean hasNextBoolean(); boolean nextBoolean();
boolean hasNextLine(); String nextLine(); // the rest of the current lineO contrato é idêntico em todos os métodos tipados: hasNextX() verifica se o próximo token pode ser analisado como X sem consumi-lo; nextX() o consome. Uma incompatibilidade (nextInt() quando o token é "hello") lança InputMismatchException. Fim do fluxo lança NoSuchElementException.
Um token é, por padrão, uma sequência máxima de caracteres não-espaço em branco. O padrão delimitador é o que Pattern.UNICODE_CHARACTER_CLASS considera espaço em branco — espaços, tabulações, quebras de linha e similares. Você pode alterá-lo com useDelimiter(...).
Construtores
new Scanner(InputStream source); // typical: System.in
new Scanner(InputStream source, Charset charset); // explicit charset (preferred for files)
new Scanner(Path source, Charset charset); // open a file by path
new Scanner(String source); // parse a literal String — great for tests
new Scanner(Readable source); // wrap any Readable (Reader, CharBuffer, ...)A mesma regra vale para o restante de java.io/java.nio: sempre passe um charset explícito ao ler bytes. Os construtores sem charset assumem como padrão a codificação da plataforma.
try (Scanner s = new Scanner(path, StandardCharsets.UTF_8)) {
while (s.hasNextInt()) {
process(s.nextInt());
}
}Fechar o Scanner fecha o stream subjacente. Não feche um Scanner que envolve System.in — fechá-lo fecha o System.in, e qualquer leitura subsequente na mesma JVM falhará.
A armadilha do nextInt / nextLine
A pergunta mais feita sobre Java no Stack Overflow.
Scanner s = new Scanner(System.in);
System.out.print("age: "); int age = s.nextInt();
System.out.print("name: "); String name = s.nextLine();Digite 30, pressione Enter, depois Alice, pressione Enter. Esperado: age=30, name=Alice. Real: age=30, name="".
O motivo: nextInt() lê os dígitos 30 e para. Ele deixa o \n final no buffer de entrada. O próximo nextLine() lê tudo até a próxima quebra de linha — que está bem ali, imediatamente — e retorna a string vazia antes que o usuário tenha a chance de digitar qualquer coisa.
A correção é uma das seguintes:
int age = s.nextInt(); s.nextLine(); // explicit "skip to end of line"
String name = s.nextLine();ou, de forma mais robusta, analisar a linha inteira você mesmo:
int age = Integer.parseInt(s.nextLine().trim()); // always reads the full line
String name = s.nextLine();O segundo padrão é o que eu uso em código real. Misturar métodos de leitura de tokens (nextInt, nextDouble, next) com leitura de linhas (nextLine) é uma receita para bugs de deslocamento; escolha um e fique com ele. Ou analise linha a linha com nextLine, ou analise token a token com next* e chame nextLine apenas para o propósito explícito de "pular o restante desta linha".
hasNext é a condição do loop
A estrutura de todo loop com Scanner:
while (s.hasNextInt()) { // predicate, no exception
int n = s.nextInt(); // consume
process(n);
}hasNextInt() retorna false no fim do fluxo e quando o próximo token não é um inteiro — então o loop termina corretamente no EOF e em um token não numérico (o que frequentemente é o comportamento desejado, por exemplo quando o rodapé final é não numérico). Se você quiser falhar ruidosamente em vez disso, use hasNext() e deixe nextInt() lançar InputMismatchException na incompatibilidade:
while (s.hasNext()) {
int n = s.nextInt(); // throws if the token isn't an int
process(n);
}Mesma verificação de fim de fluxo, comportamento diferente em tokens inválidos.
Delimitadores personalizados
O delimitador padrão é espaço em branco. Para entradas semelhantes a CSV, você pode alterá-lo:
s.useDelimiter(",|\\R"); // comma or any line break\\R é a expressão regular Java para "qualquer sequência de nova linha" (\n, \r\n, \r, além dos separadores de linha Unicode). O padrão combinado divide em vírgulas e quebras de linha, portanto 1,2,3\n4,5,6 produz seis tokens.
Dito isso: para CSV real, use uma biblioteca CSV. Scanner não lida com campos entre aspas, vírgulas escapadas ou quebras de linha incorporadas. Para casos simples — uma lista de números, uma configuração delimitada por espaços — é perfeito.
A armadilha do locale
nextDouble() analisa com o separador decimal do locale padrão. Em uma JVM alemã, 3.14 falha (3,14 é a forma alemã). Em uma JVM dos EUA, 3,14 falha.
Para entradas legíveis por máquina, force o locale do parser:
s.useLocale(Locale.ROOT); // dot as decimal separator, no grouping
double x = s.nextDouble(); // now parses "3.14"Locale.ROOT é o locale "neutro" — a convenção para analisar arquivos de dados que não são destinados a humanos. Esquecer isso é o motivo mais comum de um leitor de CSV funcionar em desenvolvimento e falhar no CI: a máquina do desenvolvedor e a máquina de CI têm locales padrão diferentes.
Scanner vs BufferedReader
Scanner | BufferedReader | |
|---|---|---|
| Lê | tokens (tipados) | linhas (String) |
| Velocidade | lento (regex em cada token) | rápido |
| Conveniência | alta (nextInt etc.) | baixa (você analisa manualmente) |
| Ideal para | entradas pequenas, prompts interativos, testes | arquivos grandes, processamento de logs, loops intensivos |
Regra geral: se a entrada vem de um humano e você quer tipos, use Scanner. Se a entrada é um arquivo e você quer linhas, use BufferedReader. Para entradas do tamanho de programação competitiva (milhões de tokens), BufferedReader + StringTokenizer é uma ordem de grandeza mais rápido que Scanner.
Um exemplo prático: analisando um pequeno formato de texto
O programa abaixo analisa um pequeno arquivo de texto delimitado por espaços com três registros por linha — id name score — usando Scanner. Ele demonstra o loop hasNextInt(), a correção do locale para nextDouble(), a armadilha nextInt/nextLine e sua resolução, e finalmente useDelimiter para uma alternativa semelhante a CSV.
O que tirar da execução:
- A primeira leitura analisou três registros de três tipos diferentes em três linhas de código. A API baseada em tokens é genuinamente conveniente quando a entrada tem a forma de tokens — sem regex, sem
String.split, semInteger.parseIntmanual. Esse é o caso de uso doScanner. useLocale(Locale.ROOT)foi a linha que tornou97.5analisável. Sem ela, o parser usa o locale padrão da JVM; em uma máquina onde esse é o alemão,97.5lançariaInputMismatchException. Para entradas legíveis por máquina, sempre fixe o locale.- A divisão com bug/correto para a armadilha imprimiu
name=''e depoisname='Alice'. O bug era real —nextInt()deixou o\nno buffer — e a correção orientada a linhas (Integer.parseInt(s.nextLine().trim())) foi a forma mais limpa de evitar misturar os dois estilos de leitura. Escolha um estilo e fique com ele. - O bloco
useDelimiter("," + "|" + "\\R")analisou linhas separadas por vírgulas com o mesmo código de leitura de tokens, apenas com um delimitador diferente. A mesma ressalva do texto se aplica: isso funciona para CSV limpo e quebra em CSV do mundo real com campos entre aspas. Use uma biblioteca CSV real para qualquer coisa que tenha vindo do Excel. - O rodapé de entrada mista (
-- end --) mostrou por quehasNextInt()é a condição de loop correta: ele retornoufalseno primeiro token não inteiro e o loop encerrou corretamente. Mudar parahasNext()teria permitido que o loop continuasse aténextInt()lançar — ambas as formas são úteis, dependendo de um token não inteiro significar "acabamos" ou "a entrada está quebrada."
O que vem a seguir
PrintWriter (o capítulo anterior) e Scanner são as classes de entrada/saída orientadas a caracteres que a maioria do código Java introdutório usa. O próximo capítulo, Java PrintStream, aborda o irmão orientado a bytes do PrintWriter — e explica por que System.out e System.err são PrintStreams em vez de PrintWriters.