0

I've been running into a problem with Scanner#nextLine. From my understanding, nextLine() should return the rest of the current input stream and then move on to the next line.

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

The problem is the last line. If I leave it as input.nextLine(), the next iteration of the loop accepts a newline character for the month. Why is that? Shouldn't the call to nextLine in the catch block consume the rest of the line (including the newline) and prompt the user correctly in the next iteration? Note: I've decided to print them to try and figure out what's happening, but no cigar.

I've gathered some output from the terminal to illustrate what I mean:

// 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
    */

Before someone marks this as a duplicate, please understand that I've looked at the differences between next and nextLine on stackoverflow already. nextLine should consume the newline character but it doesn't seem to do that here. Thanks.

Quantum Dot
  • 395
  • 1
  • 5
  • 12
  • 2
    `if (input.hasNextInt() == true){` is the same as `if (input.hasNextInt()){`. Why compare to true? – Andreas Mar 20 '16 at 00:48

3 Answers3

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

for input badinput will evaluate input.hasNextInt() as false which means that else block will be executed without consuming that badinput (to do it we need to call next() - not nextLine() because as you probably know if we use nextLine after nextInt we will consume remaining line separator, not value from next like, more info at Scanner is skipping nextLine() after using next(), nextInt() or other nextFoo() methods).

So since else block simply throws exception it moves control flow to catch section. This means we are skipping break so our loop will need to iterate once again.

Now in catch section you are simply printing

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

which prints exception e, string "\nOne of your inputs was not valid." and result of nextLine() (which as explained before) will simply consume line separators which remained after last nextInt() call, so we still didn't consume badinput from Scanner.

This means that when loop starts another iteration and asks for month, it receives batinput which is not valid int so nextInt() throws InputMismatchException. And again we end up in catch block and we call nextLine() which this time consumes badinput.

Now since we finally consumed that incorrect value loop will start another iteration and we will be asked for value for month.

To avoid this kind of problems please read examples from: Validating input using java.util.Scanner. In first example you will find way to validate each input at time it is provided

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

To avoid writing this code many times create your own utility method. You can even skip condition where you require from number to be positive like:

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

With this method your code could be reduced to

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

You can add another version of that utility method in which you can let programmer add conditions which number should pass. We can use IntPredicate functional interface for that added in Java 8, which will allow us to create conditions using lambdas like

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

Usage:

int year = getInt(input, "Please enter a two-digit year", i -> (i>=10 && i<=99));
Community
  • 1
  • 1
Pshemo
  • 113,402
  • 22
  • 170
  • 242
1

I suspect when you are entering two digit year, and as you are using next() to read it so it will read the next string only. And it will leave 2 to be read by your nextLine() new line or empty value before even you enter the value for your 2 digit year and anything after that will be left over including the new line or carriage return as you entered an invalid value. So your nextLine() inside catch just reads that left over part of invalid input but leaves the new line or carriage return as is. which is causing the exception to occur while you expect prompt to appear to read month. you can place nextLine() after each nextInt() or next() to resolve the issue.

Pushpendra Pal
  • 640
  • 6
  • 19
1

Remember, the Scanner does not see your print statements, it just reads input as a stream of characters. The fact that you, as a user, enter those characters one line at a time is meaningless to the scanner.

So, you type 8<ENTER> (where <ENTER> represents that actual newline character(s) of your OS). After nextInt(), the 8 has been consumed.

You then type 2<ENTER>, making the pending input <ENTER>2<ENTER>. Remember, only the 8 was consumed, so far. nextInt() skips then whitespace and returns 2, thereby consuming <ENTER>2.

You then type badinput<ENTER>, making the pending input <ENTER>badinput<ENTER>. Since the next token is not a valid integer number, you throw exception, and enter the catch block, where you call nextLine(). It consumes all character up to and including the first <ENTER>, and returns the text before, i.e. an empty string.

At this point in time, badinput<ENTER> is still pending in the stream, and is processed when you loop back.


This is one of the main flaws in how people use Scanner. nextInt() doesn't consume the line, only the token, leaving the rest of the line behind.

Example of how things go bad with Scanner:

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

Because user entered all 3 values on the first line, you code will get the values, but will still print the next two prompts, even though that is unnecessary. It's just weird like that.

Solution 1: Don't use Scanner. It's just too dang weird. Too easy to use, and soooo easy to misuse, aka soooo difficult to use correctly.

Solution 2: Call nextLine() after each nextInt() to flush (silently consume) any extra text after the accepted value. If you do that, the example would go like this:

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

The <SPACE>2<SPACE>17<ENTER> on the first line would be silently ignored.

You could extend the logic to if (! nextLine().trim().isEmpty()) {/*ERROR*/} if you want full error handling.

Andreas
  • 138,167
  • 8
  • 112
  • 195