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 只能创建一次吗?

2 原因分析
结合源码分析,真正的原因在 System.in。我们来看 System.in 在 Scanner 对象中的走向。
首先,从 System 类源码中可以知道 System.in 是不可变的静态资源,即只有一份。从源码说明中可以知道,它作为 InputStream 会在使用前被系统自动打开并连接到输入源(如键盘),那也就是说它只能被使用一次,如果被关闭就无法再使用了。

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

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

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

所以当再一次创建 Scanner 对象,传入的是一个已经被关闭了的 System.in,它此时已经无法读取输入,所以在尝试读取操作时会抛出异常。
3 解决办法
既然找到了原因:input.close() 使 System.in 过早地关闭了,那解决办法自然有了:最后再调用 close()。可以有三种方式:
-
在需要时随时创建一个 Scanner 对象传入
System.in,但在使用后不马上调用close(),而是在所有获取输入操作结束后再调用close(); -
只创建一个 Scanner 对象,将其作为参数传入需要获取输入的方法中,在所有获取输入操作结束后再调用
close(); -
创建一个静态 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();
}
}


浙公网安备 33010602011771号