Le scanner ignore nextLine () après avoir utilisé next () ou nextFoo ()?


684

J'utilise les Scannerméthodes nextInt()et nextLine()pour lire les entrées.

Cela ressemble à ceci:

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)

Le problème est qu'après avoir entré la valeur numérique, le premier input.nextLine()est ignoré et le second input.nextLine()est exécuté, de sorte que ma sortie ressemble à ceci:

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

J'ai testé mon application et il semble que le problème réside dans son utilisation input.nextInt(). Si je le supprime, les deux string1 = input.nextLine()et string2 = input.nextLine()sont exécutés comme je le souhaite.


5
Ou vous pourriez être comme moi et utiliser BufferedReader :) Je me fiche que ce soit de la vieille école, cela a toujours fonctionné et fonctionnera toujours pour moi. En outre, la connaissance de BufferedReader a une application ailleurs. Je n'aime tout simplement pas Scanner.
Cruncher

Réponses:


831

En effet, la Scanner.nextIntméthode ne lit pas le caractère de nouvelle ligne dans votre entrée créée en appuyant sur "Entrée", et donc l'appel à Scanner.nextLinerevient après avoir lu cette nouvelle ligne .

Vous rencontrerez le comportement similaire lorsque vous utilisez Scanner.nextLineaprès Scanner.next()ou toute Scanner.nextFoométhode (sauf nextLineelle-même).

Solution de contournement:

  • Soit vous passez un Scanner.nextLineappel après chacun, Scanner.nextIntsoit Scanner.nextFoovous consommez le reste de cette ligne, y compris la nouvelle ligne

    int option = input.nextInt();
    input.nextLine();  // Consume newline left-over
    String str1 = input.nextLine();
  • Ou, encore mieux, lisez l'entrée Scanner.nextLineet convertissez-la au format approprié dont vous avez besoin. Par exemple, vous pouvez convertir un entier en utilisant la Integer.parseInt(String)méthode.

    int option = 0;
    try {
        option = Integer.parseInt(input.nextLine());
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
    String str1 = input.nextLine();

4
@blekione. Vous devez utiliser try-catch, car Integer.parseIntjette NumberFormatExceptionlorsqu'un argument non valide lui est passé. Vous découvrirez l'exception plus tard. Pour EG: - Integer.parseInt("abc"). Vous ne voulez pas que "abc" soit converti en int?
Rohit Jain

3
@blekione. Donc, dans le cas ci-dessus, votre code s'arrêtera à ce stade et vous ne pourrez pas continuer l'exécution. Avec la gestion des exceptions, vous pouvez gérer ce type de conditions.
Rohit Jain

4
Dans quelle mesure ce dernier est-il meilleur? AFAIK Scanner # nextInt () est beaucoup plus indulgent dans la recherche des entrées correctes, en autorisant les virgules de groupe et les préfixes et suffixes locaux. Integer # parseInt () autorise uniquement les chiffres et le point décimal plus un signe facultatif.
Mordechai

5
Personnellement, je préfère un Scanner#hasNextFoochèque à l'avance au lieu d'un try-catch, mais cela fonctionne aussi.
MildlyMilquetoast

1
Utiliser ParseDouble a un nouveau problème, que nextDouble utilise la configuration régionale de décimal (. Ou,) mais Parsedouble reçoit toujours US décimal (.).
olmerg

209

Le problème vient de la méthode input.nextInt () - elle ne lit que la valeur int. Ainsi, lorsque vous continuez à lire avec input.nextLine (), vous recevez la touche Entrée "\ n". Donc, pour ignorer cela, vous devez ajouter le input.nextLine () . J'espère que cela devrait être clair maintenant.

Essayez-le comme ça:

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();

umm semble une solution, rendant la ligne sautée non utilisée nextLine, mais j'ai toujours besoin d'une explication de ce comportement
Eng.Fouad


Question: posée le 27 octobre 12 à 16h37. répondu le 14 août 11 à 12:24. Comment est-ce encore possible?
Apoorv Patne

79

C'est parce que lorsque vous entrez un nombre puis appuyez sur Enter, input.nextInt()ne consomme que le nombre, pas la "fin de ligne". Lorsqu'il input.nextLine()s'exécute, il consomme la "fin de ligne" toujours dans le tampon de la première entrée.

Au lieu de cela, utilisez input.nextLine()immédiatement aprèsinput.nextInt()


7
@Victor ce n'est pas un bug. cela fonctionne comme spécifié. vous pourriez cependant faire valoir qu'il devrait y avoir un moyen facile de dire à l'api de consommer également tous les espaces suivant l'entrée cible.
Bohème

Je vois. merci @Bohemian, c'est exactement ce que je dis. Je pense que cela devrait être changé, peut-être pouvez-vous m'indiquer où suggérer ce "problème" dans le prochain JCP.
Victor

@victor Vous pouvez demander (et découvrir comment contribuer du code à) une fonctionnalité via la page Signaler un bug ou Demander une fonctionnalité .
Bohème


48

Il semble y avoir beaucoup de questions sur ce problème java.util.Scanner. Je pense qu'une solution plus lisible / idiomatique serait d'appeler scanner.skip("[\r\n]+")pour supprimer tous les caractères de nouvelle ligne après l'appel nextInt().

EDIT: comme @PatrickParker l'a noté ci-dessous, cela provoquera une boucle infinie si l'utilisateur entre des espaces après le nombre. Voir leur réponse pour un meilleur modèle à utiliser avec skip: https://stackoverflow.com/a/42471816/143585



Je sais ce que nous faisons pour supprimer les données dans le tampon, mais dans ce cas, veuillez m'aider avec ceci: stackoverflow.com/questions/33585314/having-issues-with-scanner
Khuong

1
Pour info, cela provoquera une boucle infinie si l'utilisateur tape des tabulations ou des espaces après la saisie numérique. Voir ma réponse ici pour un meilleur saut: stackoverflow.com/a/42471816/7098259
Patrick Parker

@PatrickParker: cela provoque en effet une boucle infinie! Merci d'avoir modifié la réponse pour créer un lien vers la vôtre.
Denis Tulskiy

33

Il le fait parce input.nextInt();qu'il ne capture pas la nouvelle ligne. vous pourriez faire comme les autres proposés en ajoutant un input.nextLine();dessous.
Alternativement, vous pouvez le faire en style C # et analyser une nextLine en un entier comme ceci:

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

Faire cela fonctionne aussi bien et cela vous permet d'économiser une ligne de code.



1
vous devez utiliser try catch ici. Que se passera-t-il si l'entrée n'est pas un nombre. NumberFormatException doit être géré ici.
Arundev

Cela suppose qu'il n'y a qu'un seul jeton int sur chaque ligne pour laquelle vous essayez ceci.
cellepo

25

TL; DR À utiliser scanner.skip("\\R")avant chaque scanner.newLine()appel, qui est exécuté après:

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

Ce que vous devez savoir:

  • le texte qui représente quelques lignes contient également des caractères non imprimables entre les lignes (nous les appelons séparateurs de lignes) comme

    • retour chariot (CR - dans les littéraux de chaîne représentés comme "\r")
    • saut de ligne (LF - dans les littéraux de chaîne représentés comme "\n")
  • lorsque vous lisez des données à partir de la console, cela permet à l'utilisateur de taper sa réponse et lorsqu'il a terminé, il doit en quelque sorte confirmer ce fait. Pour ce faire, l'utilisateur doit appuyer sur la touche "Entrée" / "Retour" du clavier.

    Ce qui est important, c'est que cette clé, en plus de garantir le placement des données utilisateur sur l'entrée standard (représentée par System.inlaquelle est lu par Scanner), envoie également des séparateurs de ligne dépendants du système d'exploitation (comme pour Windows \r\n).

    Ainsi, lorsque vous demandez à l'utilisateur une valeur telle que age, et que l'utilisateur tape 42 et appuie sur Entrée, l'entrée standard contient "42\r\n".

Problème

Scanner#nextInt(et d'autres méthodes) ne permet pas au scanner de consommer ces séparateurs de ligne. Il les lira (comment le scanner pourrait-il savoir qu'il n'y a pas plus de chiffres de l'utilisateur qui représentent une valeur que face aux espaces blancs?) Qui les supprimera de l'entrée standard, mais il mettra également en cache ces séparateurs de ligne en interne . Ce dont nous devons nous souvenir, c'est que toutes les méthodes du scanner sont toujours en train de numériser à partir du texte mis en cache. Scanner#nextTypeSystem.inage

Maintenant, il Scanner#nextLine()suffit de collecter et de retourner tous les caractères jusqu'à ce qu'il trouve des séparateurs de ligne (ou la fin du flux). Mais comme les séparateurs de lignes après avoir lu le numéro à partir de la console se trouvent immédiatement dans le cache de Scanner, il renvoie une chaîne vide, ce qui signifie que Scanner n'a pas pu trouver de caractère avant ces séparateurs de lignes (ou fin de flux).
BTW consommenextLine également ces séparateurs de ligne.

Solution

Ainsi, lorsque vous souhaitez demander un numéro, puis une ligne entière, tout en évitant cette chaîne vide à la suite de nextLine, soit

  • consommer le séparateur de lignes laissé par le nextIntcache des scanners par
    • appeler nextLine,
    • ou IMO de manière plus lisible serait d'appeler skip("\\R")ou skip("\r\n|\r|\n")de laisser le scanner ignorer la partie correspondant au séparateur de ligne (plus d'informations sur \R: https://stackoverflow.com/a/31060125 )
  • n'utilisez pas nextInt(ni next, ni aucune nextTYPEméthode) du tout. Au lieu de cela, lisez l'intégralité des données ligne par ligne en utilisant nextLineet en analysant les numéros de chaque ligne (en supposant qu'une ligne ne contient qu'un seul numéro) au type approprié, comme intvia Integer.parseInt.

BTW : les méthodes peuvent ignorer les délimiteurs (par défaut, tous les espaces comme les tabulations, les séparateurs de lignes), y compris ceux mis en cache par le scanner, jusqu'à ce qu'ils trouvent la prochaine valeur non délimitante (jeton). Merci à cela pour l'entrée comme le codeScanner#nextType"42\r\n\r\n321\r\n\r\n\r\nfoobar"

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

sera en mesure d'assigner correctement num1=42 num2=321 name=foobar.


14

Au lieu d' input.nextLine()utiliser input.next(), cela devrait résoudre le problème.

Code modifié:

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();
}


13

Si vous souhaitez lire les chaînes et les entiers, une solution consiste à utiliser deux 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();

1
C'est ce que je cherchais depuis un certain temps maintenant. Déménagement intelligent!
Liam Wilson

1
@BdLearner scanner.nextLine()devrait fonctionner.
André Valenti

11

Si vous souhaitez analyser rapidement les entrées sans vous perdre dans la méthode nextLine () de la classe Scanner, utilisez pour cela un scanner d'entrée personnalisé.

Code:

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;
    }

}

Avantages:

  • Analyse les entrées plus rapidement que BufferReader
  • Réduit la complexité temporelle
  • Vide le tampon pour chaque entrée suivante

Méthodes:

  • scanChar () - analyse un seul caractère
  • scanInt () - analyse la valeur entière
  • scanLong () - valeur longue de numérisation
  • scanString () - valeur de chaîne d'analyse
  • scanDouble () - Scan Double valeur
  • scanInt (int [] array) - analyse le tableau complet (entier)
  • scanLong (tableau long []) - analyse le tableau complet (long)

Utilisation:

  1. Copiez le code donné sous votre code java.
  2. Initialiser l'objet pour une classe donnée

ScanReader sc = new ScanReader(System.in); 3. Importez les classes nécessaires:

import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; 4. Lancez IOException à partir de votre méthode principale pour gérer l'exception 5. Utilisez les méthodes fournies. 6. Profitez

Exemple :

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....

10

Afin d'éviter le problème, utilisez nextLine();immédiatement après nextInt();car cela aide à vider le tampon. Lorsque vous appuyez surENTER le nextInt();ne capture pas la nouvelle ligne et, par conséquent, ignore le Scannercode plus tard.

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

9

sc.nextLine()est meilleure par rapport à l'analyse de l'entrée. Parce que sur le plan des performances, ce sera bon.


5

Je suppose que je suis assez tard pour la fête ..

Comme indiqué précédemment, appeler input.nextLine()après avoir obtenu votre valeur int résoudra votre problème. La raison pour laquelle votre code n'a pas fonctionné était qu'il n'y avait rien d'autre à stocker à partir de votre entrée (où vous avez entré l'int) dans string1. Je vais juste jeter un peu plus de lumière sur l'ensemble du sujet.

Considérez nextLine () comme l' intrus parmi les méthodes nextFoo () de la classe Scanner. Prenons un exemple rapide. Disons que nous avons deux lignes de code comme celles ci-dessous:

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

Si nous entrons la valeur ci-dessous (comme une seule ligne d'entrée)

54 234

La valeur de notre firstNumberet secondNumbervariable devient respectivement 54 et 234. La raison pour laquelle cela fonctionne de cette façon est qu'un nouveau saut de ligne ( c'est-à-dire \ n ) N'EST PAS généré automatiquement lorsque la méthode nextInt () prend les valeurs. Il prend simplement le "prochain int" et passe à autre chose. C'est la même chose pour le reste des méthodes nextFoo () sauf nextLine ().

nextLine () génère un nouveau saut de ligne immédiatement après avoir pris une valeur; c'est ce que @RohitJain veut dire en disant que le nouveau fil de ligne est "consommé".

Enfin, la méthode next () prend simplement la chaîne la plus proche sans générer de nouvelle ligne; cela en fait la méthode préférentielle pour prendre des chaînes séparées sur la même ligne.

J'espère que cela aide .. Joyeux codage!


qu'est-ce qu'un "nouveau saut de ligne"?
Archer

@Abcd Un nouveau saut de ligne signifie essentiellement "partir d'une nouvelle ligne".
Taslim Oseni

1
Cet exemple "54 234" clarifie vraiment les choses.
Alonso del Arte

3

Utilisez 2 objets scanner au lieu d'un

Scanner input = new Scanner(System.in);
System.out.println("Enter numerical value");    
int option;
Scanner input2 = new Scanner(System.in);
option = input2.nextInt();

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);
    }

Si vous utilisez la méthode nextLine () immédiatement après la méthode nextInt (), rappelez-vous que nextInt () lit les jetons entiers; à cause de cela, le dernier caractère de nouvelle ligne pour cette ligne d'entrée entière est toujours en file d'attente dans le tampon d'entrée et la prochaine nextLine () lira le reste de la ligne entière (qui est vide).
Neeraj Gahlawat

2

si j'attends une entrée non vide

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


utilisé dans l'exemple ci-dessus:

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

2

Dans l'un de mes cas d'utilisation, j'ai eu le scénario de lecture d'une valeur de chaîne précédée de quelques valeurs entières . J'ai dû utiliser une " boucle for / while " pour lire les valeurs. Et aucune des suggestions ci-dessus n'a fonctionné dans ce cas.

Utiliser input.next()au lieu de input.nextLine()résoudre le problème. J'espère que cela pourrait être utile pour ceux qui font face à un scénario similaire.


2

Utilisez ce code, il résoudra votre problème.

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)

2

Comme les nextXXX()méthodes ne lisent pas newline, sauf nextLine(). Nous pouvons ignorer newlineaprès avoir lu n'importe quelle non-stringvaleur ( intdans ce cas) en utilisant scanner.skip()comme ci-dessous:

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);

0

Pourquoi ne pas utiliser un nouveau scanner pour chaque lecture? Comme ci-dessous. Avec cette approche, vous ne confronterez pas votre problème.

int i = new Scanner(System.in).nextInt();

3
Mais vous devez ensuite fermer le Scannerpour éviter les fuites de mémoire. Perdre du temps?
TheCoffeeCup

1
Le GC ne s'en occupe-t-il pas si le scanner est enveloppé dans une méthode? Comme: String getInput () {return new Scanner (System.in) .nextLine ()};
Tobias Johansson

C'est absolument incorrect. nextInt()ne consommera pas la nouvelle ligne, qu'elle soit dans une "nouvelle" Scannerou une déjà utilisée.
Dawood ibn Kareem
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.