0

I want to output a question to the console and then get the next line of input after the question was output.

For example, my program could be sleeping or doing some time-consuming computation, and while the user is waiting they might decide to type some notes into the console (perhaps without hitting enter, or perhaps over several lines). Once the sleep is completed, the program then asks the user a question, "What is your name?" and then it should wait for the next line of input containing the user's name, and ignore any random notes the user made while the sleep was going on.

Here's some code that tries to do that:

public static void main(String[] args) throws InterruptedException {
    Scanner scanner = new Scanner(System.in);
    Thread.sleep(10_000);
    System.out.println("What is your name?");
//  while (scanner.hasNext()) {
//      scanner.nextLine();
//  }
    String name = scanner.nextLine();
    System.out.println("Hi, " + name);
}

This behaves as follows when I type a couple of lines during the sleep:

gpeo
hpotWhat is your name?
Hi, gpeo

The problem is that scanner will read the next input continuing from the last input it read, not from the last System.out.println() (which makes sense). The commented out code tries to rectify that problem by reading past all earlier input first, then waiting on one more line to assign to name. However, scanner.hasNext() does not work as I was hoping, since when there is no next token it does not simply return false but waits for another token (so I don't know why it bothers to return a boolean at all).

Another thing that baffles me is that during the sleep if you type stuff on a single line, that single does in fact get ignored:

brbr irgjojWhat is your name?
A
Hi, A

I thought it was going to output Hi, brbr irgjojA, so that makes me think I might be misunderstanding how console input and Scanner work.

Edit: The last example was from a run within IntelliJ. When I run from my Bash commandline instead I get Hi, brbr irgjojA. The output of the first example does not change though.

Also, I was asked if this question is the same as this, and apparently I have to explain why it's not here or it will appear on the question. The issue in that post (and others like it) is that he/she is mixing scanner.nextLine() with scanner.nextInt() and similar methods that do not read the whole line or the line ending. I am only using nextLine() to read input, and my issue is quite different.

Further edit

I managed to discard the first line of random notes based on this answer to another question. Here is the new code:

public static void main(String[] args) throws InterruptedException, IOException {
    Scanner scanner = new Scanner(System.in);
    Thread.sleep(10_000);
    System.out.println("What is your name?");
    while (System.in.available() > 0) {
        scanner.nextLine();
    }
    String name = scanner.nextLine();
    System.out.println("Hi, " + name);
}

Here are some test runs in IntelliJ:

grgWhat is your name?
A
Hi, A

ghr
rhWhat is your name?
A
Hi, A

rghr
hrh
htWhat is your name?
Hi, hrh

uirh
iw
hjrt
sfWhat is your name?
Hi, iw

And here are similar tests in Bash:

htrWhat is your name?
A
Hi, htrA

rgj
hrWhat is your name?
A
Hi, hrA

rjkh
ry
jWhat is your name?
Hi, ry

ryi
rj
rd
jrWhat is your name?
Hi, rj

As you can see, the line inside the while loop never appears to get executed more than once for some reason. I tried adding a sleep inside the loop or using other InputStream methods like skip() and readAllBytes(), but these didn't seem to help at all.

I think there might not be anything one can do about the incomplete line that is a problem for Bash, but I'm sure there must be a way to throw out all the completed lines (rather than just the first one). The solution doesn't have to use Scanner, it should just behave as intended.

  • Does this answer your question? [Scanner is skipping nextLine() after using next() or nextFoo()?](https://stackoverflow.com/questions/13102045/scanner-is-skipping-nextline-after-using-next-or-nextfoo) – Alex Rudenko Jun 09 '20 at 19:30
  • @AlexRudenko No, the issue there (and with similar questions) is that he/she is using a `Scanner` method (`nextFoo()`) which does not read the whole line with line terminator. Here I am only using the `nextLine()` method for reading. – Hairfridge Jun 09 '20 at 19:41
  • Could not reproduce the issue with _ignored_ input: ```qqqqaaaassswwwwAAAWhat is your name? Hi, qqqqaaaassswwwwAAA``` That is I cannot even print on another line. – Alex Rudenko Jun 09 '20 at 20:46
  • @AlexRudenko If you have a newline after `AAA`, then your output _does_ reproduce the issue in my first example. Otherwise, the behaviour is different, but it's still not the desired behaviour. – Hairfridge Jun 09 '20 at 22:01
  • It seems that you cannot get desired behaviour from `Scanner/System.in` based on streaming IO - that is, they read all the user's input and they're blocking until the stream is closed or until a token (a new line in your case) has been typed. Similar issue is explained [here](https://stackoverflow.com/questions/16873134/hasnext-when-does-it-block-and-why) – Alex Rudenko Jun 10 '20 at 01:41
  • @AlexRudenko Thanks for the link; it led me to another question with somewhat helpful answer. See my latest edit for details on that. Even if the desired behaviour can't be done with `Scanner`, I'd be interested in anything else that can do it. – Hairfridge Jun 10 '20 at 11:26

1 Answers1

1

The Scanner uses a buffer. It’s default size is 1024 characters. So by the first nextLine() call, it reads up to 1024 of the available characters into the buffer. This is necessary, as the Scanner doesn’t even know how many characters belong to the next line, before filling the buffer and searching for a line break in the buffer.

Therefore, if there are less pending characters than the buffer size, the loop will iterate only once. But even when there are more characters, and more loop iterations, the resulting state likely is to have some pending lines in the buffer.

As long as the Scanner’s buffer is in its initial empty state, you can flush the source stream directly, instead of using the scanner:

Scanner scanner = new Scanner(System.in);
Thread.sleep(10_000);
while(System.in.available() > 0) {
    System.in.read(new byte[System.in.available()]);
}
System.out.println("What is your name?");
String name = scanner.nextLine();
System.out.println("Hi, " + name);

Note that it would be natural to use System.in.skip(System.in.available()); instead of read, but while trying it, I encountered a bug that the underlying stream did not update the available() count after a skip when reading from a console.

Note that if the Scanner is not in its initial state but has some buffered content already, there is no way to flush the buffer, as its API is intended to make no distinction between buffered and not yet buffered, so any attempt to match all content would result in reading from the source (and blocking) again. The simplest solution to get rid of the buffered content would be
scanner = new Scanner(System.in);

Holger
  • 243,335
  • 30
  • 362
  • 661
  • Thanks for this great answer! The description was very helpful, and the code solves the main problem (the smaller problem of an incomplete line seems to be out of our control). Sorry it took me a while before I could test this out and get back to you. – Hairfridge Jun 16 '20 at 21:17