Should a Scanner only be instantiated only once? if that's the case why so?

时光毁灭记忆、已成空白 提交于 2021-02-07 08:29:29

问题


I know I'm going out on a limb here, but I just can't seem to understand why can't we just create an instance of the Scanner class twice. I'll add an example just in case.

import java.util.Scanner;

public class Nope
{
    public static void main(String[] args)
    {
        System.out.println("What's your name?");
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        
        System.out.println("Welcome " + name + "!");
        scanner.close();
        
        // Now 
        System.out.println("where you do live?");
        Scanner sc = new Scanner(System.in);
        String country = sc.nextLine();
        
        System.out.println("That's a lovely place");
        sc.close();
        
    }
}

And I get a runtime error which looks something like this

What's your name?
Kate
Welcome Kate!
Exception in thread "main" where you do live?
java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
    at Nope.main(Nope.java:17)

I know it doesn't make sense to create a new object again of the same class, encouraging redundancy. But I just think it will clear my mind if I know why, don't you think so too?

What does the machine mean by 'java.util.NoSuchElementException: No line found' and people are saying Scanner ain't cloneable.

PS: I intentionally closed my first scanner and created a new object just to understand the issue.


回答1:


There are actually two separate things going on here.

  1. You should create one Scanner per input source. For example, one Scanner for each distinct input file, one for System.in, one for each distinct socket input stream.

    The reason is (as Chrylis points out) is that various methods of Scanner read ahead on the scanner's input source. If the characters are not consumed by the operation, they are not put back into the input source. Rather they are buffered by the Scanner, and kept for the next Scanner operation to use. So if you have two Scanners trying to read from the same input source, one may steal input intended for the other.

    This is the real reason why opening multiple Scanner objects on System.in is bad. Not the "redundancy" argument that you proposed. There is nothing fundamentally wrong with a bit of redundancy ... especially if it simplifies the application. But scanners competing for input may result in unexpected behavior / bugs.

  2. The second problem is that when you close() a Scanner that also closes the input source.

    In your case that means that you are closing System.in. And then you are creating a second Scanner to read from the (now closed) System.in.

    When you attempt to us a Scanner to read from a closed System.in, that leads to a NoSuchElementException.

So if you hadn't called close() on the first Scanner, your code might have worked, but that would depend on the sequence of operations you made on the first Scanner.


People are saying Scanner ain't cloneable.

They are correct.




回答2:


This answer focuses on the close() operation and why there's no option to read again from System.in if a Scanner instance previously closed it, as the top answer already gave the correct info. Just if curious.


Scanner

When a Scanner is closed, it will close its input source if the source implements the Closeable interface. A Scanner is not safe for multithreaded use without external synchronization.

  • You should create a Scanner instance for each source you want to read from.
  • If you must share the same instance, you should implement the synchronization mechanism, as it's not thread safe.
  • As other answers already pointed, close() is a "dangerous" operation.

System.in close()

Let's suppose System.in was specified as source.

This is the close method from the InputStreamReader

public void close() throws IOException
{
    synchronized (lock)
    {
       // Makes sure all intermediate data is released by the decoder.
       if (decoder != null)
          decoder.reset();
       if (in != null)
          in.close();
       in = null;
       isDone = true;
       decoder = null;
     }
}

The variable that refers to System.in is the one called in.

Two operations are performed on this InputStream (besides the null check):


1. in.close()

This does nothing at all: System.in 's class (InputStream) just leaves an empty implementation of the inherited close() method (from Closeable)

/**
 * Closes this stream. Concrete implementations of this class should free
 * any resources during close. This implementation does nothing.
 *
 */
public void close() throws IOException {
    /* empty */
}

Not even the javadocs hide the truth:

The close method of InputStream does nothing.


2. in = null

This is the real reason why you won't be able to read again from System.in. Setting it to null unables any further read attempts from this source with a new Scanner.


But... why does it throw a NoSuchElementException instead of a NullPointerException?

The initialization of the Scanner doesn't fail in the step of creating its Reader instance. This is because the InputStream is wrapped into a new BufferedInputStream. So the lock Object is not null at the initialization of the Scanner's Reader:

public InputStreamReader(InputStream in) 
{
    super(in);  
    this.in = in;
    ...
}

.

protected Reader(Object lock) 
{
    if (lock == null) {    
        throw new NullPointerException();   
    }
    this.lock = lock;
}

You'll be able to create a second Scanner instance from System.in without any exception being thrown; As the InputStream is wrapped into a new BufferedInputStream instance, the lock Object is not null and passes the filter. But the inner InputStream, System.in, is null indeed, from the moment it was set to null in the previous close() operation:

This is the lock Object in the second initialization of the Scanner for System.in. The Scanner still doesn't know something will go badly, as its initialization was succesfull (due to the wrapped BufferedInputStream) and still believes its InputStream is valid.

But at the first try for reading again from System.in, this happens:

public String nextLine() {
    if (hasNextPattern == linePattern())
        return getCachedResult();
    clearCaches();

    String result = findWithinHorizon(linePattern, 0);
    if (result == null) /* result is null, as there's no source */
       throw new NoSuchElementException("No line found");

     (...)
 }

That's the moment where Scanner notices at last that something is not going well. The result of findWithinHorizon will return null as there's no source from where to find.

As a result of previously setting System.in to null in the close() operation, you get the error when trying to read from the second Scanner instance: NoSuchElementException.




回答3:


You should only create one Scanner per input stream. Among other things, the scanner reads ahead and so will consume more of the input than has actually been returned. (This is how it knows, for example, whether the input hasNextInt() and so on.)

It's entirely sensible to create multiple scanners if you have multiple input streams (such as processing multiple files), but System.in should only have a single scanner using it.




回答4:


It's so simple, You should only create one Scanner per input. The Scanner reads line one by one using nextLine() method. last check the condition hasNext() so you can find out the arguments.

Try This

import java.util.Scanner;

public class Nope{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("What's your name?");
        String name = scanner.nextLine();

        System.out.println("Welcome " + name + "!");
        // Now
        System.out.println("where you do live?");
        String country = scanner.nextLine();

        System.out.println(country +" Is a lovely place");
        if(!scanner.hasNext()){
        scanner.close();
        }
    }
}

output like


What's your name?
xyz
Welcome xyz!
where you do live?
rjk
rjk Is a lovely place



来源:https://stackoverflow.com/questions/65503259/should-a-scanner-only-be-instantiated-only-once-if-thats-the-case-why-so

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!