0

I was trying to write a method that enables one to input integers via Scanner without the program crashing due to exceptions. Here's what I initially had:(here, sc is a Scanner object)

    public static int inputInt(String message) {                                            
    int returnval = 0;
    System.out.println(message);
    try {
        returnval = sc.nextInt();
    } catch (Exception ex) {
        System.out.println("You must enter an integer.");
        inputInt(message);
    }
    return returnval;
}

When I tested the program with an invalid input, an infinite loop was initiated, with the message and "You must enter an integer" being printed many times before being halted by Eclipse. I fixed this using the following code:

 public static int inputInt(String message) { 
    int returnval = 0;
    System.out.println(message);
    String valstring = sc.nextLine();
    try {
        returnval = Integer.parseInt(valstring);
    } catch (Exception ex) {
        System.out.println("You must enter an integer.");
        inputInt(message);
    }
    return returnval;
}

Why does the first method fail, but not the second? Might there be a cleaner way to accomplish this?

Ayesha
  • 103
  • 2
  • 1
    possible duplicate of http://stackoverflow.com/questions/20741189/exceptions-and-infinite-loops – ajb Jan 09 '14 at 00:57
  • The basic reason is this: if you tell the scanner to look for an integer, and it sees a character that doesn't belong in an integer, it throws an exception, but that character is still there in the input. You have to do something to force the scanner to skip over it before asking it to try again. `sc.nextLine()` (see Doorknob of Snow's answer) will do the job. – ajb Jan 09 '14 at 01:00
  • note that if you use `nextInt()`, there is a _newline character_ left in your input. – Baby Jan 09 '14 at 01:02
  • @RafaEl True. But also note that if the next `nextInt()` sees the newline, it will skip over it, because `nextInt()` looks at tokens and whitespace characters such as newlines are (by default) not part of tokens. – ajb Jan 09 '14 at 01:16

2 Answers2

2

Yes, there is a cleaner way: use hasNextInt:

public static int inputInt(String message) {
    System.out.println(message);
    while (!sc.hasNextInt()) {
        sc.nextLine(); // clear the bad input first

        System.out.println("You must enter an integer.");
        System.out.println(message); // why use recursion? just use a while loop
    }
    return sc.nextInt();
}

The changes I made:

  • Uses hasNextInt, so you don't have to use exceptions
  • Added sc.nextLine(); (the root of your problem)
  • Instead of recursion, just does a while loop (why recurse when you can simple loop?)
  • Eliminated temporary variable
  • Made it more readable (IMO)
tckmn
  • 57,719
  • 27
  • 114
  • 156
  • One question here, though. The input 5 5 5 (three fives with spaces between) is accepted by this method. Is this a problem with my program? – Ayesha Jan 09 '14 at 01:27
  • @Ayesha That's because `hasNextInt` simple takes the next token (which would be 5). If you don't want that, call `nextLine` and test if it's a valid int. – tckmn Jan 09 '14 at 01:28
  • This would be done via something like what is shown here, correct?http://stackoverflow.com/questions/5439529/determine-if-a-string-is-an-integer-in-java – Ayesha Jan 09 '14 at 01:41
1

Problem

Scanner.nextInt() has the capacity of throwing 3 exceptions:

1- InputMismatchException: Thrown when the next item isn't an int.
2- NoSuchElementException: When there isn't a next anything.
3- IllegalStateException: If the Scanner has been closed.

So when you enter an illegal value, such as "abc" for example, the top piece of code does this:

Takes "abc" and tries to convert it to an int. (nextInt()). Because "abc" isn't a number, it cannot convert, and throws an InputMismatchException. However, beacuse the method didn't complete successfully, "abc" is left as the next item to be read. Because the exception was thrown, the code inside your catch block is run. It prints out "You must enter an integer." Then it calls itself again, passing itself message. It then initialises returnval and prints out the same message from the first run, because you passed it to yourself again. Then you enter the try block, where, because "abc" wasn't read successfully and left there, the scanner reads "abc" and tries to convert it to an int again. Of course, this won't work, and the cycle starts again.

Solution

Now you know what your problem is, you can figure out a solution. The most elegant in my mind would be to use the Scanner method, hasNextInt(). This will return a boolean letting you know if the next item to be read can be converted to an int. For example, if the next item is "abc", the method will return false. For the next item "1", the method will return true. So, if you modify your code like so, it should work:

public static int inputInt(String message) {                                            
    int returnval = 0;
    System.out.println(message);
    while(!sc.hasNextInt()) {
        sc.nextLine();
        System.out.println("You must enter an integer.");
    }
    returnval = sc.nextInt();
    return returnval;
}

What this code does:

1- Initializes returnval and prints out message.
2- Enters the while loop as long as the scanner doesn't have an int to read. Effectively "waiting" for the next int to be input.
3- Once it has an int to read, it reads the int and saves the value in returnval.
4- It returns returnval to the caller.

(Slow and steady wins the race, lol. I always seem to be the slow one to answer. Maybe because I write small novels as answers.... ;))

Community
  • 1
  • 1
Andrew Gies
  • 719
  • 8
  • 20
  • That's great! I liked how your answer was very comprehensive, don't change your style :) – Ayesha Jan 09 '14 at 01:32
  • How does one use hasNextInt() so that it considers the validity of the entire input, rather than just the first token entered? – Ayesha Jan 09 '14 at 01:48
  • @Ayesha In what sense? If the next few characters to be read are "365" then the `int` that will be read, (because the code will validate that as an `int` and proceed to read it), will be 365. A "token" can be defined as a string of characters that are significant in a group. In this case, the token for the above input is "365", not just the single character "3". – Andrew Gies Jan 09 '14 at 01:56
  • The input 5 5 5 is the one I used - I want such an input to be rejected. – Ayesha Jan 09 '14 at 02:23
  • Hmm..I tested that input. All it does is return the first "5" as an `int`. If you don't want it do so, I guess you could use `Scanner.findInLine(" ")` to look for any spaces in the entire line before you even try to parse an `int`. If so, you can return without parsing. – Andrew Gies Jan 09 '14 at 02:38
  • Of course. The JavaDocs are infinitely helpful for things like this. That's where I found the `findInLine()` method. The JavaDoc for Scanner is [here](http://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html). – Andrew Gies Jan 09 '14 at 03:21