1

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?

Andrei
  • 5,858
  • 6
  • 29
  • 60
  • 2
    What do you think `inputScanner.close();` does (or to be more precise does to `System.in`)? – Pshemo Nov 05 '15 at 18:52
  • Already looked here http://stackoverflow.com/questions/19950713/scanner-input-validation-in-while-loop and here http://stackoverflow.com/questions/10490344/how-to-get-out-of-while-loop-in-java-with-scanner-method-hasnext-as-condition ? – Ernst Ernst Nov 05 '15 at 18:55
  • Try running the code. And please add an explanation – Andrei Nov 05 '15 at 18:58
  • [Here](http://stackoverflow.com/a/27286893/1816253) is the answer for your EDIT. – Sebastian Osiński Nov 05 '15 at 19:07
  • "Once the underlying native file descriptors are closed, it is not possible to reopen them." Why ? – Andrei Nov 05 '15 at 19:08
  • `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 – Frakcool Nov 05 '15 at 19:15
  • 1
    Are you asking why you can't reopen closed stream? – Pshemo Nov 05 '15 at 19:16
  • Yes, why can't be reopened? Also, after being pointed to another SO post, apparently the native file descriptors cannot be re-opened. Why is that? – Andrei Nov 05 '15 at 19:23
  • 1
    In current state your question may seem a little too broad since you are asking two questions (related, but still different). First one "*Why isn't my inputScanner blocking after the first input*" can be answered with "*because (1) newly creates scanner on which `close` wasn't invoked yet will not throw exception, and since System.in is closed method like `hasNextFoo` invoked on such unclosed scanner will always return false*". But second one "why can't we reopened closed (standard) stream" requires separate answer. So maybe create separate question for it. – Pshemo Nov 05 '15 at 20:58
  • Re-editing the question – Andrei Nov 05 '15 at 21:06
  • You shouldn't change your question that much. Its [original version](https://stackoverflow.com/revisions/33552505/1) was about different problem which was already answered. Avoid updates which invalidate already posted answers. So if your previous question wasn't perfect but was answered simply ask new one: [What is the the best way to ask follow up questions?](http://meta.stackoverflow.com/q/266767/1393766) and rollback this one to version which matched posted answers. You will also increase probability of getting answers since most people observe *new* questions, not *edited* ones. – Pshemo Nov 05 '15 at 21:33

2 Answers2

4

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.

Edit

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.

Community
  • 1
  • 1
Frakcool
  • 10,088
  • 9
  • 41
  • 71
  • Read the comments of the question – Andrei Nov 05 '15 at 19:25
  • Why isn't it possible to reopen the stream? – Andrei Nov 05 '15 at 19:53
  • 1
    Because of Output Stream's [general contract](http://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html#close()) states that. It's like trying to access a zombie stream, you can see "close" as a "kill" function. You can keep it alive until you're 100% sure you won't need it anymore or create a new instance as `inputScanner = new Scanner(System.in);` however I don't recommend this last solution. Still not sure how to explain or give you a concrete answer to your question. But that's how I interpret it. – Frakcool Nov 05 '15 at 20:28
2

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();
Pshemo
  • 113,402
  • 22
  • 170
  • 242
  • 1
    Additional information based on suspicions rather than facts so I posted them as comments: reopening stream may be two side operation which requires acceptence from both parties. Lets say you have client and server. When you are connection to server, you are also getting some unique informations from server like port number on which they will communicate and when client will close its connection to server it can't simply reopen it but it and get same port, but he needs to create new connection (to get new informations since old ones may be give to other client). – Pshemo Nov 05 '15 at 20:24
  • 1
    Other thing is that server may want to intentionally close its stream because it recognized you as some kind of threat so it would be dangerous to simply let any client reopen stream which was closed and read rest of data he wanted even if server wants to prevent it. – Pshemo Nov 05 '15 at 20:26
  • A more complete answer than mine I guess. And the comments right above, didn't thought about those scenarios. +1 – Frakcool Nov 05 '15 at 20:27
  • @Frakcool Like I said, they are just mine suspicions created in the middle of writing that answer. I can't guarantee that they ware main reasons of contract which prevents process to reopen its standard streams. – Pshemo Nov 05 '15 at 20:28
  • However I think they're pretty accurate. My last comment on my own answer was based mostly on suspicions too, since there's really no docs to answer the OP's question (`why can't OutputStream be reopened`) at all. – Frakcool Nov 05 '15 at 20:31
  • I don't code much in other languages or OS than Java + Windows so I can't be sure but I suspect that this is more OS contract/mechanism rather than Java's (but I may be wrong). – Pshemo Nov 05 '15 at 20:32