Java中Scanner与System.in的正确关闭姿势与常见误区解析
在Java控制台程序中,我们经常会使用Scanner配合System.in来读取用户输入的内容。很多初学者在使用过程中会遇到一个问题:关闭Scanner之后,再次想要读取输入就会报错。今天我们就来详细聊聊这两者的关闭逻辑,以及常见的使用误区。
Scanner与System.in的关系
首先明确一个核心概念:System.in是Java标准输入流,它是一个静态的InputStream对象,属于JVM级别的资源,并不是Scanner私有的资源。Scanner只是一个封装了输入流的工具类,我们可以通过Scanner scanner = new Scanner(System.in)的方式,把标准输入流交给Scanner来管理读取逻辑。
错误关闭方式示例
很多新手会写出下面这样的代码,在读取完一次输入后就直接关闭Scanner:
import java.util.Scanner;
public class WrongCloseDemo {
public static void main(String[] args) {
// 创建Scanner绑定System.in
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个内容:");
String firstInput = scanner.nextLine();
System.out.println("你输入的第一个内容是:" + firstInput);
// 错误操作:直接关闭Scanner
scanner.close();
// 尝试再次创建Scanner读取输入
Scanner newScanner = new Scanner(System.in);
System.out.println("请输入第二个内容:");
String secondInput = newScanner.nextLine(); // 这里会抛出异常
System.out.println("你输入的第二个内容是:" + secondInput);
newScanner.close();
}
}运行上面这段代码,在输入第一个内容之后,程序会抛出java.util.NoSuchElementException异常,提示没有可用的输入流。这是因为当我们调用scanner.close()的时候,Scanner的关闭逻辑会同步关闭它绑定的System.in流。而System.in是全局唯一的,一旦被关闭,后续再创建的Scanner绑定到System.in上,就会发现流已经不可用,自然无法读取输入。
正确关闭姿势
针对不同场景,我们有两种正确的处理方式:
场景1:程序生命周期内只需要读取一次输入
如果整个程序只需要读取一次用户输入,那么可以在使用完Scanner之后关闭它,因为此时程序也快结束了,关闭System.in不会影响其他逻辑:
import java.util.Scanner;
public class CorrectCloseDemo1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的名字:");
String name = scanner.nextLine();
System.out.println("你好," + name);
// 程序即将结束,关闭Scanner同时关闭System.in,没有影响
scanner.close();
}
}场景2:程序生命周期内需要多次读取输入
如果程序需要多次读取用户输入,比如循环读取用户的操作指令,那么永远不要关闭绑定了System.in的Scanner。Scanner本身只是工具类,不关闭它也不会造成严重的资源泄漏,因为System.in是JVM管理的标准流,程序结束的时候JVM会自动回收相关资源。
import java.util.Scanner;
public class CorrectCloseDemo2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean isRunning = true;
while (isRunning) {
System.out.println("请输入操作指令(输入exit退出):");
String command = scanner.nextLine();
if ("exit".equals(command)) {
isRunning = false;
System.out.println("程序即将退出");
} else {
System.out.println("你输入的指令是:" + command);
}
}
// 这里不需要调用scanner.close(),避免关闭System.in
// 程序结束JVM会自动处理System.in的回收
}
}常见误区总结
- 误区1:认为
Scanner是独立资源,关闭它不会影响System.in。实际上关闭绑定System.in的Scanner会同步关闭标准输入流。 - 误区2:每次创建
Scanner都对应关闭一次。如果多次创建绑定System.in的Scanner,只要关闭其中一个,其他的都会失效。 - 误区3:担心不关闭
Scanner会造成内存泄漏。对于绑定System.in的Scanner,不关闭是完全安全的,JVM会在程序结束时统一回收相关资源。
特殊情况:Scanner绑定其他输入流
如果你的Scanner不是绑定System.in,而是绑定了其他自定义的文件流、网络流,那么使用完之后一定要关闭Scanner,因为这类流是程序自己申请的资源,不会由JVM自动回收,不及时关闭会造成资源泄漏:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class OtherStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
Scanner scanner = null;
try {
fis = new FileInputStream("test.txt");
scanner = new Scanner(fis);
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 绑定其他流的Scanner使用完必须关闭
if (scanner != null) {
scanner.close(); // 这里关闭会同步关闭绑定的fis流
}
}
}
}总结一下:处理Scanner和System.in的时候,记住“一次创建,全程使用,不主动关闭”的原则,就能避免大部分相关的异常问题。如果是其他输入流绑定的Scanner,则一定要在使用完成后及时关闭。