O scanner está pulando nextLine () depois de usar next () ou nextFoo ()?

684

Estou usando os Scannermétodos nextInt()e nextLine()para ler entradas.

Se parece com isso:

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
System.out.println("Enter 1st string"); 
String string1 = input.nextLine(); // Read 1st string (this is skipped)
System.out.println("Enter 2nd string");
String string2 = input.nextLine(); // Read 2nd string (this appears right after reading numerical value)

O problema é que, depois de inserir o valor numérico, o primeiro input.nextLine()é ignorado e o segundo input.nextLine()é executado, para que minha saída fique assim:

Enter numerical value
3   // This is my input
Enter 1st string    // The program is supposed to stop here and wait for my input, but is skipped
Enter 2nd string    // ...and this line is executed and waits for my input

Testei meu aplicativo e parece que o problema está no uso input.nextInt(). Se eu excluí-lo, ambos string1 = input.nextLine()e string2 = input.nextLine()são executados como eu quero que sejam.

blekione
fonte
5
Ou você pode ser como eu e usar o BufferedReader :) Não me importo se é da velha escola, sempre funcionou e sempre funcionará para mim. Além disso, o conhecimento do BufferedReader tem aplicação em outro lugar. Eu simplesmente não gosto de Scanner.
Cruncher

Respostas:

831

Isso Scanner.nextIntocorre porque o método não lê o caractere de nova linha em sua entrada criada pressionando "Enter" e, portanto, a chamada Scanner.nextLineretorna depois de ler essa nova linha .

Você encontrará o comportamento semelhante ao usar o método Scanner.nextLineposterior Scanner.next()ou qualquer outro Scanner.nextFoométodo (exceto nextLineele próprio).

Gambiarra:

  • Faça uma Scanner.nextLinechamada após cada uma Scanner.nextIntou Scanner.nextFooconsuma o restante dessa linha, incluindo a nova linha

    int option = input.nextInt();
    input.nextLine();  // Consume newline left-over
    String str1 = input.nextLine();
    
  • Ou, melhor ainda, leia a entrada Scanner.nextLinee converta-a no formato adequado que você precisa. Por exemplo, você pode converter para um número inteiro usando o Integer.parseInt(String)método

    int option = 0;
    try {
        option = Integer.parseInt(input.nextLine());
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
    String str1 = input.nextLine();
    
Rohit Jain
fonte
4
@blekione. Você precisa usar try-catch, porque Integer.parseIntlança NumberFormatExceptionquando um argumento inválido é passado para ele. Você aprenderá sobre a exceção mais tarde. Para EG: - Integer.parseInt("abc"). Você não quer que "abc" seja convertido para int, certo?
Rohit Jain
3
@blekione. Portanto, no caso acima, seu código será interrompido nesse ponto e você não poderá continuar a execução. Com o tratamento de exceções, você pode lidar com esse tipo de condições.
Rohit Jain
4
Até que ponto o último é melhor? O AFAIK Scanner # nextInt () é muito mais flexível para encontrar ints corretos, permitindo vírgulas de grupo e prefixos e sufixos de localidade. O número inteiro # parseInt () permite apenas dígitos e ponto decimal, além de um sinal opcional.
Mordechai
5
Pessoalmente, prefiro um Scanner#hasNextFoocheque antes do que um try-catch, mas isso também funciona.
MildlyMilquetoast
1
O uso de ParseDouble tem um novo problema, que nextDouble usa a configuração regional de decimal (. Ou,) mas o Parsedouble sempre recebe decimal dos EUA (.).
olmerg
209

O problema está no método input.nextInt () - ele somente lê o valor int. Então, quando você continuar lendo com input.nextLine (), receberá a tecla Enter "\ n". Então, para pular isso, você deve adicionar o input.nextLine () . Espero que isso esteja claro agora.

Tente assim:

System.out.print("Insert a number: ");
int number = input.nextInt();
input.nextLine(); // This line you have to add (It consumes the \n character)
System.out.print("Text1: ");
String text1 = input.nextLine();
System.out.print("Text2: ");
String text2 = input.nextLine();
Prine
fonte
umm parece uma solução, tornando não usou a linha ignorado nextLine, mas ainda precisa de uma explicação para este comportamento
Eng.Fouad
2
FYI: mesclado de stackoverflow.com/questions/7056749/…
Shog9
Pergunta: perguntado 27 de outubro de 2012 às 16:37. respondeu Aug 14 '11 às 12:24. Como isso é possível?
Apoorv Patne
79

É porque quando você digita um número e pressiona Enter, input.nextInt()consome apenas o número, não o "fim de linha". Quando input.nextLine()executado, consome o "fim de linha" ainda no buffer desde a primeira entrada.

Em vez disso, use input.nextLine()imediatamente apósinput.nextInt()

Boêmio
fonte
7
@ Victor não é bug. está funcionando como especificado. você pode argumentar, porém, que deve haver uma maneira fácil de dizer à API que também consuma qualquer espaço em branco após a entrada de destino.
Bohemian
Eu vejo. obrigado @Bohemian, é exatamente isso que estou discutindo. Eu acho que isso deve ser alterado, talvez você possa me indicar onde sugerir esse "problema" no próximo JCP.
213 Victor Victor
@victor Você pode solicitar (e descobrir como contribuir com código) para um recurso na página Relatar um bug ou Solicitar um recurso .
Bohemian
48

Parece haver muitas perguntas sobre esse problema java.util.Scanner. Eu acho que uma solução mais legível / idiomática seria ligar scanner.skip("[\r\n]+")para descartar qualquer caractere de nova linha depois de ligar nextInt().

EDIT: como @PatrickParker observou abaixo, isso causará um loop infinito se o usuário inserir qualquer espaço em branco após o número. Consulte a resposta para obter um padrão melhor para usar com o pular: https://stackoverflow.com/a/42471816/143585

Denis Tulskiy
fonte
Eu sei o que podemos fazer para remover os dados em buffer, mas neste caso, por favor me ajudar com isso: stackoverflow.com/questions/33585314/having-issues-with-scanner
Khuong
1
Para sua informação, isso causará um loop infinito se o usuário digitar qualquer tabulação ou espaço após a entrada numérica. Veja minha resposta aqui para um salto melhor: stackoverflow.com/a/42471816/7098259
Patrick Parker
@PatrickParker: realmente causa um loop infinito! Obrigado, editou a resposta para criar um link para o seu.
Denis Tulskiy
33

Faz isso porque input.nextInt();não captura a nova linha. você poderia fazer como os outros propostos adicionando um input.nextLine();por baixo.
Como alternativa, você pode fazê-lo no estilo C # e analisar um nextLine em um número inteiro da seguinte maneira:

int number = Integer.parseInt(input.nextLine()); 

Fazer isso funciona da mesma maneira e economiza uma linha de código.

Café elétrico
fonte
1
você tem que usar tentar pegar aqui. O que acontecerá se a entrada não for um número. NumberFormatException precisa ser tratado aqui.
Arundev 25/03/19
Isso pressupõe que haja apenas um token int em cada linha para a qual você tenta.
Cellepo 18/05/19
25

TL; DR Use scanner.skip("\\R")antes de cada scanner.newLine()chamada, que é executada após:

  • scanner.next()
  • scanner.next*TYPE*() método.

Coisas que você precisa saber:

  • O texto que representa poucas linhas também contém caracteres imprimíveis entre as linhas (nós os chamamos de separadores de linha), como

    • retorno de carro (CR - em literais String representados como "\r")
    • avanço de linha (LF - em literais String representados como "\n")
  • quando você está lendo dados do console, ele permite que o usuário digite sua resposta e, quando terminar, ele precisa de alguma forma confirmar esse fato. Para isso, o usuário deve pressionar a tecla "Enter" / "Return" no teclado.

    O importante é que essa chave, além de garantir a colocação dos dados do usuário na entrada padrão (representada pela System.inqual é lida porScanner ), também envie separadores de linha dependentes do SO (como no Windows \r\n) depois dela.

    Portanto, quando você pede ao usuário um valor como age, e o usuário digita 42 e pressiona enter, a entrada padrão conterá"42\r\n" .

Problema

Scanner#nextInt(e outros métodos) não permite que o Scanner consuma esses separadores de linha. Ele as lerá (de que outra forma o Scanner saberia que não há mais dígitos do usuário que representam valor do que espaços em branco?) Que os removerão da entrada padrão, mas também armazenarão em cache esses separadores de linha internamenteScanner#nextTypeSystem.inage . O que precisamos lembrar é que todos os métodos do Scanner estão sempre digitalizando a partir do texto em cache.

Agora, Scanner#nextLine()basta coletar e retornar todos os caracteres até encontrar os separadores de linha (ou final do fluxo). Porém, como os separadores de linha após a leitura do número no console são encontrados imediatamente no cache do Scanner, ele retorna String vazia, o que significa que o Scanner não conseguiu encontrar nenhum caractere antes desses separadores de linha (ou no final do fluxo).
BTW nextLinetambém consome esses separadores de linha.

Solução

Portanto, quando você deseja solicitar o número e a linha inteira, evitando a sequência vazia como resultado de nextLine,

  • separador de linha de consumo deixado por nextInt cache Scanners
    • chamando nextLine ,
    • ou o modo mais legível da IMO seria ligando skip("\\R")ou skip("\r\n|\r|\n")permitindo que o Scanner pule parte correspondente ao separador de linhas (mais informações sobre \R: https://stackoverflow.com/a/31060125 )
  • não use nextInt(nem next, nem nenhum nextTYPEmétodo). Em vez disso, leia os dados inteiros linha por linha usando nextLinee analisando números de cada linha (assumindo que uma linha contenha apenas um número) para o tipo apropriado, como intvia Integer.parseInt.

BTW : os métodos podem ignorar delimitadores (por padrão, todos os espaços em branco, como guias, separadores de linha), incluindo os armazenados em cache pelo scanner, até encontrar o próximo valor não-delimitador (token). Graças a isso para entrada como códigoScanner#nextType"42\r\n\r\n321\r\n\r\n\r\nfoobar"

int num1 = sc.nextInt();
int num2 = sc.nextInt();
String name = sc.next();

será capaz de atribuir corretamente num1=42 num2=321 name=foobar.

Pshemo
fonte
14

Em vez de input.nextLine()usar input.next(), isso deve resolver o problema.

Código modificado:

public static Scanner input = new Scanner(System.in);

public static void main(String[] args)
{
    System.out.print("Insert a number: ");
    int number = input.nextInt();
    System.out.print("Text1: ");
    String text1 = input.next();
    System.out.print("Text2: ");
    String text2 = input.next();
}
Castaldi
fonte
13

Se você quiser ler as strings e ints, uma solução é usar dois scanners:

Scanner stringScanner = new Scanner(System.in);
Scanner intScanner = new Scanner(System.in);

intScanner.nextInt();
String s = stringScanner.nextLine(); // unaffected by previous nextInt()
System.out.println(s);

intScanner.close();
stringScanner.close();
André Valenti
fonte
1
Era isso que eu procurava há algum tempo. Jogada inteligente!
Liam Wilson
1
@BdLearner scanner.nextLine()deve funcionar.
André Valenti
11

Se você deseja digitalizar rapidamente a entrada sem se confundir com o método nextLine () da classe Scanner, use o Custom Input Scanner.

Código:

class ScanReader {
/**
* @author Nikunj Khokhar
*/
    private byte[] buf = new byte[4 * 1024];
    private int index;
    private BufferedInputStream in;
    private int total;

    public ScanReader(InputStream inputStream) {
        in = new BufferedInputStream(inputStream);
    }

    private int scan() throws IOException {
        if (index >= total) {
            index = 0;
            total = in.read(buf);
            if (total <= 0) return -1;
        }
        return buf[index++];
    }
    public char scanChar(){
        int c=scan();
        while (isWhiteSpace(c))c=scan();
        return (char)c;
    }


    public int scanInt() throws IOException {
        int integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public String scanString() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        StringBuilder res = new StringBuilder();
        do {
            res.appendCodePoint(c);
            c = scan();
        } while (!isWhiteSpace(c));
        return res.toString();
    }

    private boolean isWhiteSpace(int n) {
        if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
        else return false;
    }

    public long scanLong() throws IOException {
        long integer = 0;
        int n = scan();
        while (isWhiteSpace(n)) n = scan();
        int neg = 1;
        if (n == '-') {
            neg = -1;
            n = scan();
        }
        while (!isWhiteSpace(n)) {
            if (n >= '0' && n <= '9') {
                integer *= 10;
                integer += n - '0';
                n = scan();
            }
        }
        return neg * integer;
    }

    public void scanLong(long[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanLong();
    }

    public void scanInt(int[] A) throws IOException {
        for (int i = 0; i < A.length; i++) A[i] = scanInt();
    }

    public double scanDouble() throws IOException {
        int c = scan();
        while (isWhiteSpace(c)) c = scan();
        int sgn = 1;
        if (c == '-') {
            sgn = -1;
            c = scan();
        }
        double res = 0;
        while (!isWhiteSpace(c) && c != '.') {
            if (c == 'e' || c == 'E') {
                return res * Math.pow(10, scanInt());
            }
            res *= 10;
            res += c - '0';
            c = scan();
        }
        if (c == '.') {
            c = scan();
            double m = 1;
            while (!isWhiteSpace(c)) {
                if (c == 'e' || c == 'E') {
                    return res * Math.pow(10, scanInt());
                }
                m /= 10;
                res += (c - '0') * m;
                c = scan();
            }
        }
        return res * sgn;
    }

}

Vantagens:

  • Digitaliza a entrada mais rapidamente que o BufferReader
  • Reduz a complexidade do tempo
  • Libera o buffer para todas as próximas entradas

Métodos :

  • scanChar () - verifica um caractere
  • scanInt () - valor inteiro da verificação
  • scanLong () - valor longo da verificação
  • scanString () - valor da string de varredura
  • scanDouble () - scan Double value
  • scanInt (int [] array) - verifica a matriz completa (número inteiro)
  • scanLong (matriz longa []) - verifica a matriz completa (longa)

Uso:

  1. Copie o código fornecido abaixo do seu código java.
  2. Inicializar objeto para determinada classe

ScanReader sc = new ScanReader(System.in); 3. Importar Classes necessárias:

import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; 4. Lance IOException do seu método principal para lidar com a exceção 5. Use métodos fornecidos. 6. Aproveite

Exemplo:

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
class Main{
    public static void main(String... as) throws IOException{
        ScanReader sc = new ScanReader(System.in);
        int a=sc.scanInt();
        System.out.println(a);
    }
}
class ScanReader....
NIKUNJ KHOKHAR
fonte
10

Para evitar o problema, use nextLine();imediatamente depois nextInt();, pois ajuda a limpar o buffer. Quando você pressiona ENTERo botão nextInt();não captura a nova linha e, portanto, ignora o Scannercódigo posteriormente.

Scanner scanner =  new Scanner(System.in);
int option = scanner.nextInt();
scanner.nextLine(); //clearing the buffer
Urvashi Gupta
fonte
9

sc.nextLine()é melhor em comparação com a análise da entrada. Porque o desempenho será bom.

shankar upadhyay
fonte
5

Eu acho que estou muito atrasado para a festa ..

Como declarado anteriormente, chamar input.nextLine()depois de obter seu valor int resolverá seu problema. A razão pela qual seu código não funcionou foi porque não havia mais nada para armazenar da sua entrada (onde você inseriu o int) nostring1 . Vou lançar um pouco mais de luz para todo o tópico.

Considere nextLine () como o ímpar entre os métodos nextFoo () na classe Scanner. Vamos dar um exemplo rápido. Digamos que temos duas linhas de código como as abaixo:

int firstNumber = input.nextInt();
int secondNumber = input.nextInt();

Se inserirmos o valor abaixo (como uma única linha de entrada)

54 234

O valor de nossa firstNumbere secondNumbervariável se tornam 54 e 234, respectivamente. A razão pela qual isso funciona dessa maneira é porque um novo avanço de linha (por exemplo, \ n ) NÃO É gerado automaticamente quando o método nextInt () recebe os valores. Simplesmente pega o "próximo int" e segue em frente. É o mesmo para o restante dos métodos nextFoo (), exceto nextLine ().

nextLine () gera um novo feed de linha imediatamente após obter um valor; é isso que @RohitJain significa dizendo que o novo feed de linha é "consumido".

Por fim, o método next () simplesmente pega a String mais próxima sem gerar uma nova linha; isso o torna o método preferencial para obter seqüências de caracteres separadas na mesma linha única.

Espero que isso ajude .. Feliz codificação!

Taslim Oseni
fonte
o que é um "novo feed de linha"?
Archer
@Abcd Um novo feed de linha significa basicamente 'a partir de uma nova linha'.
Taslim Oseni 13/01/19
1
Esse exemplo "54 234" realmente esclarece as coisas.
Alonso del Arte
3

Use 2 objetos do scanner em vez de um

Scanner input = new Scanner(System.in);
System.out.println("Enter numerical value");    
int option;
Scanner input2 = new Scanner(System.in);
option = input2.nextInt();
Harsh Shah
fonte
2
public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int i = scan.nextInt();
        scan.nextLine();
        double d = scan.nextDouble();
        scan.nextLine();
        String s = scan.nextLine();

        System.out.println("String: " + s);
        System.out.println("Double: " + d);
        System.out.println("Int: " + i);
    }
Neeraj Gahlawat
fonte
Se você usar o método nextLine () imediatamente após o método nextInt (), lembre-se de que nextInt () lê tokens inteiros; por isso, o último caractere de nova linha dessa linha de entrada inteira ainda está na fila no buffer de entrada e o próximo nextLine () estará lendo o restante da linha inteira (que está vazia).
Neeraj Gahlawat
2

se eu espero uma entrada não vazia

public static Function<Scanner,String> scanLine = (scan -> {
    String s = scan.nextLine();
    return( s.length() == 0 ? scan.nextLine() : s );
  });


usado no exemplo acima:

System.out.println("Enter numerical value");    
int option = input.nextInt(); // read numerical value from input

System.out.println("Enter 1st string"); 
String string1 = scanLine.apply( input ); // read 1st string
System.out.println("Enter 2nd string");
String string2 = scanLine.apply( input ); // read 2nd string
Kaplan
fonte
2

Em um dos meus casos de uso, tive o cenário de ler um valor de cadeia precedido por alguns valores inteiros . Eu tive que usar um loop " for / while " para ler os valores. E nenhuma das sugestões acima funcionou neste caso.

Usando em input.next()vez de input.nextLine()corrigido o problema. Espero que isso possa ser útil para aqueles que lidam com um cenário semelhante.

Giri
fonte
2

Use este código para corrigir o seu problema.

System.out.println("Enter numerical value");    
int option;
option = input.nextInt(); // Read numerical value from input
input.nextLine();
System.out.println("Enter 1st string"); 
String string1 = input.nextLine(); // Read 1st string (this is skipped)
System.out.println("Enter 2nd string");
String string2 = input.nextLine(); // Read 2nd string (this appears right after reading numerical value)
Jeewantha Lahiru
fonte
2

Como os nextXXX()métodos não leem newline, exceto nextLine(). Podemos pular a newlineapós ler qualquer non-stringvalor ( intneste caso) usando scanner.skip()o seguinte:

Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(x);
double y = sc.nextDouble();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(y);
char z = sc.next().charAt(0);
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(z);
String hello = sc.nextLine();
System.out.println(hello);
float tt = sc.nextFloat();
sc.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");
System.out.println(tt);
Sandeep Kumar
fonte
0

Por que não usar um novo scanner para todas as leituras? Como abaixo. Com essa abordagem, você não enfrentará seu problema.

int i = new Scanner(System.in).nextInt();
Tobias Johansson
fonte
3
Mas então você deve fechar o Scannerpara evitar vazamento de memória. Desperdiçando tempo?
TheCoffeeCup
1
O GC não cuida disso se o Scanner estiver envolvido em um método? Como: String getInput () {retorna novo Scanner (System.in) .nextLine ()};
Tobias Johansson
Isso é absolutamente incorreto. nextInt()não consumirá a nova linha, independentemente de estar em uma "nova" Scannerou em uma já usada.
Dawood ibn Kareem