Scanner 踩坑:java.util.NoSuchElementException

1 问题描述

使用 Scanner 过后,感觉应该像读取文件之后一样将它关闭,所以调用 close() 方法。在下一次需要输入时,再重新创建 Scanner 对象读取输入。好像没什么问题。

import java.util.Scanner;

public class ScannerTest {
    public static void main(String[] args) {
        System.out.println("请输入:");
        new ScannerTest().getInput();
        System.out.println("请再输入:");
        new ScannerTest().getInput();
    }

    void getInput() {
        Scanner input = new Scanner(System.in);
        String str = input.next(); // 报错的地方
        System.out.println(str);
        input.close();
    }
}

然而运行时抛出异常如下。难道同一程序中 Scanner 只能创建一次吗?

image

2 原因分析

结合源码分析,真正的原因在 System.in我们来看 System.in 在 Scanner 对象中的走向。

首先,从 System 类源码中可以知道 System.in 是不可变的静态资源,即只有一份。从源码说明中可以知道,它作为 InputStream 会在使用前被系统自动打开并连接到输入源(如键盘),那也就是说它只能被使用一次,如果被关闭就无法再使用了。

image

然后再看,在通过 new Scanner(System.in) 创建 Scanner 对象时,Scanner 的一个构造器被调用,

image

这个构造器调用了另一个构造器,并传入了 System.in 作为 source 参数。在这一个构造器中,Scanner 对象的成员变量 source 被确定下来。

image

最后,当我们调用 close() 时,sourceclose() 方法会被调用,实际被关闭的就是 System.in 这一静态资源。

image

所以当再一次创建 Scanner 对象,传入的是一个已经被关闭了的 System.in,它此时已经无法读取输入,所以在尝试读取操作时会抛出异常。

3 解决办法

既然找到了原因:input.close() 使 System.in 过早地关闭了,那解决办法自然有了:最后再调用 close()。可以有三种方式:

  1. 在需要时随时创建一个 Scanner 对象传入 System.in,但在使用后不马上调用 close(),而是在所有获取输入操作结束后再调用 close();

  2. 只创建一个 Scanner 对象,将其作为参数传入需要获取输入的方法中,在所有获取输入操作结束后再调用 close()

  3. 创建一个静态 Scanner 对象,当不再需要使用该静态 Scanner 对象获取输入后调用 close()

第三种方式实现起来更加简洁:

public class ScannerTest {
    private static Scanner input = new Scanner(System.in);

    public static void main(String[] args) {
        System.out.println("请输入:");
        System.out.println(input.next());
        System.out.println("请再输入:");
        System.out.println(input.next());
        close();
    }

    public static void close() {
        input.close();
    }
}

image

参考

java.util.NoSuchElementException原因分析以及解决方法

posted @ 2021-08-06 19:03  alterwl  阅读(930)  评论(0编辑  收藏  举报