1. 概述

Scanner 类是 Java 5 引入的一个实用工具类,位于 java.util 包中,常用于解析基本类型和字符串,底层支持正则表达式匹配。它在处理控制台输入、文件解析等场景中非常常见。

本文重点剖析 hasNext()hasNextLine() 两个方法。初看之下它们似乎功能相近,实则行为差异显著,稍不注意就会踩坑 ❌。理解它们的区别,能帮你避免不少“读不到数据”或“多读一行”的诡异问题。

想全面了解 Scanner 的更多用法,可以参考我们之前的 Java Scanner 快速指南

2. hasNext()

2.1 基本用法

hasNext() 方法用于判断当前 Scanner 是否还有下一个有效 token(词元)。

⚠️ 关键点:Scanner 默认使用空白字符(whitespace)作为分隔符(delimiter)来切分输入流。这里的空白字符不仅包括空格,还包括制表符 \t、换行符 \n,甚至其他 Unicode 空白字符(详见 Character.isWhitespace())。

✅ 简单说:hasNext() 只要能从输入中“抠”出一个非空白的片段,就返回 true

另外需要注意:

  • 连续的空白字符被视为一个分隔符
  • 输入末尾的空行不会产生 token,hasNext() 会返回 false

来看一个例子。我们构造一段多行文本:

String INPUT = new StringBuilder()
    .append("magic\tproject\n")
    .append("     database: oracle\n")
    .append("dependencies:\n")
    .append("spring:foo:bar\n")
    .append("\n")  // 注意:输入以一个空行结尾
    .toString();

使用默认分隔符解析:

Scanner scanner = new Scanner(INPUT);
while (scanner.hasNext()) {
    log.info(scanner.next());
}
log.info("--------OUTPUT--END---------");

输出结果:

[DEMO]magic
[DEMO]project
[DEMO]database:
[DEMO]oracle
[DEMO]dependencies:
[DEMO]spring:foo:bar
[DEMO]--------OUTPUT--END---------

可以看到,空白被忽略,每行的非空部分都被正确切分成了独立 token。

2.2 自定义分隔符

Scanner 提供了 useDelimiter(String pattern) 方法,允许我们自定义分隔符。一旦设置,hasNext()next() 就会基于新规则进行判断和读取。

假设我们想提取 dependencies: 后面的依赖项,比如 springfoobar,可以这样做:

Scanner scanner = new Scanner(INPUT);
while (scanner.hasNext()) {
    String token = scanner.next();
    if ("dependencies:".equals(token)) {
        scanner.useDelimiter(":");
    }
    log.info(token);
}
log.info("--------OUTPUT--END---------");

输出:

[DEMO]magic
[DEMO]project
[DEMO]database:
[DEMO]oracle
[DEMO]dependencies:
[DEMO]
spring
[DEMO]foo
[DEMO]bar


[DEMO]--------OUTPUT--END---------

问题来了 ❌:

  • spring 前面多了一个空行(其实是换行符 \n
  • 最后多了两个空行

原因很简单:当我们把分隔符改成 : 时,之前的换行符 \n 不再是分隔符,变成了 token 的一部分!所以 next() 读到了一个包含 \n 的字符串,导致输出错乱。

2.3 使用正则表达式作为分隔符

解决上面问题的思路很直接:让分隔符同时匹配 :任意空白字符

我们可以传入一个正则表达式:

scanner.useDelimiter(":|\\s+");

这个正则的意思是:匹配一个冒号 : 或者一个及以上的空白字符(\s+)。

修改后输出变为:

[DEMO]magic
[DEMO]project
[DEMO]database:
[DEMO]oracle
[DEMO]dependencies:
[DEMO]spring
[DEMO]foo
[DEMO]bar
[DEMO]--------OUTPUT--END---------

✅ 完美!现在 dependencies 后的各项被干净地切分出来,换行和空格都不再干扰。

3. hasNextLine()

hasNext() 不同,hasNextLine() 的关注点是(line)。

✅ 它判断的是输入中是否还有下一行,无论这一行是空还是非空

我们还是用之前的 INPUT 字符串,这次用 hasNextLine() 配合 nextLine() 来逐行读取,并加上行号:

Scanner scanner = new Scanner(INPUT);
int i = 0;
while (scanner.hasNextLine()) {
    log.info(String.format("%d|%s", ++i, scanner.nextLine()));
}
log.info("--------OUTPUT--END---------");

输出:

[DEMO]1|magic    project
[DEMO]2|     database: oracle
[DEMO]3|dependencies:
[DEMO]4|spring:foo:bar
[DEMO]5|
[DEMO]--------OUTPUT--END---------

可以看到:

  • 每行原始内容被完整保留(包括空格和制表符)
  • 第5行是空行,但依然被读取并输出

⚠️ 这正是 hasNextLine()hasNext() 的核心区别:前者按行读,保留格式;后者按 token 读,忽略空白。

4. 总结

方法 判断依据 是否忽略空白 典型用途
hasNext() 是否还有下一个 token ✅ 是 解析字段、提取非空值
hasNextLine() 是否还有下一行 ❌ 否 逐行处理文本、保留原始格式

简单粗暴记法:

  • 要“切词”用 hasNext() + next()
  • 要“读行”用 hasNextLine() + nextLine()

混用时尤其注意分隔符的影响,避免因换行符处理不当导致数据错乱。

完整示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-scanner


原始标题:Java Scanner hasNext() vs. hasNextLine()