第一单元 · 终端、变量、输入 · 第 3 / 12 节
用 Scanner 读入并计算
用 Scanner 读取固定的整数/小数/单词输入,代入算术表达式,用比较运算得到 boolean,分清等待输入与程序卡死。
把网页放左边,SSH 终端放右边。本节继续在终端里写 .java、编译、运行,所有文件都放在独立目录 ~/java-practice/lesson03,不碰 lesson01、lesson02 的目录。
再提醒一次四种块:bash 块是要粘到终端的命令,java 块是源码,input 块是只读的输入示例,output 块是只读的程序输出;复制按钮只对前两种有效。
前两节的程序只会打印写死的内容。这一节让程序从标准输入读数据:程序运行到读入语句时会停下来等你给数据,拿到数据再继续算。这个“停下来等”是正常行为,不是程序卡死;分不清这两件事,是 SSH 新手最常见的慌张来源。
Java 用 Scanner 这个工具读输入,它不在默认的 java.lang 里,所以文件开头要写一行 import java.util.Scanner;。
import 的意思是“我要用 java.util 这个包里叫 Scanner 的类”。这一行必须放在 public class 之前,且只能出现一次。java.util 是 Java 自带的工具包,不需要下载或安装。
读输入的固定写法是 Scanner scanner = new Scanner(System.in);。
System.in 表示标准输入(stdin),也就是终端里键入的内容或管道/文件送进来的内容。new Scanner(System.in) 是创建 Scanner 工具的固定形式;这里的 new 先照写,第四单元才解释“创建对象”的完整含义。后面的 scanner.next()、scanner.nextInt()、scanner.nextDouble() 分别读一个单词、一个整数、一个小数。
网页上的 input 块是只读的,表示“终端里要键入的内容”,不是命令;output 块也是只读的,表示程序会打印什么。复制按钮只对 bash 命令和 java 源码有效,别把输入/输出块当命令粘贴。
$ 提示符;输出和输入示例没有复制按钮。已完成 0 / 6 步
第 1 步:建 lesson03 目录,写第一版 ScannerDemo.java
先建目录、进入、保护旧文件。目录名和文件名都换了,动作和上一节相同。
$ source ~/java-practice/env.sh
$ mkdir -p ~/java-practice/lesson03
$ cd ~/java-practice/lesson03
$ pwd
$ if [ -e ScannerDemo.java ]; then mv ScannerDemo.java "ScannerDemo.java.bak.$(date +%s)"; fi
nano 仍然单独一块,避免吞掉后面的命令:
$ nano ScannerDemo.java
把下面整份源码粘贴进去,按 Ctrl+O、Enter、Ctrl+X 保存退出。
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = scanner.next();
System.out.println("请输入年龄:");
int age = scanner.nextInt();
System.out.println("你好," + name + ",你今年 " + age + " 岁。");
}
}
逐行看:
import java.util.Scanner;:导入 Scanner 类,必须在类声明之前。Scanner scanner = new Scanner(System.in);:创建一个叫scanner的变量,让它从标准输入读数据。String name = scanner.next();:读一个“单词”。next()以空白(空格、Tab、换行)分隔,所以输入小安会读到一个字符串;如果输入小 安,它只会读到小。int age = scanner.nextInt();:读一个整数。如果输入18,得到18;如果输入18.5或十八,会抛InputMismatchException,因为类型对不上。System.out.println("你好," + name + ",你今年 " + age + " 岁。");:把文字和变量拼接后打印。
Scanner 不会自动跳过“类型不对”的内容,也不会帮你把 18.5 截成 18;它要求输入的下一个“词”正好是整数。输入的顺序也必须和代码里的读取顺序一致:代码先读姓名,终端就先给姓名;再读年龄,就给年龄。
第 2 步:编译,用 printf 管道喂固定输入
先编译。javac 只检查源码,不会读你的输入;真正的输入发生在 java 运行时。
$ javac -encoding UTF-8 ScannerDemo.java
为了让每次运行结果一致,本节不靠手输,而是用 printf 把两行输入“喂”给程序。printf 是 shell 命令,\n 表示换行;竖线 | 叫管道,把左边命令的输出接到右边命令的标准输入。
$ printf '小安\n18\n' | java ScannerDemo
程序读到的标准输入就是下面的交互输入(示例,只读):
小安
18
运行后屏幕上是:
请输入姓名:
请输入年龄:
你好,小安,你今年 18 岁。
在终端里手输时,最后一行也要按 Enter,程序才会收到这一行;用 printf 时末尾加 \n 是同样的道理。管道结束(EOF)时 Scanner 通常也能读到最后一个词,但不要依赖这个边界,统一在末尾写 \n 更稳。
java ScannerDemo 单独运行也可以,只是它会停在 next() 那里等你键入;在 SSH 终端里键入 小安 回车、再键入 18 回车,效果一样。但本课程为了可复现,网页给出的运行命令统一带固定输入。
第 3 步:改用输入文件,理解“等待输入”不是卡死
除了管道,还可以把输入存成文件,再用 < 重定向给程序。先保护同名文件,再写入固定内容:
$ if [ -e input.txt ]; then mv input.txt "input.txt.bak.$(date +%s)"; fi
$ printf '小安\n18\n' > input.txt
$ cat input.txt
$ java ScannerDemo < input.txt
> 表示“把输出写进文件”;如果文件已存在,> 会覆盖它,所以上一行先做了存在性检查和改名备份。cat input.txt 只是把文件内容显示出来核对,应该看到两行:小安、18。
< 表示“把文件内容接到程序的标准输入”。java ScannerDemo < input.txt 等价于你亲手键入这两行,只是不经过键盘。程序从文件得到的标准输入就是下面的交互输入(示例,只读):
小安
18
输出和上一步相同:
请输入姓名:
请输入年龄:
你好,小安,你今年 18 岁。
现在试着自己解释“卡死”:如果你只运行 java ScannerDemo,终端没有新提示符、光标停在下一行,这不是死机,而是程序在 scanner.next() 或 scanner.nextInt() 处等输入。键入符合类型的内容并回车,它就会继续;不想继续时按 Ctrl+C 取消这次运行。判断方法:看程序是否已经打印了提示语,再看它停在哪一次读取;不要急着重启服务器。
第 4 步:用 nextDouble 读小数并计算
把文件整份替换成下面这份。输入从“姓名/年龄”换成“单价/数量”,用 nextDouble() 读小数、nextInt() 读整数。
$ nano ScannerDemo.java
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入单价:");
double price = scanner.nextDouble();
System.out.println("请输入数量:");
int count = scanner.nextInt();
System.out.println("总价:" + (price * count));
}
}
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '12.5\n3\n' | java ScannerDemo
下面是交互输入(示例,只读):
12.5
3
输出:
请输入单价:
请输入数量:
总价:37.5
nextDouble() 认小数点,本课程环境的 locale 下用 . 作为小数点;locale 是系统的语言/地区设置,会影响小数点是 . 还是 ,。如果你输入 12,5,可能得到 InputMismatchException,先换成 12.5。price * count 是 double * int,结果是 double,所以 37.5 带小数点。括号不是必须的(乘法优先级高于加法),但写上能让“先算总价、再拼接”更清楚。
第 5 步:读两个整数,做和、差、积、商、余数
把文件整份替换成下面这份,读两个整数,把上一节学的算术运算接到输入上。
$ nano ScannerDemo.java
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个整数:");
int a = scanner.nextInt();
System.out.println("请输入第二个整数:");
int b = scanner.nextInt();
System.out.println("和:" + (a + b));
System.out.println("差:" + (a - b));
System.out.println("积:" + (a * b));
System.out.println("商:" + (a / b));
System.out.println("余数:" + (a % b));
}
}
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '17\n5\n' | java ScannerDemo
交互输入(示例,只读):
17
5
输出:
请输入第一个整数:
请输入第二个整数:
和:22
差:12
积:85
商:3
余数:2
17 / 5 仍是整数除法,得到 3;17 % 5 得到 2。想让商带小数,可以把其中一个数转成 double,例如 (double) a / b,那时输出会变成 3.4。本节先保持整数;下一单元 lesson04 学 if 后再处理“除数为 0 要提示”的逻辑。现在固定输入里不要放 0,否则 a / b 会抛 ArithmeticException: / by zero。
第 6 步:用比较运算得到 boolean
最后把两个数比较一下。比较运算符 >、<、==、!=、>=、<= 的结果不是数字,而是 boolean:true 或 false。把文件整份替换成:
$ nano ScannerDemo.java
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个整数:");
int a = scanner.nextInt();
System.out.println("请输入第二个整数:");
int b = scanner.nextInt();
System.out.println("a > b:" + (a > b));
System.out.println("a < b:" + (a < b));
System.out.println("a == b:" + (a == b));
System.out.println("a != b:" + (a != b));
System.out.println("a >= b:" + (a >= b));
System.out.println("a <= b:" + (a <= b));
}
}
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '7\n4\n' | java ScannerDemo
交互输入(示例,只读):
7
4
输出:
请输入第一个整数:
请输入第二个整数:
a > b:true
a < b:false
a == b:false
a != b:true
a >= b:true
a <= b:false
要点:
a > b读作“a 大于 b”,结果是true或false,可以直接println。a == b是判断相等;a = b是赋值,会把b的值写进a,两者完全不同。a != b是“不相等”;>=是“大于或等于”,<=是“小于或等于”。- 这些
boolean结果下一单元会直接放进if的条件里;本节只打印,不学分叉。
比较运算的两边类型要能比较:int 和 int、double 和 double 都可以;不要把字符串和整数直接用 > 比较,那会编译失败。
常见错误
- **现象:
cannot find symbol,指向Scanner。原因:漏了import java.util.Scanner;,或 import 写在了public class后面。先检查什么:文件第一行(或类声明前)是否有这条 import,拼写和大小写是否完全一致。** - **现象:
java.util.InputMismatchException。原因:nextInt()读到了字母或小数,或nextDouble()读到了12,5这种用逗号的小数。先检查什么:对照代码的读取顺序和input块,确认第一个词是整数、第二个词也是整数;小数用.。** - **现象:程序停住,没有新提示符,看起来“卡死”。原因:程序正在
next()/nextInt()/nextDouble()等标准输入。先检查什么:看它已经打印到哪一句提示;键入符合类型的内容并回车,或改用printf ... | java .../java ... < input.txt。不想继续就按Ctrl+C。** - **现象:
java.util.NoSuchElementException。原因:输入提前结束,程序还要读下一个词却已经没有数据。先检查什么:input块的行数是否够、每行是否只放一个值、printf末尾是否有\n。** - **现象:输出里提示语和结果粘在一行。原因:用了
System.out.print而不是println,或程序没有在预期位置换行。先检查什么:本节的提示语都用println;如果你改成了print,读输入前可能看不到提示,补一个换行即可。** - **现象:
java.lang.ArithmeticException: / by zero。原因:整数除法或取余的除数是0。先检查什么:第二个输入是否为0;本节固定输入用5,不要改成0。**
练习
- 读两个小数,输出它们的和与平均值。固定输入用
3.5和4.5,期望输出和:8.0、平均:4.0。提示:两次nextDouble(),平均值除以2。 - 先预测:把第 6 步的固定输入改成
7和7,六个比较分别输出true还是false?预测后改printf里的数字运行验证。
先自己写、先写预测,再展开答案。
提示 提示
第 1 题用 double first = scanner.nextDouble(); 和 double second = scanner.nextDouble();;两个 double 相加、除以 2,结果仍是 double。第 2 题先想“相等时哪些比较成立”,再注意 >= 和 <= 在相等时都是 true。
参考答案 对答案
第 1 题的一份完整源码:
import java.util.Scanner;
public class AverageDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个小数:");
double first = scanner.nextDouble();
System.out.println("请输入第二个小数:");
double second = scanner.nextDouble();
System.out.println("和:" + (first + second));
System.out.println("平均:" + ((first + second) / 2));
}
}
交互输入(示例,只读):
3.5
4.5
运行输出:
请输入第一个小数:
请输入第二个小数:
和:8.0
平均:4.0
first 和 second 都是 double,所以 (first + second) / 2 做的是小数除法,不会截成整数。
第 2 题:输入 7 和 7 时,a > b 是 false,a < b 是 false,a == b 是 true,a != b 是 false,a >= b 是 true,a <= b 是 true。相等时“大于等于”和“小于等于”都成立,这是 >=、<= 与 >、< 的区别。
做到这里
做到这里今天可以停。你已经会让程序用 Scanner 读单词、整数、小数,能把输入代入算术表达式,能用比较运算得到 boolean,也能分清“程序在等输入”和“程序真的卡死”。
本节没有 if、循环、数组或自定义方法;比较结果只是打印出来,还没有用来做判断。下一单元 lesson04 才学 if 和 switch,那时这些 true/false 才会真正控制程序走哪条路。
来源 来源与改编说明(非官方)
主题参考:黑马程序员 Java 入门到起飞(BV17F411T7Ao,2022-04-27)P026、P030–P031、P034 的键盘录入、算术运算、类型转换、赋值和关系运算符主题。本站为独立原创讲解,非官方站点/教材,未获授权,不逐字复刻视频或讲义;示例、错误清单与练习为原创补充。详见 source/SOURCE.md。
公开核对入口:
- 课程页:https://www.bilibili.com/video/BV17F411T7Ao/
- Oracle JDK 17
ScannerAPI:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Scanner.html - Oracle JDK 17
java:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html
说明:本站是个人学习整理,不是官方教材;不包含原课程的书本、视频或受版权保护的整本内容。原课程与商标归其权利人。