控制台输入输出
本教程共 100 篇 · 第 17 篇 · 更新于 2026-08-05 · 约 17 分钟阅读
17. 控制台输入输出
本节目标:学会在控制台打印内容,并用
Scanner从键盘读入用户输入,躲开next与nextLine混淆、以及读数字抛异常的坑。
为什么要专门学输入输出
程序不是自言自语的黑盒。你得让它把结果说出来,也得让它听懂你说什么。控制台就是初学阶段最省事的「嘴」和「耳朵」——不用装数据库、不用起服务器,一个类就能跑。
更实际的理由:调试。代码跑歪了,最快的定位手段就是在关键位置打一行值出来看看。这招老掉牙,但至今没人能离开它。
先说输出
输出你已经用了无数次:System.out.println("...")。它的意思是把内容打到控制台,并换行。
System.out.println("你好");
System.out.println("世界");
```java
两行会分别占一行。如果不想换行,用 `print`:
```java
System.out.print("你好");
System.out.print("世界"); // 输出:你好世界(同一行)
要一次拼出多个值,用 + 连接:System.out.println("总和=" + sum);。字符串和别的类型相加时,会自动把对方也转成字符串再拼。
还有个 printf,能按格式占位输出,像模板填空:
String name = "小明";
int age = 18;
System.out.printf("我叫%s,今年%d岁%n", name, age);
// 输出:我叫小明,今年18岁
```bash
`%s` 填字符串,`%d` 填整数,`%n` 是换行。格式化输出在后面讲字符串格式化时会专门展开,这里先知道有这么个用法。
> [!NOTE]
> 想在 `print` 里手动换行,可以在字符串里写 `\n`:`System.out.print("第一行\n第二行");` 会打出两行。`println` 本质上就是「打印内容 + 自动补一个换行」。所以 `print("hi\n")` 和 `println("hi")` 效果一样。
### 三种输出方法怎么选
| 方法 | 是否换行 | 参数形式 | 什么时候用 |
|------|----------|----------|------------|
| `println(x)` | 自动换行 | 一个值 | 绝大多数情况,打一行看一行 |
| `print(x)` | 不换行 | 一个值 | 想在同一行拼接输出,比如循环打星号 |
| `printf(fmt, ...)` | 看格式串里有没有 `%n` | 格式串 + 多个值 | 要对齐、要控小数位、要一次填多个值 |
一条经验:能用 `println` 说清楚的,就别上 `printf`。等到要打表格、要保留两位小数,`printf` 才真正省事。
### System.out 到底是什么
这一长串看着像咒语,拆开其实很直白:`System` 是 Java 自带的一个类,`out` 是它的一个静态字段,类型是「打印流」。`println` 就是 `out` 这个对象上的方法。
初学不用深究,只要记住「想往控制台输出,就写 `System.out.println(...)`」这个固定套路。等你学了 `static` 和对象,回头看这行会豁然开朗。
### 错误信息走 System.err
除了 `System.out`,还有个 `System.err`,专门打错误信息。
```java
System.out.println("这是正常输出");
System.err.println("这是错误输出");
两者都往控制台打,但它们是两条独立的流。在 IDE 里 System.err 通常显示成红色;在命令行里可以分别重定向到不同文件:
java Demo > out.txt 2> err.txt
```bash
正常结果进 `out.txt`,错误信息进 `err.txt`,互不干扰。这就是分两条流的价值。
> [!TIP]
> 两条流的刷新时机不一样,混着打时输出顺序可能和你写的顺序对不上。调试时统一用 `System.out`,别一会 out 一会 err,省得自己被顺序绕晕。
## 读取键盘输入:Scanner
要在控制台读用户输入,最常用的是 `java.util.Scanner` 类。先创建它,绑定到「标准输入」(键盘):
```java
import java.util.Scanner;
public class InputDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入姓名:");
String name = sc.next();
System.out.println("你输入的是:" + name);
sc.close(); // 用完关闭
}
}
逐行看这段:
import java.util.Scanner;告诉编译器去哪找Scanner这个类。new Scanner(System.in)造一个扫描器,System.in就代表键盘。这句固定这么写。System.out.print("请输入姓名:")用print不换行,光标停在冒号后面,看起来像在等你输入。sc.next()阻塞在这儿,直到你敲了内容并按回车。sc.close()收尾,释放资源。
漏掉 import 这一行,编译器立刻翻脸:
InputDemo.java:5: error: cannot find symbol
Scanner sc = new Scanner(System.in);
^
symbol: class Scanner
location: class InputDemo
```bash
`cannot find symbol` 加 `symbol: class Scanner`,意思就是「我不认识 Scanner」。九成是漏了导入,剩下一成是名字拼错了大小写。
> [!NOTE]
> IDE 通常会自动帮你补上导入。但用记事本 + 命令行练手时没人替你补,看到这条报错先回头检查文件头几行。
## next 和 nextLine 的区别
读取字符串有两个方法,行为不一样,这是头号坑。
`next()`:**遇到空格或回车就停**,只拿空格前的内容。它读不到带空格的字符串。
`nextLine()`:**遇到回车才停**,回车前所有内容(包括空格)都拿走。
```java
Scanner sc = new Scanner(System.in);
System.out.print("next 方式:");
if (sc.hasNext()) {
String s1 = sc.next();
System.out.println("得到:" + s1); // 输入 "hello world" 只拿到 hello
}
System.out.print("nextLine 方式:");
if (sc.hasNextLine()) {
String s2 = sc.nextLine();
System.out.println("得到:" + s2); // 输入 "hello world" 拿到整句
}
sc.close();
想读一整句话(可能带空格),用 nextLine();只想读一个单词,用 next()。
吞行问题的完整现场
「吞行」是这一章最值得记住的坑。先看它长什么样:
import java.util.Scanner;
public class SwallowDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("年龄:");
int age = sc.nextInt(); // 只吃走数字,回车留在缓冲区
System.out.print("住址:");
String addr = sc.nextLine(); // 一上来就撞到那个回车,直接返回空串
System.out.println("年龄=" + age + ",住址=[" + addr + "]");
sc.close();
}
}
```bash
输入 `20` 回车,程序连问都不问就结束了,打出 `年龄=20,住址=[]`。
原因不神秘:你敲的其实是三个字符 `2`、`0`、换行。`nextInt()` 只取走 `20`,那个换行还躺在缓冲区里。轮到 `nextLine()` 时,它一看第一个字符就是换行,判定「这行到头了」,于是返回空字符串走人。
`next()` 后面接 `nextLine()` 也是同一个病根。
### 两种解法
**解法一:中间多吃一次回车。**
```java
int age = sc.nextInt();
sc.nextLine(); // 专门用来吃掉残留的回车,返回值丢弃
String addr = sc.nextLine(); // 这下能正常读到了
解法二:全程只用 nextLine,需要数字就自己转。
System.out.print("年龄:");
int age = Integer.parseInt(sc.nextLine().trim()); // 整行读进来再转成 int
System.out.print("住址:");
String addr = sc.nextLine();
```bash
解法二更稳,因为它从头到尾只有一种读取方式,不存在「谁吃了谁没吃」的问题。`trim()` 顺手去掉两端空格,防止用户手滑多打一个空格。
> [!WARNING]
> 别在同一段代码里混用 `nextInt()`/`next()` 和 `nextLine()`。混用是绝大多数「读不到内容」「程序不等我输入就跑完了」的根源。要么全程 `nextLine()`,要么每次读完非整行内容后补一次 `sc.nextLine()`。
## 读取数字
读整数、小数同样用 `Scanner`,但有个稳妥写法:先判断、再读取。
```java
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个整数:");
if (sc.hasNextInt()) { // 先判断是不是整数
int n = sc.nextInt(); // 再读取
System.out.println("你输入了:" + n);
} else {
System.out.println("那不是整数");
}
sc.close();
hasNextInt() 先确认输入真的是整数,才用 nextInt() 取。类型对应:hasNextDouble() 配 nextDouble(),依此类推。
不先判断会怎样?用户敲个 abc,程序当场崩:
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:964)
at java.base/java.util.Scanner.next(Scanner.java:1619)
at java.base/java.util.Scanner.nextInt(Scanner.java:2284)
at InputDemo.main(InputDemo.java:8)
```bash
`InputMismatchException` 直译就是「输入类型对不上」。看到它,去检查 `nextXxx()` 那一行要的类型和用户实际敲的东西是否匹配。
还有个近亲:输入已经读完了还继续读,会抛 `NoSuchElementException`。命令行里按 Ctrl+D(Windows 是 Ctrl+Z 再回车)表示输入结束,之后再调 `nextLine()` 就是这个报错。
## 读多个数字求平均
把上面拼起来,写个实用的小例子:连续读入若干数字,输入非数字就结束,最后报总和与平均值。
```java
import java.util.Scanner;
public class AverageDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double sum = 0;
int count = 0;
System.out.println("请输入数字,输入非数字结束:");
while (sc.hasNextDouble()) {
double x = sc.nextDouble();
sum += x;
count++;
}
if (count > 0) {
System.out.println(count + " 个数的和是 " + sum);
System.out.println("平均值是 " + (sum / count));
}
sc.close();
}
}
while (sc.hasNextDouble()) 会一直读,直到你输入一个不是数字的东西(比如敲个字母回车)才停下来。
Scanner 常用方法速查
| 方法 | 读到什么 | 配套判断方法 |
|---|---|---|
next() | 一个「单词」,空格或回车就停 | hasNext() |
nextLine() | 一整行,回车才停,结果不含回车 | hasNextLine() |
nextInt() | 一个 int 整数 | hasNextInt() |
nextLong() | 一个 long 整数 | hasNextLong() |
nextDouble() | 一个 double 小数 | hasNextDouble() |
nextBoolean() | true 或 false | hasNextBoolean() |
规律很整齐:nextXxx() 负责读,hasNextXxx() 负责先探路。养成「探一下再读」的习惯,程序就不会被一次手滑输入干掉。
用完记得关闭
Scanner 用完后调 sc.close() 关掉,释放资源。这是个好习惯。前面每段示例结尾都写了 sc.close()。
Warning关掉包裹
System.in的 Scanner,等于把标准输入一起关了。之后再new一个新 Scanner 也读不到东西,直接抛NoSuchElementException。所以一个程序里通常只建一个 Scanner,在 main 的最后统一关闭,别在每个方法里各建各关。
Tip在正式项目里,更现代的做法是用
try-with-resources让 Scanner 自动关闭,写到第 67 章讲异常处理时你会见到。初学阶段手动close()就够了。
printf 的占位符速查
printf 里那些 %x 叫格式符,常用的几个:
| 格式符 | 作用 | 例子 |
|---|---|---|
%s | 字符串 | printf("%s", "hi") |
%d | 十进制整数 | printf("%d", 10) |
%f | 小数 | printf("%f", 3.14) |
%n | 换行 | 跨平台通用 |
%c | 单个字符 | printf("%c", 'A') |
%f 还能控小数位,比如 %.2f 保留两位小数:printf("%.2f", 3.14159) 输出 3.14。格式化输出在字符串章节会更系统地讲。
一次读入一行里的多个整数
有时用户会在一行里敲好几个数,用空格隔开。这时可以配合 hasNextInt 循环读:
Scanner sc = new Scanner(System.in);
System.out.println("请输入若干整数,用空格分开:");
int sum = 0;
while (sc.hasNextInt()) {
sum += sc.nextInt();
}
System.out.println("总和:" + sum);
sc.close();
```bash
只要还在同一行、且敲的是整数,就一直累加;敲个字母或回车结束,就报总和。这比「每行一个数」更贴近真实交互。
## 中文输入乱码怎么办
如果你在控制台输入中文,读出来变成乱码,多半是编码不一致。控制台按系统编码传字符,而你的源文件是 UTF-8,两边对不上就会乱。
创建 Scanner 时显式指定字符集:
```java
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
Scanner sc = new Scanner(System.in, StandardCharsets.UTF_8);
Scanner(InputStream, Charset) 这个构造方法是 Java 10 加的,用常量而不是字符串,拼错了编译期就报错,比较踏实。
老代码里常见另一种写法:
// 历史兼容,不推荐:字符集用字符串传,拼错要到运行时才炸
Scanner sc = new Scanner(System.in, "UTF-8");
```bash
功能一样,但字符串写错(比如打成 `"UTF8 "` 多个空格)编译器不管,运行时才抛 `IllegalArgumentException`。新代码统一用 `StandardCharsets` 常量。
> [!NOTE]
> Windows 命令行默认可能是 GBK。除了改代码,也可以在命令行先执行 `chcp 65001` 把控制台切到 UTF-8。两边口径一致,乱码自然就没了。
## Java 25 LTS 的新写法:IO.println 与 IO.readln
Java 25 LTS 正式引入了 `java.lang.IO` 类(JEP 512),专门简化控制台交互。它在 `java.lang` 包下,不用写 import 就能直接用:
```java
public class HelloIO {
public static void main(String[] args) {
String name = IO.readln("请输入姓名:"); // 打印提示并读一行
IO.println("你好," + name);
}
}
对比一下老套路,能省掉不少仪式感的代码:
| 需求 | 传统写法 | Java 25 写法 |
|---|---|---|
| 打印一行 | System.out.println(x) | IO.println(x) |
| 打印不换行 | System.out.print(x) | IO.print(x) |
| 读一行 | new Scanner(System.in).nextLine() | IO.readln() |
| 带提示读一行 | 先 print 提示再 nextLine() | IO.readln("提示:") |
IO.readln() 永远返回字符串,要数字得自己转,和前面说的「全程 nextLine」策略是一个思路。
Note
IO只提供最基础的读写,功能远不如Scanner丰富(没有hasNextInt这类探路方法)。它的定位是让初学者和写小脚本的人少敲几个字。本教程后续示例仍以System.out和Scanner为主,因为它们在 Java 8 到 25 各版本上都能跑。
小结
输出用 System.out.println(换行)、print(不换行)、printf(格式化占位)。错误信息可以走 System.err,方便单独重定向。
读键盘用 Scanner,先 new Scanner(System.in)。读字符串 next() 遇空格停、nextLine() 遇回车停,两者混用会踩吞行坑。
读数字先 hasNextXxx() 判断再 nextXxx() 读取,避免 InputMismatchException。用完 sc.close() 关闭,且整个程序只关一次。
常见疑问
问:print 里写 \n 和 printf 里写 %n 有区别吗?
有。\n 固定是换行符 LF,%n 会按当前操作系统给出对应的行分隔符(Windows 上是 CRLF)。只在控制台看,两者没差别;要把内容写进文件给别的程序读,用 %n 更稳妥。
问:为什么我明明输入了内容,程序却好像没反应?
先确认 Scanner 是不是在等一个它要不到的东西。比如你调了 nextInt(),却输入了 12ab,Scanner 会一直卡着等合法整数。另一种情况是漏了按回车——Scanner 是按行缓冲的,不敲回车它收不到任何字符。
问:一定要用 Scanner 吗?有别的读输入方式吗?
有。BufferedReader 配 InputStreamReader 读大量数据更快,Java 25 还多了 IO.readln()。但 Scanner 胜在会自动切分单词、自动转类型、还带 hasNextXxx 探路,学习阶段最顺手。等到第 87 章讲字符流时会见到 BufferedReader 的写法。