Reputation: 7607
Why isn't my inputScanner blocking after the first input? It goes into a continous loop. Ignore other details of this code.
public class Test {
public static void main(String[] args) {
boolean finished;
do {
Scanner inputScanner = new Scanner(System.in);
finished = inputScanner.hasNext("exit");
boolean validNumber = inputScanner.hasNextDouble();
if (validNumber) {
double number = inputScanner.nextDouble();
System.out.print(number);
} else if (!finished) {
System.out.println("Please try again.");
}
inputScanner.close();
} while (!finished);
}
}
EDIT: On a previous post which was related to this, it was mentioned that "So if you are going use System.in later don't close it (if it is closed, we can't reopen it and read any data from it, hence exception)". Why is this happening?
Upvotes: 1
Views: 2332
Reputation: 124235
If you take a look at documentation of Scanners methods like hasNext(String)
or hasNextDouble
you will see that it
Throws:
IllegalStateException - if this scanner is closed
(emphasis mine)
So to throw IllegalStateException
you first need to close Scanner, not stream from which it is reading data.
So lets take a look at this example:
Scanner sc = new Scanner(System.in);
System.out.println("type something:");
System.out.println(sc.hasNext());// true
System.out.println("your data: "+ sc.nextLine());
sc.close();
System.out.println("type something:");
System.out.println(sc.hasNext());// throws java.lang.IllegalStateException: Scanner closed
Last line throws IllegalStateException
because you are invoking hasNext
method on closed scanner (so we know that after invoking sc.close()
stream from which it reads must be also closed so we can safely assume that there are no more elements to read, or since stream was closed we may not be allowed to read it).
Now if we don't close scanner but close System.in
we will still be able to use this instance of scanner without getting exceptions. So lets simply change sc.close();
to System.in.close()
(I will skip exceptions handling for simplicity):
Scanner sc = new Scanner(System.in);
System.out.println("type something:");
System.out.println(sc.hasNext());// true
System.out.println("your data: "+ sc.nextLine());
System.in.close();
System.out.println("type something:");
System.out.println(sc.hasNext());// false
As you can see there is no exception here because it wasn't scanner which was closed, but stream which scanner which was being read.
Why closing System.in
doesn't cause scanner to throw exception?
I suspect that decision to not throw exception here was made with assumption that exception symbolize problem with code. If programmer allowed scanner to being closed he should also make sure that this particular closed instance of scanner will not be used anywhere.
Now returning false
instead of throwing exception is normal reaction where there is no more elements to read. So if stream which scanner was reading was closed naturally (like when we read text file and read its last line so there is nothing more to read) scanner handles this situation like something normal (so there is no need to point that this is some exceptional situation).
Now in your loop you are kind of combining these two scenarios. Your code can be simplified to something like:
Scanner sc = new Scanner(System.in);
System.out.println("type something:");
System.out.println(sc.hasNext());// true
System.out.println("your data: "+ sc.nextLine());
System.in.close();
sc = new Scanner(System.in);//IMPORTANT
System.out.println("type something:");
System.out.println(sc.hasNext());// false
As you see in line
sc = new Scanner(System.in);//IMPORTANT
you are creating new instance of scanner which wasn't closed yet, so its hasXYZ
methods always returns false
because System.in
can't provide no more values.
Additional trap
One problem which I didn't mentioned earlier is fact that in case of wrong input, which is neither "exit"
nor double
if you are are not consuming that invalid cached value from scanner by using any of nextXZY
methods like hasNext("exit")
or hasNextDouble
will be still based on that invalid data, like:
Scanner sc = new Scanner("foo 1");
System.out.println(sc.hasNextInt());//false because `foo` is not integer
System.out.println(sc.hasNextInt());//also false because we are still
//reading `foo` which is not integer
String str = sc.next();//lets read (sonsume) foo
System.out.println(sc.hasNextInt());//true since 1 is integer
Solution
Simplest solution to such problem is creating only one instance of Scanner
which will handle System.in
and reuse it in your entire application. Then at the end of your application you can decide to close your scanner or System.in
.
So your code can look like:
boolean finished;
Scanner inputScanner = new Scanner(System.in);
do {
finished = inputScanner.hasNext("exit");
boolean validNumber = inputScanner.hasNextDouble();
if (validNumber) {
double number = inputScanner.nextDouble();
System.out.print(number);
} else if (!finished) {
System.out.println("Please try again.");
inputScanner.next();// lets not forget to consume from scanner cached
// invalid data which is neither double or "exit"
}
} while (!finished);
inputScanner.close();
Upvotes: 2
Reputation: 11153
Why isn't my inputScanner blocking after the first input?
Because you're creating a new Scanner each time you enter the loop, so it's not the same object on 1st iteration than in 2nd and further iterations
public class Test {
public static void main(String[] args) {
boolean finished;
do {
Scanner inputScanner = new Scanner(System.in); //Here you're making a new instance of inputScanner each time you come to this line after the do-while loop ends.
finished = inputScanner.hasNext("exit");
boolean validNumber = inputScanner.hasNextDouble();
if (validNumber) {
double number = inputScanner.nextDouble();
System.out.print(number);
} else if (!finished) {
System.out.println("Please try again.");
}
inputScanner.close();
} while (!finished);
}
}
If you want it to be "blocked" or "closed", then move this line before the do {
line.
Scanner inputScanner = new Scanner(System.in);
For your second question:
So if you are going use System.in later don't close it (if it is closed, we can't reopen it and read any data from it, hence exception)
From Oracle's docs:
"Attempting to perform search operations after a scanner has been closed will result in an IllegalStateException"
It's like trying to make a dead person to do something, it would be like a zombie! (And Java hates zombies!) D:
But you're not getting that IllegalStateException
because as I said on the answer for your 1st question, you're making a new object each time you go into the do-while
loop.
Why can't you reopen it? Also from Oracle's docs:
When a Scanner is closed, it will close its input source if the source implements the Closeable interface.
Thus inputScanner.close()
closes System.in.
And because of the general contract for OutputStream's close (with the help of this answer):
public void close() throws IOException
--> Closes this input stream and releases any system resources associated with this stream. The general contract of close is that it closes the input stream. A closed stream cannot perform input operations and cannot be reopened.
Upvotes: 4