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:
后面的依赖项,比如 spring
、foo
、bar
,可以这样做:
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