【问题标题】:Scanner continuous loop扫描仪连续循环
【发布时间】:2016-02-06 18:09:43
【问题描述】:

为什么我的 inputScanner 在第一次输入后没有阻塞?它进入一个连续循环。忽略此代码的其他细节。

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);
    }
}

编辑:在与此相关的上一篇文章中,提到“因此,如果您稍后要使用 System.in,请不要关闭它(如果它已关闭,我们可以'不要重新打开它并从中读取任何数据,因此异常)“。为什么会这样?

【问题讨论】:

  • 你认为inputScanner.close(); 做了什么(或者更准确地说是System.in)?
  • 尝试运行代码。并请添加解释
  • 你在问为什么不能重新打开已关闭的流吗?
  • 在当前状态下,您的问题可能看起来有点过于宽泛,因为您要问两个问题(相关,但仍然不同)。第一个“为什么我的 inputScanner 在第一次输入后没有阻塞”可以用“因为 (1) 新创建的扫描仪在其上尚未调用 close 不会抛出异常,并且由于 System.in 是封闭的方法,如 hasNextFoo 在此类未封闭的扫描仪上调用将始终返回 false"。但是第二个“为什么我们不能重新打开封闭(标准)流”需要单独的答案。所以也许可以为它创建单独的问题。

标签: java


【解决方案1】:

为什么我的 inputScanner 在第一次输入后没有阻塞?

因为您每次进入循环时都会创建一个新的 Scanner,所以第一次迭代与第二次及以后的迭代中的对象不同

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);
    }
}

如果您希望它被“阻止”或“关闭”,请将此行移到 do { 行之前。

Scanner inputScanner = new Scanner(System.in);

第二个问题:

因此,如果您稍后要使用 System.in,请不要关闭它(如果是 关闭,我们无法重新打开它并从中读取任何数据,因此异常)

来自Oracle's docs

"尝试在扫描程序完成后执行搜索操作 关闭将导致 IllegalStateException"

这就像试图让一个死人做某事一样,就像僵尸一样! (Java 讨厌僵尸!) D:

但是您没有得到 IllegalStateException,因为正如我在第一个问题的答案中所说,每次进入 do-while 循环时,您都会创建一个新对象。

编辑

为什么不能重新打开?同样来自 Oracle 的文档:

当一个 Scanner 关闭时,它会关闭它的输入源,如果源 实现 Closeable 接口。

因此inputScanner.close() 关闭System.in.

并且由于 OutputStream 的总合约关闭(在this answer 的帮助下):

public void close() throws IOException --> 关闭此输入流并释放与此流相关的所有系统资源。 close 的一般约定是它关闭输入流。关闭的流无法执行输入操作,并且无法重新打开。

【讨论】:

  • 阅读问题的cmets
  • 为什么无法重新打开直播?
  • 因为输出流的general contract 声明了这一点。这就像试图访问僵尸流一样,您可以将“关闭”视为“杀死”功能。你可以让它保持活力,直到你 100% 确定你不再需要它或创建一个新实例为 inputScanner = new Scanner(System.in); 但我不推荐最后一个解决方案。仍然不知道如何解释或给你一个具体的答案。但这就是我的解释。
【解决方案2】:

如果您查看像 hasNext(String)hasNextDouble 这样的扫描仪方法的文档,您会发现它

投掷:
IllegalStateException - 如果此 扫描仪 已关闭

(强调我的)

所以要抛出IllegalStateException,你首先需要关闭扫描器,而不是它正在读取数据的流。

让我们看一下这个例子:

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

最后一行抛出IllegalStateException,因为您在关闭的扫描仪上调用hasNext方法(所以我们知道在调用它读取的sc.close()流之后也必须关闭,所以我们可以安全地假设没有更多元素读取,或者由于流已关闭,我们可能无法读取它)。

现在,如果我们不关闭扫描仪而是关闭System.in,我们仍然可以使用此扫描仪实例而不会出现异常。所以让我们简单地将sc.close(); 更改为System.in.close()(为简单起见,我将跳过异常处理):

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

如您所见,这里没有例外,因为关闭的不是扫描仪,而是正在读取哪个扫描仪的流。

为什么关闭System.in 不会导致扫描器抛出异常?

我怀疑这里不抛出异常的决定是假设异常象征代码问题。如果程序员允许关闭扫描仪,他还应该确保不会在任何地方使用这个特定的关闭扫描仪实例。
现在返回 false 而不是抛出异常是正常反应,因为没有更多元素要读取。因此,如果扫描仪正在读取的流自然关闭(例如,当我们读取文本文件并读取其最后一行,因此没有更多内容可读取)扫描仪会像正常情况一样处理这种情况(因此无需指出这是一些 特殊情况)。

现在在您的循环中,您可以将这两种情况结合起来。您的代码可以简化为:

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

如你所见

sc = new Scanner(System.in);//IMPORTANT

您正在创建尚未关闭的扫描仪新实例,因此其 hasXYZ 方法始终返回 false,因为 System.in 无法提供更多值。

额外的陷阱

我之前没有提到的一个问题是,如果输入错误,这既不是"exit",也不是double,如果你没有使用任何nextXZY从扫描器中消耗无效的缓存值hasNext("exit")hasNextDouble 之类的方法仍将基于该无效数据,例如:

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

解决方案

此类问题的最简单解决方案是仅创建一个 Scanner 实例,该实例将处理 System.in 并在整个应用程序中重用它。然后在您的应用程序结束时,您可以决定关闭您的扫描仪或System.in

所以你的代码看起来像:

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();

【讨论】:

  • 基于怀疑而非事实的附加信息,因此我将它们发布为 cmets:重新打开流可能是需要双方接受的双向操作。假设您有客户端和服务器。当您连接到服务器时,您还会从服务器获取一些独特的信息,例如它们将通信的端口号,当客户端关闭与服务器的连接时,它不能简单地重新打开它,但它会获得相同的端口,但他需要创建新连接(获取新信息,因为旧信息可能会提供给其他客户端)。
  • 另一件事是服务器可能想要有意关闭其流,因为它认为您是某种威胁,因此简单地让任何客户端重新打开已关闭的流并读取他的其余数据是危险的即使服务器想要阻止它也想要。
  • 我猜的答案比我的更完整。而上面的cmets,并没有考虑到这些情况。 +1
  • @Frakcool 就像我说的,它们只是我在写这个答案的过程中产生的怀疑。我不能保证他们知道合同的主要原因是阻止流程重新打开其标准流。
  • 但是我认为它们非常准确。我对自己答案的最后评论也主要基于怀疑,因为根本没有文档可以回答 OP 的问题 (why can't OutputStream be reopened)。
猜你喜欢
  • 1970-01-01
  • 2012-09-27
  • 2016-01-26
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多