Java: Infinite loop using Scanner in.hasNextInt()

50,233

Solution 1

In your last else block, you need to clear the 'w' or other invalid input from the Scanner. You can do this by calling next() on the Scanner and ignoring its return value to throw away that invalid input, as follows:

else
{
      System.out.println("You have entered an invalid input. Try again.");
      in.next();
}

Solution 2

The problem was that you did not advance the Scanner past the problematic input. From hasNextInt() documentation:

Returns true if the next token in this scanner's input can be interpreted as an int value in the default radix using the nextInt() method. The scanner does not advance past any input.

This is true of all hasNextXXX() methods: they return true or false, without advancing the Scanner.

Here's a snippet to illustrate the problem:

    String input = "1 2 3 oops 4 5 6";
    Scanner sc = new Scanner(input);
    while (sc.hasNext()) {
        if (sc.hasNextInt()) {
            int num = sc.nextInt();
            System.out.println("Got " + num);
        } else {
            System.out.println("int, please!");
            //sc.next(); // uncomment to fix!
        }
    }

You will find that this program will go into an infinite loop, asking int, please! repeatedly.

If you uncomment the sc.next() statement, then it will make the Scanner go past the token that fails hasNextInt(). The program would then print:

Got 1
Got 2
Got 3
int, please!
Got 4
Got 5
Got 6

The fact that a failed hasNextXXX() check doesn't skip the input is intentional: it allows you to perform additional checks on that token if necessary. Here's an example to illustrate:

    String input = " 1 true foo 2 false bar 3 ";
    Scanner sc = new Scanner(input);
    while (sc.hasNext()) {
        if (sc.hasNextInt()) {
            System.out.println("(int) " + sc.nextInt());
        } else if (sc.hasNextBoolean()) {
            System.out.println("(boolean) " + sc.nextBoolean());
        } else {
            System.out.println(sc.next());
        }
    }

If you run this program, it will output the following:

(int) 1
(boolean) true
foo
(int) 2
(boolean) false
bar
(int) 3

Solution 3

This statement by Ben S. about the non-blocking call is false:

Also, hasNextInt() does not block. It's the non-blocking check to see if a future next call could get input without blocking.

...although I do recognize that the documentation can easily be misread to give this opinion, and the name itself implies it is to be used for this purpose. The relevant quote, with emphasis added:

The next() and hasNext() methods and their primitive-type companion methods (such as nextInt() and hasNextInt()) first skip any input that matches the delimiter pattern, and then attempt to return the next token. Both hasNext and next methods may block waiting for further input. Whether a hasNext method blocks has no connection to whether or not its associated next method will block.

It is a subtle point, to be sure. Either saying "Both the hasNext and next methods", or "Both hasnext() and next()" would have implied that the companion methods would act differently. But seeing as they conform to the same naming convention (and the documentation, of course), it's reasonable to expect they act the same, and hasNext() clearly says that it can block.

Meta note: this should probably be a comment to the incorrect post, but it seems that as a new user I can only post this answer (or edit the wiki which seems to be preferred for sytlistic changes, not those of substance).

Solution 4

Flag variables are too error prone to use. Use explicit loop control with comments instead. Also, hasNextInt() does not block. It's the non-blocking check to see if a future next call could get input without blocking. If you want to block, use the nextInt() method.

// Scanner that will read the integer
final Scanner in = new Scanner(System.in);
int inputInt;
do {  // Loop until we have correct input
    System.out.print("Specify an integer between 0 and 5: ");
    try {
        inputInt = in.nextInt(); // Blocks for user input
        if (inputInt >= 0 && inputInt <= 5)  { 
            break;    // Got valid input, stop looping
        } else {
            System.out.println("You have not entered a number between 0 and 5. Try again.");
            continue; // restart loop, wrong number
         }
    } catch (final InputMismatchException e) {
        System.out.println("You have entered an invalid input. Try again.");
        in.next();    // discard non-int input
        continue;     // restart loop, didn't get an integer input
    }
} while (true);
Share:
50,233
Tomek
Author by

Tomek

Updated on July 09, 2022

Comments

  • Tomek
    Tomek almost 2 years

    I am using the following code:

    while (invalidInput)
    {
        // ask the user to specify a number to update the times by
        System.out.print("Specify an integer between 0 and 5: ");
    
        if (in.hasNextInt())
        {
            // get the update value
            updateValue = in.nextInt();
    
            // check to see if it was within range
            if (updateValue >= 0 && updateValue <= 5) 
            { 
                invalidInput = false; 
            } 
            else 
            {
                System.out.println("You have not entered a number between 0 and 5. Try again.");
            }
        } else
        {
            System.out.println("You have entered an invalid input. Try again.");
        }
    }
    

    However, if I enter a 'w' it will tell me "You have entered invalid input. Try Again." and then it will go into an infinite loop showing the text "Specify an integer between 0 and 5: You have entered an invalid input. Try again."

    Why is this happening? Isn't the program supposed to wait for the user to input and press enter each time it reaches the statement:

    if (in.hasNextInt())
    
  • Tomek
    Tomek over 14 years
    is there a reason you make the Scanner final?
  • Ben S
    Ben S over 14 years
    I make it final since it won't change. The compiler might do certain optimizations because of this. It's a good habit to declare everything that can be final. The loop will be broken when the user enters correct input. It's only infinite if the user choose to make it infinite. If you want, I can edit it so that it only allows for a given number of retries.
  • Tomek
    Tomek over 14 years
    I mean if i enter a 'w' it goes into an infinite loop of displaying "Specify an integer between 0 and 5: You have entered an invalid input. Try again." "Specify an integer between 0 and 5: You have entered an invalid input. Try again." "Specify an integer between 0 and 5: You have entered an invalid input. Try again." and so on. It was not until I added the in.next() to the catch statement to "to clear the 'w' or other invalid input from the Scanner" as Brasse said. Unrelated, but you produce some quality code
  • Ben S
    Ben S over 14 years
    I fix my example to properly discard non-int inputs.
  • Tomek
    Tomek over 14 years
    thank you but the in.next() statement should go in the catch block
  • Lie Ryan
    Lie Ryan over 13 years
    congratulations for doing cargo cult programming
  • LarsH
    LarsH almost 6 years
    As @RichFletcher pointed out, it's not true that "hasNextInt() does not block." The documentation is explicit on that point. I think what you meant was that hasNextInt() doesn't advance past any input.