W3docs

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 line

O 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

ScannerBufferedReader
tokens (tipados)linhas (String)
Velocidadelento (regex em cada token)rápido
Conveniênciaalta (nextInt etc.)baixa (você analisa manualmente)
Ideal paraentradas pequenas, prompts interativos, testesarquivos 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.

java— editable, runs on the server

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, sem Integer.parseInt manual. Esse é o caso de uso do Scanner.
  • useLocale(Locale.ROOT) foi a linha que tornou 97.5 analisável. Sem ela, o parser usa o locale padrão da JVM; em uma máquina onde esse é o alemão, 97.5 lançaria InputMismatchException. Para entradas legíveis por máquina, sempre fixe o locale.
  • A divisão com bug/correto para a armadilha imprimiu name='' e depois name='Alice'. O bug era real — nextInt() deixou o \n no 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 que hasNextInt() é a condição de loop correta: ele retornou false no primeiro token não inteiro e o loop encerrou corretamente. Mudar para hasNext() 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.

Prática

Prática
Em uma JVM com alemão como locale padrão, você chama `scanner.nextDouble()` para analisar '3.14' de um arquivo de configuração. O que acontece e qual é a solução?
Em uma JVM com alemão como locale padrão, você chama `scanner.nextDouble()` para analisar '3.14' de um arquivo de configuração. O que acontece e qual é a solução?
Was this page helpful?