The methods of scanner will block for more input if there is no current token and the source
(in this case System.in
) hasn't reached end-of-stream yet. This will then
result in it waiting for your input.
This is also explicitly mentioned in the documentation of hasNext()
(and the other blocking methods):
This method may block while waiting for input to scan.
This can also be seen in the actual implementation of hasNext()
:
public boolean hasNext() {
ensureOpen();
saveState();
modCount++;
while (!sourceClosed) {
if (hasTokenInBuffer()) {
return revertState(true);
}
readInput();
}
boolean result = hasTokenInBuffer();
return revertState(result);
}
As you can see, it will loop as long as the source isn't closed, and if there is no token in the
buffer, it will call readInput()
to find the next token. This read will block if
there is currently nothing to read from the source at this time, but it hasn't reached
end-of-stream/end-of-file yet.
It seems that many programmers are expecting hasNext()
(and other has..()
methods by class Scanner
) only inspect the availability of input data, whereas the
method also blocks (waiting for input) if the expected data is not available.
I think one of the ways to overcome this limitation is to create your own method to handle the
input e.g. readInput()
such as follows. But to get a line of text as input, simply use
the nextLine()
instead of using the readInput()
.
import java.util.Scanner;
public class ChkInput {
static Scanner input = new Scanner(System.in);
// Swallow all input data each time we read input
public static String[] readInput() {
String in = input.nextLine();
String[] data = in.split("[\s]+");
return data;
}
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("Input a number : ");
String[] data = readInput();
// handle the expected input as nextInt()
int num = Integer.parseInt(data[0]);
System.out.println("Number is " + num);
// If more data are available, do something before next loop
// Here if expecting more data but length == 1, you know data missing.
// so next loop get the data
if (data.length > 1) {
// example: just print them out.
System.out.println("More input data waiting : ");
for (int i = 1; i < data.length; i++) {
System.out.print(data[i] + ", ");
}
System.out.println();
}
}
}
}
UPDATE: The following is an example of implementing the idea mentioned above into a
class named InputBuffer
.
// file: ChkInput.java
import java.util.Scanner;
// InputBuffer : An implementation of using buffer to hold input.
// With better control of hasNext() and flushing (clear data)
//
class InputBuffer {
Scanner input;
String[] buff;
int buffIndex;
// constructor
//----
public InputBuffer(Scanner scannerDev) throws Exception {
if (scannerDev == null) {
throw new Exception("Scanner is not defined");
}
input = scannerDev;
buff = new String[0];
buffIndex = 0;
}
// verify if any data available in buffer.
// if no data just proceed (no blocking).
//----
public boolean hasNext() {
return buffIndex < buff.length;
}
// read data as a token
//----
public String readInput() throws Exception {
if (buffIndex == buff.length) {
String in = input.nextLine().trim();
buff = in.split("[\s]+");
buffIndex = 0;
}
String data = buff[buffIndex];
buffIndex++;
return data;
}
// read data as a line.
// if buffer filled with tokens and a subsequent
// readInputLine() follows a readInput(), all
// tokens rejoined using space (' ')
//----
public String readInputLine() throws Exception {
String data;
if (buffIndex == buff.length) {
data = input.nextLine();
} else {
data = "";
for (int i = buffIndex; i < buff.length; i++) {
data = data + buff[i];
if (i < buff.length) {
data = data + " ";
}
}
}
flush();
return data;
}
// clear remaining data in buffer
//----
public void flush() {
buff = new String[0];
buffIndex = 0;
}
// explicitly close the scanner device
//---
public void close() {
input.close();
}
}
// Test program
public class ChkInput {
public static void main(String[] args) throws Exception {
InputBuffer input = new InputBuffer(new Scanner(System.in));
while (true) {
System.out.println("Enter line of text (x to stop): ");
String name = input.readInputLine();
if (name.equals("x")) {
break;
}
System.out.println("Line = " + name);
System.out.println("Input a number : ");
String data = input.readInput();
// handle the expected input like nextInt();
int num = Integer.parseInt(data);
System.out.println("Number is " + num);
// hasNext() checks if there is another data available, do something before next loop
// if there is no data, return false and proceed to next loop. No blocking (waiting input) by hasNext()
if (input.hasNext()) {
System.out.println("Get the next data : " + input.readInput());
}
input.flush(); // clear all remaining data, start fresh
}
}
}
UPDATE: The example codes had been updated to fix some bugs and adding improvements.