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