Java多方法用Scanner读System.in抛NoSuchElementException咨询
JDBC控制台程序Scanner报错问题解答
报错根因
你遇到的java.util.NoSuchElementException,本质是业务方法里新建Scanner绑定System.in后,在finally块调用s.close()时,会连带关闭Scanner底层依赖的System.in全局标准输入流。第一个方法执行完流就被关了,后续方法再新建Scanner绑定已经关闭的System.in,读不到任何输入就会直接抛错。你移除各业务方法内的s.close()调用确实能修复问题,更规范的实现是全局只维护一个绑定System.in的Scanner实例复用。
三个疑问的具体解答
1. 是否可以直接忽略Eclipse针对未关闭Scanner给出的内存泄漏警告?
分场景判断:
- 如果Scanner绑定的是程序自行打开的文件流、网络流等非全局资源,必须手动关闭,这类警告不能忽略,否则会造成操作系统层面的文件句柄泄漏。
- 如果Scanner绑定的是System.in,这个流是JVM启动时自动初始化的全局资源,生命周期和整个程序一致,程序退出前不需要手动关闭,提前关闭反而会导致后续输入逻辑失效,这种场景下的警告可以直接忽略。
- 如果想消除警告,可以在程序主类里定义全局唯一的Scanner实例供所有业务方法复用,等所有逻辑执行完、程序即将退出前再调用一次close即可,示例代码如下:
public class Runner { // 全局复用的Scanner实例,绑定System.in public static final Scanner INPUT_SCANNER = new Scanner(System.in); public static void main(String[] args) { CustomerDAO customerDAO = new CustomerDAO("root", "root"); Customer customer = new Customer(); customer.createCustomer(customer); customerDAO.deleteCustomerByID(); customerDAO.updateCustomer(customer); System.out.println(customerDAO.readAll()); // 所有逻辑执行完再统一关闭,消除警告 INPUT_SCANNER.close(); } }
后续所有业务方法直接调用Runner.INPUT_SCANNER读取输入即可,不需要重复创建Scanner实例,也不需要单独写close逻辑。
2. 为什么创建新的Scanner绑定System.in时,无法重新打开已经被关闭的System.in流?
- Scanner的close方法逻辑是:如果自身绑定的底层流实现了Closeable接口,会直接调用底层流的close方法释放资源。System.in本质是JVM对接操作系统控制台输入的特殊文件输入流,对应操作系统全局分配的标准输入文件描述符(类Unix系统为fd 0,Windows为控制台输入句柄)。
- 一旦调用close方法,这个文件描述符会被操作系统直接回收,JVM没有权限、也没有提供接口重新初始化这个全局标准输入流。哪怕新建再多Scanner对象,本质都是往已经被释放的无效流上读数据,自然拿不到输入,直接抛出异常。
3. 调用hasNextLine()方法做输入前置校验,或是通过switch-case实现用户功能选择菜单,是否是该场景下的合理实现方案?
这两种都是控制台交互程序的标准合理实现,但要注意两个常见坑:
- 用hasNextInt()/*hasNextLine()做输入校验时,如果用户输入类型不匹配(比如要求输入数字用户输了字母),必须调用一次nextLine()*把输入缓冲区里残留的非法内容读走,否则会因为缓冲区残留数据导致校验逻辑死循环。
- 调用nextInt()/*nextDouble()这类基本类型读取方法后,缓冲区会残留一个回车换行符,如果后续要调用nextLine()读取字符串,必须先调用一次空的nextLine()*把残留换行吞掉,否则会直接跳过后续的字符串输入步骤。
- 额外提一句:你现在把Scanner输入逻辑写在Customer、CustomerDAO这类实体/DAO类里是不合理的分层设计。DAO层只应该负责数据库CRUD操作,控制台交互、参数读取的逻辑应该抽到专门的控制层,DAO方法直接接收外部传入的参数即可,分层拆分后也不会出现多个方法重复创建Scanner的问题。
内容的提问来源于stack exchange,提问作者AceKokuren
相关产品推荐
相关产品推荐

