Scanner 与控制台输入输出 | JavaSE
Scanner 与控制台输入输出
一、学习目标
完成本章后,你应该能够:
- 能够解释程序中的输入(Input)与输出(Output)分别是什么。
- 能够使用
System.out完成基本控制台输出。 - 能够正确导入并创建
Scanner对象。 - 能够使用
nextInt()、nextDouble()、next()、nextLine()获取控制台数据。 - 能够理解 Scanner 的 token(标记)读取模型和默认分隔符。
- 能够解释
nextInt()后调用nextLine()为什么可能立即得到空字符串。 - 能够根据实际需求选择“按 token 读取”还是“按整行读取”。
- 能够发现并定位常见控制台输入错误,而不是把 Scanner 当作需要死记模板的代码。
二、核心知识
2.1 什么是输入与输出
程序处理数据时,通常包含三个基本阶段:
输入
↓
处理
↓
输出
例如一个成绩程序:
用户输入成绩
↓
程序计算和判断
↓
控制台输出结果
因此:
输入(Input)
表示:
数据从程序外部进入程序。
例如:
键盘输入
文件读取
网络请求
数据库查询结果
输出(Output)
表示:
程序把处理结果发送到程序外部。
例如:
控制台打印
写入文件
网络响应
数据库保存
本章先学习最简单的一种:
控制台输入输出
2.2 Java 控制台输出
我们从 HelloWorld 开始就一直使用:
System.out.println("Hello Java");
这里可以拆开理解:
System
↓
out
↓
println(...)
System.out 是 Java 提供的标准输出对象。
初学阶段可以把它理解为:
程序向控制台输出数据的入口。
2.3 print() 与 println()
例如:
System.out.print("Hello");
System.out.print("Java");
输出:
HelloJava
因为:
print()
输出内容后不会主动结束当前行。
而:
System.out.println("Hello");
System.out.println("Java");
输出:
Hello
Java
println() 会在输出之后结束当前行。
因此:
print → 输出,但不换行
println → 输出并结束当前行
2.4 println() 可以输出不同类型的数据
例如:
System.out.println(100);
System.out.println(3.14);
System.out.println('A');
System.out.println(true);
System.out.println("Java");
都可以正常输出。
这是因为 PrintStream 为不同类型提供了对应的输出能力。
初学阶段不需要背它内部所有方法,只需要知道:
System.out.println(...)可以直接输出大多数常见 Java 数据。
2.5 printf() 格式化输出
Java 还提供:
System.out.printf(...)
用于格式化输出。
例如:
String name = "LingXi";
int age = 22;
System.out.printf("姓名:%s,年龄:%d%n", name, age);
常见格式符:
| 格式符 | 常见用途 |
| ------ | ------------ |
| %s | 字符串 |
| %d | 十进制整数 |
| %f | 浮点数 |
| %n | 平台相关换行 |
例如:
double price = 19.9;
System.out.printf("价格:%.2f 元%n", price);
可以控制显示两位小数。
格式化输出属于非常实用的控制台技能,但不需要在本章背完整 Formatter 语法。
2.6 什么是 Scanner
如果程序只会:
自己输出固定数据
交互能力非常有限。
例如:
int age = 18;
年龄已经写死在代码中。
如果希望运行时由用户输入:
请输入年龄:
22
程序就需要读取键盘数据。
Java 提供:
Scanner
帮助我们解析文本输入。
Scanner 位于:
java.util
包中。
完整类名:
java.util.Scanner
2.7 什么是 API
API:
Application Programming Interface
中文通常译作:
应用程序编程接口。
可以简单理解成:
Java 或第三方已经写好的功能,我们按照规定的方式直接调用。
例如:
Scanner
String
System
Random
ArrayList
都是以后不断使用的 Java API。
学习 API 最重要的不是:
背完所有方法。
而是学会:
知道它解决什么问题
↓
知道核心 API
↓
知道如何查官方文档
↓
知道如何组合使用
真正的 Java 开发者不可能把整个 JDK API 全部背下来。
2.8 为什么 Scanner 需要 import
Scanner 的完整名称是:
java.util.Scanner
因此通常需要:
import java.util.Scanner;
告诉编译器:
后面代码中的 Scanner 指的是
java.util.Scanner。
例如:
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
}
}
2.9 为什么 String 和 System 经常不需要 import
例如:
String name = "Java";
System.out.println(name);
我们通常没有写:
import java.lang.String;
import java.lang.System;
因为:
String
System
属于:
java.lang
而 Java 会自动导入 java.lang 中的类型。
所以:
Scanner → java.util.Scanner → 通常需要 import
String → java.lang.String → 不需要显式 import
System → java.lang.System → 不需要显式 import
2.10 创建 Scanner 对象
控制台输入的经典写法:
Scanner sc = new Scanner(System.in);
可以先整体记住:
Scanner sc = new Scanner(System.in);
随着后面学习面向对象,你会进一步理解:
Scanner
sc
new
Scanner(...)
System.in
各自的完整含义。
目前重点理解:
sc
代表一个 Scanner 对象,我们可以使用它读取输入。
而:
System.in
表示 Java 的标准输入。
控制台程序中,它通常与键盘输入连接。
2.11 Scanner 不是直接“读取变量”
例如:
int age = sc.nextInt();
真正的过程可以理解为:
用户在控制台输入文本
↓
Scanner 读取输入
↓
Scanner 尝试把输入解析成 int
↓
返回 int
↓
保存到 age
所以:
sc.nextInt()
本身就是一个会产生结果的表达式。
例如用户输入:
22
nextInt() 会返回:
22
于是:
int age = 22;
三、使用方法
3.1 Scanner 基本使用流程
最基本流程:
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("你的年龄是:" + age);
}
}
可以暂时拆成三步:
第一步:导包
↓
import java.util.Scanner;
第二步:创建 Scanner
↓
Scanner sc = new Scanner(System.in);
第三步:读取数据
↓
sc.nextInt();
3.2 nextInt()
用于把下一个可读取 token 解析为:
int
例如:
System.out.println("请输入年龄:");
int age = sc.nextInt();
输入:
22
得到:
age = 22
如果用户输入:
abc
它不能解析成合法 int,程序就会出现输入类型不匹配的问题。
因此:
用户输入的数据必须符合程序预期的类型。
3.3 nextDouble()
用于读取:
double
例如:
System.out.println("请输入身高:");
double height = sc.nextDouble();
输入:
1.75
程序可以得到:
1.75
例如:
double score = sc.nextDouble();
适合读取:
成绩
身高
体重
价格
等小数数据。
3.4 next()
next() 返回:
String
例如:
System.out.println("请输入用户名:");
String username = sc.next();
如果输入:
LingXi
得到:
LingXi
但是:
next()
默认按照 Scanner 的分隔符读取:
下一个完整 token。
Scanner 默认使用空白字符作为分隔。
所以如果输入:
Shanxi University
调用:
sc.next()
通常只得到:
Shanxi
而不是完整一行。
3.5 什么是 token
这是理解 Scanner 最关键的概念。
假设输入:
LingXi 22 175.5 Java
Scanner 默认会根据空白将输入拆成若干 token:
LingXi
22
175.5
Java
可以想象为:
LingXi | 22 | 175.5 | Java
然后:
sc.next();
读取:
LingXi
sc.nextInt();
读取并解析:
22
sc.nextDouble();
读取并解析:
175.5
3.6 Scanner 默认分隔符
Scanner 默认使用:
whitespace
也就是空白字符作为 token 分隔依据。
例如:
空格
Tab
换行
通常都可以作为 token 之间的分隔。
所以对于:
sc.next();
它真正关心的是:
下一个 token。
而不是:
下一整行。
3.7 nextLine()
与前面的:
next()
nextInt()
nextDouble()
不同:
nextLine()
以:
当前行
作为主要读取单位。
它会返回:
当前扫描位置到这一行结束之间的内容,不包含行尾分隔符。
例如用户输入:
Shanxi University Software Engineering
使用:
String text = sc.nextLine();
可以读取整行:
Shanxi University Software Engineering
所以:
next()
更适合读取一个 token。
而:
nextLine()
适合读取:
完整姓名
学校名称
详细地址
一句话
整行文本
3.8 next() 与 nextLine() 的核心区别
可以总结:
| 方法 | 读取模型 |
| -------------- | ----------------------------- |
| next() | 下一个 token |
| nextInt() | 下一个 token,并解析成 int |
| nextDouble() | 下一个 token,并解析成 double |
| nextLine() | 当前扫描位置到行尾 |
这张表非常重要。
Scanner 很多所谓的:
坑
其实都是:
没有理解 token 与 line 是两套不同的读取方式。
3.9 最经典的问题:nextInt() 后接 nextLine()
观察:
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("请输入姓名:");
String name = sc.nextLine();
System.out.println(name);
用户输入:
18
Ling Xi
很多初学者会发现:
name 好像根本没让我输入
甚至认为:
Scanner 把 nextLine() 跳过去了。
实际上不是。
3.10 为什么 nextLine() 会得到空字符串
假设用户输入:
18↵
其中:
↵
代表按下 Enter 后产生的行结束。
nextInt() 的任务是:
找到并读取下一个整数 token。
所以它读取了:
18
但是当前这一行后面:
没有其他普通文本
当随后执行:
sc.nextLine();
它的任务是:
返回当前位置到本行结束之间剩余的文本。
这一段恰好:
什么都没有
于是返回:
""
然后扫描位置进入下一行。
因此本质不是:
nextLine() 被跳过
而是:
nextLine()成功读取了当前行剩余内容,只是剩余内容恰好为空。
这是一个非常重要的区别。
3.11 解决方案一:主动消费当前行剩余部分
例如:
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt();
sc.nextLine();
System.out.println("请输入姓名:");
String name = sc.nextLine();
System.out.println("姓名:" + name);
这里第一个:
sc.nextLine();
专门用于:
消费
nextInt()后当前行剩余的内容。
接下来第二个:
sc.nextLine();
才真正读取下一行姓名。
3.12 解决方案二:统一按行读取
另一种思路:
尽量统一使用
nextLine()获取一整行文本,再根据需要转换类型。
例如概念上:
String ageText = sc.nextLine();
int age = Integer.parseInt(ageText);
这种方式的优点是:
读取边界更统一
尤其在复杂交互程序中,更容易控制:
整行输入
空格
换行
但是这里又引入了:
字符串 → 数值
的转换。
初学阶段先掌握两种思路即可:
方案一:
token 方法 + 正确处理行尾
方案二:
统一 nextLine + 自己解析
3.13 读取完整个人信息
例如:
import java.util.Scanner;
public class UserInfoDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = sc.nextLine();
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("请输入身高:");
double height = sc.nextDouble();
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("身高:" + height);
}
}
这里因为:
nextLine()
先执行,再执行数值 token 读取,所以没有立即出现前面那个经典问题。
但只要后续又准备读取整行,就必须重新考虑扫描位置。
3.14 使用 print() 制作输入提示
例如:
System.out.print("请输入姓名:");
String name = sc.nextLine();
因为:
print()
不会换行。
控制台效果:
请输入姓名:LingXi
而:
System.out.println("请输入姓名:");
会显示:
请输入姓名:
LingXi
两种都正确,只是交互效果不同。
3.15 使用 printf() 输出结果
例如:
String name = "LingXi";
int age = 22;
double score = 95.567;
System.out.printf(
"姓名:%s,年龄:%d,Java成绩:%.2f%n",
name,
age,
score
);
输出类似:
姓名:LingXi,年龄:22,Java成绩:95.57
因此控制台输出并不是只有:
println
还可以根据需求选择:
print
println
printf
四、原理与进阶
4.1 Scanner 的本质是文本扫描和解析
Scanner 不是:
一个只能读取键盘的特殊工具。
它本质上是一个:
文本扫描器。
它可以根据分隔规则,将输入拆成 token,然后将 token 进一步解析成:
int
long
double
boolean
String
……
所以:
nextInt()
实际上包含两件事情:
找到下一个 token
+
将 token 解析成 int
4.2 nextInt() 并不是直接从键盘拿到一个二进制 int
用户在控制台输入:
123
本质上首先表现为文本输入。
Scanner 找到:
"123"
这个 token。
然后根据整数解析规则将其转换成:
int 123
所以:
控制台输入
和:
Java 内部数值
之间仍然存在:
文本解析
的过程。
4.3 Scanner 读取操作可能等待输入
例如:
int age = sc.nextInt();
程序执行到这里时,如果没有可以读取的数据:
程序会等待用户输入
这是一种正常行为。
因此所谓:
程序“卡住了”
有时候并不是程序死循环,而只是:
正在等待输入。
在控制台程序 Debug 时要能够区分这两种情况。
4.4 nextLine() 与 token API 的不同边界
Scanner 的:
next()
nextInt()
nextDouble()
依赖:
delimiter
寻找 token。
而:
nextLine()
直接处理:
当前行剩余内容
这就是为什么混合两套 API 时,需要特别关注:
扫描位置现在到底在哪里?
以后 Debug Scanner 问题,不要只盯着:
我上一句调用了哪个方法
更应该画出:
输入文本
+
当前扫描位置
4.5 hasNextInt() 可以先判断
Scanner 还提供:
hasNextInt()
用于判断下一个 token 是否可以解释成 int。
例如:
if (sc.hasNextInt()) {
int age = sc.nextInt();
}
它体现一种很重要的程序设计思想:
先检查输入是否符合要求,再进行真正读取。
本章只需要认识这种能力。
完善的非法输入处理会在后续异常、流程控制等章节继续组合学习。
4.6 是否应该关闭 Scanner
Scanner 实现了:
Closeable
因此具有:
close()
方法。
但是当 Scanner 包装的是:
System.in
关闭 Scanner 通常也会关闭底层标准输入。
这意味着后面代码如果仍然希望使用:
System.in
继续读取控制台,就可能出现问题。
因此在一个非常简单、马上结束的控制台程序里:
程序结束后资源会一起结束
而在更复杂程序里,应统一规划输入资源的生命周期,而不是到处创建、关闭多个 Scanner。
初学阶段最重要的是避免这种写法:
每输入一个数据
就 new 一个 Scanner
通常整个控制台程序共享一个 Scanner 就足够了。
五、实践应用
Scanner 适合学习阶段的:
控制台练习
算法测试
简单菜单程序
学生管理系统
小游戏
数据录入工具
例如:
请输入姓名:
LingXi
请输入年龄:
22
请输入 Java 成绩:
95.5
可以让原来写死的数据:
String name = "LingXi";
int age = 22;
变成:
运行时由用户决定
这一步非常重要。
因为程序开始从:
静态演示代码
转向:
可交互程序
后续:
if
switch
循环
数组
对象
项目
都可以通过 Scanner 接收真实输入。
六、常见问题
6.1 Scanner 为什么要导包?
因为它位于:
java.util.Scanner
而不是自动导入的:
java.lang
所以通常需要:
import java.util.Scanner;
6.2 System 和 String 为什么不用导包?
因为它们属于:
java.lang
Java 会自动导入该包中的类型。
6.3 next() 能读取带空格的完整姓名吗?
通常不能。
例如:
Ling Xi
使用:
sc.next()
通常首先得到:
Ling
如果希望读取完整一行,应考虑:
nextLine()
6.4 为什么 nextInt() 后的 nextLine() 好像被跳过了?
它并没有被跳过。
nextLine() 会读取:
当前扫描位置到当前行结束之间的内容。
在:
18↵
这种输入中,整数 18 被 nextInt() 读取后,当前行剩余普通文本为空。
因此:
nextLine()
返回:
""
随后移动到下一行。
6.5 nextInt() 输入字母会怎样?
例如程序期待:
int age = sc.nextInt();
用户却输入:
abc
Scanner 无法将它解释为合法 int。
程序会发生输入类型不匹配。
以后学习异常处理后,会系统研究如何处理这类情况。
当前阶段必须建立:
Scanner 不会自动猜测用户真正想输入什么。
6.6 Scanner 可以读取负数吗?
可以。
例如:
-20
只要符合对应数值解析规则:
int value = sc.nextInt();
就可以读取。
6.7 Scanner 可以读取小数吗?
可以,例如:
double value = sc.nextDouble();
但需要注意:
数字格式还可能受到 Scanner locale 等配置影响。
本课程基础阶段默认按照普通开发环境中的常规数字格式学习,不在这里展开国际化解析。
6.8 System.out.print() 和 println() 有什么区别?
核心:
print()
→ 输出后不主动结束当前行
println()
→ 输出后结束当前行
6.9 输入一句地址应该用什么?
例如:
山西省 太原市 小店区 某某街道
包含空格时,更适合:
nextLine()
而不是单纯使用:
next()
6.10 一个程序应该创建很多 Scanner 吗?
一般没有必要。
例如整个程序只需要读取:
姓名
年龄
成绩
地址
可以共享:
Scanner sc = new Scanner(System.in);
统一完成读取。
七、练习与验收
7.1 知识问答
- 程序中的输入和输出分别是什么意思?
System.in与System.out分别承担什么角色?print()和println()有什么区别?printf()解决什么问题?- Scanner 属于哪个包?
- 为什么使用 Scanner 通常需要
import? - 为什么 String 和 System 不需要显式导包?
- 什么是 API?
- 为什么学习 Java API 不等于背完全部 API?
- 创建 Scanner 的常见代码是什么?
System.in在 Scanner 构造代码中表示什么?nextInt()解决什么问题?nextDouble()解决什么问题?next()和nextLine()有什么本质区别?- 什么叫 Scanner 的 token?
- Scanner 默认使用什么类型的数据作为 token 分隔依据?
- 为什么
nextInt()后直接调用nextLine()容易出现空字符串? - 如何解决
nextInt()与nextLine()混合读取问题? - 为什么 Scanner 读取操作有时会让程序“停在那里”?
hasNextInt()可以解决什么问题?
7.2 代码阅读
不要运行:
import java.util.Scanner;
public class ScannerRead01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String a = sc.next();
String b = sc.next();
System.out.println(a);
System.out.println(b);
}
}
假设输入:
Shanxi University
回答:
a得到什么?b得到什么?- 为什么不是由
a一次得到整行?
继续分析:
import java.util.Scanner;
public class ScannerRead02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int age = sc.nextInt();
String text = sc.nextLine();
System.out.println(age);
System.out.println("[" + text + "]");
}
}
输入:
18
要求:
- 预测
age。 - 预测
text的内容。 - 画出
nextInt()之后扫描位置的大致变化。 - 解释为什么第二次读取并不是“没有执行”。
7.3 手写代码
- 编写程序读取姓名、年龄、身高和 Java 成绩,并格式化输出。
- 编写程序读取两个整数并输出它们的和、差、积、商和余数。
- 使用
nextLine()读取一段包含空格的完整地址。 - 编写程序依次读取整数年龄和完整姓名,正确处理
nextInt()与nextLine()的衔接。 - 使用
printf()输出一张简单学生信息卡片。 - 分别使用
next()与nextLine()读取"Shanxi University",观察两者的区别。
7.4 Debug
下面代码出现了经典 Scanner 问题:
import java.util.Scanner;
public class ScannerDebug01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println("请输入完整姓名:");
String name = sc.nextLine();
System.out.println("年龄:" + age);
System.out.println("姓名:" + name);
}
}
要求:
- 说明用户输入
18并按 Enter 后,Scanner 当前处于什么位置。 - 解释为什么
name可能得到空字符串。 - 修复程序,使完整姓名可以正常输入。
- 不允许只背“多写一个 nextLine”,必须解释这个
nextLine()为什么存在。
继续分析:
import java.util.Scanner;
public class ScannerDebug02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt();
System.out.println(age);
}
}
如果用户输入:
hello
要求:
- 分析 Scanner 为什么不能继续得到一个合法
int。 - 判断问题来自 Java 类型系统还是用户输入数据格式。
- 思考如何在读取前判断下一个 token 是否能够作为整数读取。
7.5 综合训练
开发一个简单的“学生信息采集器”。
程序需要读取:
姓名
年龄
学校
专业
身高
Java 成绩
个人简介
要求:
- 姓名允许包含空格。
- 学校名称允许包含空格。
- 个人简介必须能够输入完整一行。
- 年龄使用整数。
- 身高和 Java 成绩使用小数。
- 最终使用
printf()或清晰的println()排版完整信息。
完成后必须自己回答:
- 哪些字段使用
nextLine()? - 哪些字段使用
nextInt()/nextDouble()? - 哪些地方可能产生 token 与 line 混合读取问题?
- 你是如何解决的?
- 如果用户把年龄输入成
"twenty",当前程序会发生什么?
7.6 本章验收
关闭资料后确认:
- [ ] 能解释输入、处理、输出三阶段。
- [ ] 能区分
System.in与System.out。 - [ ] 能使用
print()、println()和基础printf()。 - [ ] 能从零手写 Scanner 导包与创建代码。
- [ ] 能使用
nextInt()和nextDouble()。 - [ ] 能解释
next()与nextLine()的区别。 - [ ] 能用自己的话解释 token。
- [ ] 能解释 Scanner 默认为什么会把空格视为分隔。
- [ ] 能完整解释
nextInt()后nextLine()返回空字符串的原因。 - [ ] 能独立修复 token 与 line 混用问题。
- [ ] 能读取包含空格的姓名、学校或地址。
- [ ] 不需要依靠复制固定模板才能完成一个控制台输入程序。
如果目前只会背:
Scanner sc = new Scanner(System.in);
却无法解释:
System.in 是什么
next() 和 nextLine() 有什么区别
Scanner 当前读到了哪里
那么这一章仍然只是“会抄代码”,还没有真正掌握。