javajava.util.scanner

Why does the scanner still run even though expression equates to false


I've come across an issue with my scanner functionality. Even after entering "exit" in the terminal, the scanner keeps running. My suspicion is that this behavior is linked to the revertState() function inside of the implementation of hasNext(), but I'm not sure.

   String cmd;
    do {
        cmd = scanner.nextLine();
        // ...
        }
        System.out.printf("%b%n", !cmd.equals("exit"));
    } while(scanner.hasNext() && !cmd.equals("exit"));

However, rearranging the statement to !cmd.equals("exit") && scanner.hasNext() works, because scanner.hasNext() will not be invoked, thanks to the short-circuit evaluation.

I would also like to know how the hasNext() function is blocking this whole process.


Solution

  • My suspicion is that this behavior is linked to the revertState() function inside of the implementation of hasNext(), but I'm not sure.

    No, it's down to your misunderstanding of what these things mean.

    Scanner wraps a resource - in your case, System.in. System.in is NOT the keyboard. It's 'standard in', and who knows what that is. It might be the keyboard. It might be a file. It might be nothing. Try it: java -cp myClasspath com.foo.MyApp <somefile.txt and now System.in represents the contents of that file.

    The crucial difference between files and keyboard

    The concept 'it has ended' doesn't really apply to keyboards. However, it would to files: Once the file you are piping into a process has reached its end, that is it; it has ended forever. It is possible for the code to realize: No data is there. Will ever be there. For the rest of time.

    In contrast a keyboard does not end (well, there's CTRL+D/CTRL+Z but let's leave explicitly 'closing the keyboard' out of it, clearly you aren't thinking of this if you ask this question).

    You wrote your code assuming that it will; that's where your mistake is.

    Your code, after the user types 'exit' and hits enter, asks: "Hey, is there another token?" via hasNext(). The answer is: Who knows - maybe. Maybe there will be one 5 minutes from now. The question cannot be answered (yet) by the system. You thought, perhaps, that hasNext() will wait a few seconds for the user to act and then draw the conclusion: Eh, nah, the user appears done. Or possibly that it has a crystal ball and attempts to scan the user's brain to find out if the user feels they are done - which, hopefully by putting it that way, should make you realize that's not how it works. Or you are thinking: hasNext() always returns instantly - it returns true if the user has already typed something, and false if the user hasn't yet typed something - what is false now could be true later.

    That's.. just not how that function works. If hasNext() returns false, that's it. The way that API works, that cannot change - no new token means no new token. Not now. Not next minute. Not next year. Hence, why that method does not return at all - the user might type more.

    What you want, presumably, is to just check for !cmd.equals("exit"). The reason 'swapping them around' works is that the && operator 'short circuits': If evaluating the left hand side gives a definitive answer, the right hand side isn't evaluated at all, so, if !cmd.equals("exit") evaluates to false (because cmd is equal to "exit"), scanner.hasNext() isn't evaluated. That's a good thing, because evaluating scanner.hasNext() hangs your app.

    Attempting to detect CTRL+D/CTRL+Z is kinda tricky; generally if you're writing an app designed to be an interactive command line thing, just detect 'exit' - i.e. just remove your hasNext(), it doesn't do what yo think it does and serves no purpose here.