2016-03-19 2 views
0

Ich habe ein Problem mit Scanner # nextLine. Nach meinem Verständnis sollte nextLine() den Rest des aktuellen Eingabestroms zurückgeben und dann zur nächsten Zeile weitergehen.Scanner # nextLine() hinterlässt ein übrig gebliebenes Newline-Zeichen

while (true){ 
     try{ 
     System.out.println("Please enter a month in numeric form"); 
     month = input.nextInt(); 
     System.out.println("Please enter a day in numeric form"); 
     day = input.nextInt(); 
     System.out.println("Please enter a two-digit year"); 
     if (input.hasNextInt() == true){ 
      year = input.next(); 
     } 
     else{ 
      throw new java.util.InputMismatchException(); 
     } 
     break; 
     } 
     catch(Exception e){ 
     System.err.println(e); 
     System.out.println("\nOne of your inputs was not valid."); 
     System.out.println(input.nextLine()); 
     } 
    } 

Das Problem ist die letzte Zeile. Wenn ich es als input.nextLine() belasse, akzeptiert die nächste Iteration der Schleife ein Newline-Zeichen für den Monat. Warum das? Sollte der Aufruf von nextLine im catch-Block den Rest der Zeile (einschließlich der Zeilenumbruch) nicht konsumieren und den Benutzer in der nächsten Iteration korrekt auffordern? Hinweis: Ich habe beschlossen, sie auszudrucken, um herauszufinden, was passiert, aber keine Zigarre.

ich eine Ausgabe vom Terminal gesammelt haben zu veranschaulichen, was ich meine:

// What should happen (this is when catch contains input.next() rather than nextLine) 
    /* 
    Please enter a month in numeric form 
    8 
    Please enter a day in numeric form 
    2 
    Please enter a two-digit year 
    badinput 
    java.util.InputMismatchException 

    One of your inputs was not valid. 
    badinput 
    Please enter a month in numeric form <------------- prompts for input, as expected 
    */ 

// What happens when I have nextLine in the catch block (code above) 
    /* 
    Please enter a month in numeric form 
    8 
    Please enter a day in numeric form 
    2 
    Please enter a two-digit year 
    badinput 
    java.util.InputMismatchException 

    One of your inputs was not valid. 
             <------------ leftover newline printed, as expected 
    Please enter a month in numeric form <---------------- does not prompt for input due to another leftover newline (why?) 
    java.util.InputMismatchException 

    One of your inputs was not valid. 
    badinput <----------------------- prints badinput even though it should've been consumed on the last iteration (why?) 
    Please enter a month in numeric form 
    */ 

Bevor jemand markiert diese als Duplikat, haben Sie bitte Verständnis, dass ich auf die Unterschiede zwischen den nächsten und nextline auf Stackoverflow haben gesucht bereits. nextLine sollte den Newline-Charakter konsumieren, scheint dies aber nicht zu tun. Vielen Dank.

+1

'if (input.hasNextInt() == true) {' ist das gleiche wie 'if (input.hasNextInt()) {'. Warum mit wahr vergleichen? – Andreas

Antwort

2
if (input.hasNextInt() == true) { // prefer `if(input.hasNextInt())` 
    year = input.next(); 
} else { 
    throw new java.util.InputMismatchException(); 
} 

für die Eingabe badinput wird input.hasNextInt() als falsch beurteilen, was bedeutet, dass else Block ausgeführt wird, ohne raubend dass badinput (es tun müssen, wir next() nennen - nicht nextLine() weil, wie Sie wahrscheinlich wissen, ob wir nextLine verwenden nach nextInt werden wir verbleibenden Zeilentrennzeichen verbrauchen, nicht Wert von nächste like, mehr Info unter Scanner is skipping nextLine() after using next(), nextInt() or other nextFoo() methods).

Da else Block einfach Ausnahme auslöst, verschiebt es Kontrollfluss zu catch Abschnitt. Das bedeutet, dass wir break überspringen, so dass unsere Schleife erneut durchlaufen werden muss.

Jetzt in Rastabschnitt Sie einfach drucken

System.err.println(e); 
System.out.println("\nOne of your inputs was not valid."); 
System.out.println(input.nextLine()); 

die Ausnahme druckt e, string "\nOne of your inputs was not valid." und Ergebnis nextLine() (die, wie oben erläutert) verbrauchen einfach Linie Separatoren, die nach der letzten nextInt() Anruf blieb, so dass wir konsumierte immer noch nicht badinput von Scanner. Diese

bedeutet, dass, wenn Schleife eine weitere Iteration beginnt und fragt nach Monat, es batinput erhält, die nicht gültig ist int so nextInt()InputMismatchException wirft. Und wieder landen wir in catch Block und wir rufen nextLine() an, die diesmal badinput verbraucht.

Jetzt, da wir endlich diesen fehlerhaften Wert verbraucht haben, startet eine weitere Iteration und wir werden nach dem Wert für den Monat gefragt.

Um diese Art von Problemen zu vermeiden, lesen Sie bitte die folgenden Beispiele: Validating input using java.util.Scanner. Im ersten Beispiel werden Sie feststellen, Art und Weise jede Eingabe zur Zeit zu validieren es

Scanner sc = new Scanner(System.in); 
int number; 
do { 
    System.out.println("Please enter a positive number!"); 
    while (!sc.hasNextInt()) { 
     System.out.println("That's not a number!"); 
     sc.next(); // this is important! 
    } 
    number = sc.nextInt(); 
} while (number <= 0); 
System.out.println("Thank you! Got " + number); 

diesen Code zu vermeiden vorgesehen schreibt oft Ihre eigene Hilfsmethode erstellen. Sie können sogar Bedingung überspringen, wo Sie Nummer benötigen, positiv zu sein wie:

public static int getInt(Scanner sc, String askMsg) { 
    System.out.println(askMsg); 
    while (!sc.hasNextInt()) { 
     System.out.println("That's not a number. Please try again"); 
     sc.next(); // consuming incorrect token 
    } 
    //here we know that next value is proper int so we can safely 
    //read and return it 
    return sc.nextInt(); 
} 

Mit dieser Methode könnte Ihr Code

Scanner input = new Scanner(System.in); 
int month = getInt(input, "Please enter a month in numeric form"); 
int day = getInt(input, "Please enter a day in numeric form"); 
int year = getInt(input, "Please enter a two-digit year"); 

Sie eine andere Version dieses Dienstprogramm Methode hinzufügen kann reduziert werden in dem Sie dem Programmierer Bedingungen hinzufügen lassen können, welche Zahl übergeben werden soll. Wir können das IntPredicate funktionale Schnittstelle für in Java 8, hinzugefügt benutzen, die uns Bedingungen können schaffen mit Lambda-Ausdrücke wie

public static int getInt(Scanner sc, String askMsg, IntPredicate predicate) { 
    System.out.println(askMsg); 
    int number; 
    boolean isIncorrect = true; 
    do { 
     while (!sc.hasNextInt()) { 
      String value = sc.next(); // consuming incorrect token 
      System.out.println(value + " is not valid number. Please try again"); 
     } 
     number = sc.nextInt(); 
     if (!predicate.test(number)) { 
      System.out.println(number + " is not valid number. Please try again"); 
     }else{ 
      isIncorrect=false; 
     } 
    } while (isIncorrect); 

    return number; 
} 

Verbrauch:

int year = getInt(input, "Please enter a two-digit year", i -> (i>=10 && i<=99)); 
1

Ich vermute, wenn Sie zwei Ziffern Jahr eingeben, und wie Sie next() verwenden, um es zu lesen, so wird es nur die nächste Zeichenfolge lesen. Und es wird 2 übriglassen, um von Ihrer nextLine() neuen Zeile oder leerem Wert gelesen zu werden, sogar wenn Sie den Wert für Ihr zweistelliges Jahr eingeben und alles danach übrig bleibt, einschließlich der neuen Zeile oder Wagenrücklauf, wenn Sie einen ungültigen Wert eingegeben haben . Ihr nextLine() -Interface-Catch liest also nur den Teil der ungültigen Eingabe, der übrig bleibt, aber belässt die neue Zeile oder den Zeilenumbruch unverändert. Dies führt dazu, dass die Ausnahme auftritt, während Sie erwarten, dass die Meldung angezeigt wird, um den Monat zu lesen. Sie können nextLine() nach jedem nextInt() oder next() platzieren, um das Problem zu lösen.

1

Denken Sie daran, die Scanner nicht sieht Ihre print-Anweisungen, es liest nur die Eingabe als Strom von Zeichen. Die Tatsache, dass Sie als Benutzer diese Zeichen Zeile für Zeile eingeben, ist für den Scanner bedeutungslos.

Sie geben also 8<ENTER> ein (wobei <ENTER> die tatsächlichen Zeilenumbruchzeichen Ihres Betriebssystems darstellt). Nach nextInt() wurde 8 verbraucht.

Sie geben dann 2<ENTER> ein und machen die ausstehende Eingabe <ENTER>2<ENTER>. Denken Sie daran, nur die 8 wurde verbraucht, so weit. nextInt() überspringt Whitespace und gibt 2 zurück, dabei verbrauchen<ENTER>2.

Dann geben Sie badinput<ENTER> ein und machen die ausstehende Eingabe <ENTER>badinput<ENTER>. Da das nächste Token keine gültige ganze Zahl ist, werfen Sie eine Ausnahme aus und geben den catch Block ein, in dem Sie nextLine() aufrufen. Es verbraucht alle Zeichen bis einschließlich der ersten <ENTER> und gibt den Text vor, d. H. Eine leere Zeichenfolge zurück.

Zu diesem Zeitpunkt ist badinput<ENTER> noch im Stream ausstehend und wird verarbeitet, wenn Sie zurückschleifen.


Dies ist eine der wichtigsten Mängel, wie die Menschen Scanner verwenden.nextInt() verbraucht nicht die Linie, nur das Token, den Rest der Linie hinter sich lassend.

Beispiel dafür, wie die Dinge schlecht mit Scanner:

Please enter a month in numeric form 
8 2 17 
Please enter a day in numeric form 
Please enter a two-digit year 

Da Benutzern alle drei Werte in der ersten Zeile eingegeben, Code, den Sie die Werte erhalten, aber immer noch die nächsten zwei Aufforderungen drucken, auch wenn die ist unnötig. Es ist einfach so komisch.

Lösung 1: Verwenden Sie nicht Scanner. Es ist einfach zu schräg. Zu einfach zu bedienen, und so einfach zu missbrauchen, aka soooo schwierig, richtig zu verwenden.

Lösung 2: Anruf nextLine() nach jedem nextInt() zu spülen (verbrauchen still) keinen zusätzlichen Text nach dem akzeptierten Wert. Wenn Sie das tun, würde das Beispiel so:

Please enter a month in numeric form 
8 2 17 
Please enter a day in numeric form 
2 
Please enter a two-digit year 
17 

Die <SPACE>2<SPACE>17<ENTER> auf der ersten Linie stillschweigend ignoriert würde.

Sie können die Logik auf if (! nextLine().trim().isEmpty()) {/*ERROR*/} erweitern, wenn Sie eine vollständige Fehlerbehandlung wünschen.

Verwandte Themen