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↵

这种输入中,整数 18nextInt() 读取后,当前行剩余普通文本为空。

因此:

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 知识问答

  1. 程序中的输入和输出分别是什么意思?
  2. System.inSystem.out 分别承担什么角色?
  3. print()println() 有什么区别?
  4. printf() 解决什么问题?
  5. Scanner 属于哪个包?
  6. 为什么使用 Scanner 通常需要 import
  7. 为什么 String 和 System 不需要显式导包?
  8. 什么是 API?
  9. 为什么学习 Java API 不等于背完全部 API?
  10. 创建 Scanner 的常见代码是什么?
  11. System.in 在 Scanner 构造代码中表示什么?
  12. nextInt() 解决什么问题?
  13. nextDouble() 解决什么问题?
  14. next()nextLine() 有什么本质区别?
  15. 什么叫 Scanner 的 token?
  16. Scanner 默认使用什么类型的数据作为 token 分隔依据?
  17. 为什么 nextInt() 后直接调用 nextLine() 容易出现空字符串?
  18. 如何解决 nextInt()nextLine() 混合读取问题?
  19. 为什么 Scanner 读取操作有时会让程序“停在那里”?
  20. 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

回答:

  1. a 得到什么?
  2. b 得到什么?
  3. 为什么不是由 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

要求:

  1. 预测 age
  2. 预测 text 的内容。
  3. 画出 nextInt() 之后扫描位置的大致变化。
  4. 解释为什么第二次读取并不是“没有执行”。

7.3 手写代码

  1. 编写程序读取姓名、年龄、身高和 Java 成绩,并格式化输出。
  2. 编写程序读取两个整数并输出它们的和、差、积、商和余数。
  3. 使用 nextLine() 读取一段包含空格的完整地址。
  4. 编写程序依次读取整数年龄和完整姓名,正确处理 nextInt()nextLine() 的衔接。
  5. 使用 printf() 输出一张简单学生信息卡片。
  6. 分别使用 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);
    }
}

要求:

  1. 说明用户输入 18 并按 Enter 后,Scanner 当前处于什么位置。
  2. 解释为什么 name 可能得到空字符串。
  3. 修复程序,使完整姓名可以正常输入。
  4. 不允许只背“多写一个 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

要求:

  1. 分析 Scanner 为什么不能继续得到一个合法 int
  2. 判断问题来自 Java 类型系统还是用户输入数据格式。
  3. 思考如何在读取前判断下一个 token 是否能够作为整数读取。

7.5 综合训练

开发一个简单的“学生信息采集器”。

程序需要读取:

姓名
年龄
学校
专业
身高
Java 成绩
个人简介

要求:

  • 姓名允许包含空格。
  • 学校名称允许包含空格。
  • 个人简介必须能够输入完整一行。
  • 年龄使用整数。
  • 身高和 Java 成绩使用小数。
  • 最终使用 printf() 或清晰的 println() 排版完整信息。

完成后必须自己回答:

  1. 哪些字段使用 nextLine()
  2. 哪些字段使用 nextInt() / nextDouble()
  3. 哪些地方可能产生 token 与 line 混合读取问题?
  4. 你是如何解决的?
  5. 如果用户把年龄输入成 "twenty",当前程序会发生什么?

7.6 本章验收

关闭资料后确认:

  • [ ] 能解释输入、处理、输出三阶段。
  • [ ] 能区分 System.inSystem.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 当前读到了哪里

那么这一章仍然只是“会抄代码”,还没有真正掌握。