
在java开发中,scanner类因其便捷性而广泛应用于从各种输入源(如键盘、文件、字符串)读取数据。一个普遍的编程实践是,当完成对某个资源的访问后,应及时关闭该资源以释放系统句柄,避免资源泄露。然而,对于scanner包裹system.in(标准输入流)的情况,这一规则却存在一个重要的例外,并且常常成为初学者困惑的来源。
理解System.in的特殊性,首先要明确资源管理的黄金法则:谁创建资源,谁就负责关闭它。
System.in是一个预先由Java虚拟机(JVM)创建并管理的全局标准输入流。它代表了程序的默认输入源,通常是键盘。作为一个全局且生命周期与JVM进程绑定的资源,System.in不应由应用程序代码随意关闭。一旦System.in被关闭,整个JVM进程将无法再从标准输入读取数据,这将导致后续任何尝试读取标准输入的操作(包括其他Scanner实例)都会抛出NoSuchElementException或类似的错误。
Scanner类在构造时,如果传入的是一个InputStream对象(例如System.in),那么当这个Scanner实例被关闭时,它也会尝试关闭其底层包裹的InputStream。因此,调用new Scanner(System.in).close()实际上会关闭System.in,从而带来上述问题。
一些集成开发环境(IDE)的静态代码分析工具可能会提示未关闭Scanner的警告,这通常是出于对资源泄露的通用防范。但在System.in这种特定场景下,这些警告属于“过度警惕”,因为关闭System.in反而会造成更大的问题。
立即学习“Java免费学习笔记(深入)”;
对于从System.in读取数据的Scanner,最佳实践是创建一个Scanner实例,并在整个应用程序的生命周期中重用它,绝不关闭。
import java.util.Scanner;
public class InputHandler {
// 建议将System.in的Scanner作为静态或单例管理,并在整个应用生命周期中重用
private static final Scanner SYSTEM_IN_SCANNER = new Scanner(System.in);
public static int getIntegerInput(String prompt) {
System.out.print(prompt);
while (!SYSTEM_IN_SCANNER.hasNextInt()) {
System.out.println("无效输入,请输入一个整数。");
SYSTEM_IN_SCANNER.next(); // 消费掉无效输入
System.out.print(prompt);
}
int input = SYSTEM_IN_SCANNER.nextInt();
SYSTEM_IN_SCANNER.nextLine(); // 消费掉nextInt()留下的换行符
return input;
}
public static String getStringInput(String prompt) {
System.out.print(prompt);
return SYSTEM_IN_SCANNER.nextLine();
}
public static void main(String[] args) {
int age = getIntegerInput("请输入您的年龄:");
System.out.println("您的年龄是:" + age);
String name = getStringInput("请输入您的姓名:");
System.out.println("您的姓名是:" + name);
// 应用程序结束,但SYSTEM_IN_SCANNER不应被关闭
// 因为System.in是全局资源,由JVM管理其生命周期
System.out.println("程序运行结束。");
}
}注意事项:
如果Scanner是用来读取文件或其他自定义的InputStream(这些资源是由你的代码创建和拥有的),那么就应该在完成使用后关闭它们。Java 7及更高版本提供了try-with-resources语句,这是处理可关闭资源的最佳方式,它能确保资源在try块结束时(无论正常结束还是因异常结束)自动关闭。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileReaderExample {
public static void readFileContent(String filePath) {
// 对于文件Scanner,使用try-with-resources确保关闭
try (Scanner fileScanner = new Scanner(new File(filePath))) {
System.out.println("文件内容:");
while (fileScanner.hasNextLine()) {
System.out.println(fileScanner.nextLine());
}
} catch (FileNotFoundException e) {
System.err.println("错误:文件未找到 - " + e.getMessage());
} catch (Exception e) {
System.err.println("读取文件时发生未知错误:" + e.getMessage());
}
}
public static void main(String[] args) {
// 假设有一个名为 "data.txt" 的文件
// 为了演示,这里可以先创建一个虚拟文件
// 例如: new File("data.txt").createNewFile();
// 然后写入一些内容
readFileContent("data.txt");
}
}除了资源关闭问题,原始代码中还存在一些可以改进的编程习惯:
原始代码中ms_calc方法末尾调用了main(null),这是一种递归调用main方法的行为。这种做法是错误的,因为它会导致方法调用栈不断增长,最终引发StackOverflowError。如果需要重复执行某段逻辑,正确的做法是使用循环结构(如while或do-while)。
Java社区有约定俗成的命名规范,遵循这些规范可以大大提高代码的可读性和可维护性:
在getUserInt方法中,捕获Exception过于宽泛。当Scanner.nextInt()遇到非整数输入时,会抛出InputMismatchException。捕获更具体的异常可以使代码更健壮、更易于调试。
结合上述最佳实践,以下是优化后的BPM转换器代码示例:
import java.util.InputMismatchException;
import java.util.Scanner;
public class BpmConverter {
// 将System.in的Scanner作为静态成员,确保在整个应用中只创建一次且不关闭
private static final Scanner SYSTEM_IN_SCANNER = new Scanner(System.in);
public static void main(String[] args) {
boolean continueConversion = true;
while (continueConversion) {
msCalc(); // 调用BPM计算方法
System.out.println("\n是否要进行另一次BPM计算?(yes/no)");
String response = SYSTEM_IN_SCANNER.nextLine().trim().toLowerCase();
if (!response.equals("yes")) {
continueConversion = false;
}
}
System.out.println("BPM转换器已退出。感谢使用!");
// 重要:此处不关闭SYSTEM_IN_SCANNER,因为System.in是JVM管理的全局资源。
}
/**
* 计算并显示BPM到毫秒的转换结果。
*/
public static void msCalc() {
System.out.println("请输入您的BPM值:");
int bpm = getUserInt(1, 2300); // 使用共享的Scanner实例获取用户输入
// 应用命名规范,并确保浮点数运算的精度
double msWhole = Math.round((bpm * 100.0) / 6) * 4;
double msHalf = Math.round((bpm * 100.0) / 6) * 2;
int msWholeRounded = (int) msWhole;
int msHalfRounded = (int) msHalf;
System.out.println("____________________________________");
System.out.println();
System.out.println("全音符 : " + msWholeRounded + " ms");
System.out.println();
System.out.println("二分音符 : " + msHalfRounded + " ms");
for (int x = 1; x < 9; x *= 2) { // 使用x *= 2更简洁
double ms = Math.round((60000.00 / x) / bpm);
int y = x * 4;
int msRounded = (int) ms;
System.out.println();
System.out.println("1/" + y + " : " + msRounded + " ms");
}
System.out.println("____________________________________");
System.out.println();
}
/**
* 获取用户输入的整数,并进行范围验证。
*
* @param min 允许的最小值
* @param max 允许的最大值
* @return 用户输入的有效整数
*/
public static int getUserInt(int min, int max) {
int userInput = min - 1; // 初始值确保进入循环
boolean isValidInput = false;
while (!isValidInput || userInput < min || userInput > max) {
try {
userInput = SYSTEM_IN_SCANNER.nextInt();
if (userInput >= min && userInput <= max) {
isValidInput = true;
} else {
System.out.println("输入超出范围。请输入 " + min + " 到 " + max + " 之间的整数。");
}
} catch (InputMismatchException e) { // 捕获更具体的异常
System.out.println("无效输入。请输入一个整数。");
} finally {
SYSTEM_IN_SCANNER.nextLine(); // 消费掉nextInt()留下的换行符
}
}
// System.out.println(userInput); // 根据需求决定是否在此处打印
return userInput;
}
}正确管理Scanner资源,特别是与System.in相关的Scanner,是Java编程中一个重要的细节。核心要点在于:
通过理解并应用这些原则,开发者可以编写出更健壮、更高效、更符合Java最佳实践的代码。
以上就是Java Scanner与System.in:资源关闭的误区与最佳实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号