Java从环境到类与对象 · SSH 实操

第一单元 · 终端、变量、输入 · 第 3 / 12 节

用 Scanner 读入并计算

用 Scanner 读取固定的整数/小数/单词输入,代入算术表达式,用比较运算得到 boolean,分清等待输入与程序卡死。

约 30 分钟 · 6 个步骤 · 进度只存本机

把网页放左边,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 源码有效,别把输入/输出块当命令粘贴。

SSH 服务器终端Java 源码程序交互输入输出示例(只读)复制 Java 源码不带文件名;复制命令不带 $ 提示符;输出和输入示例没有复制按钮。
这是教程网页,不是在线运行环境。Java 代码和 SSH 命令都要在你的服务器上执行;网页不会执行代码、不会连接服务器、也不会接收密码。

已完成 0 / 6 步

第 1 步:建 lesson03 目录,写第一版 ScannerDemo.java

先建目录、进入、保护旧文件。目录名和文件名都换了,动作和上一节相同。

SSH 服务器终端bash
$ 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 仍然单独一块,避免吞掉后面的命令:

SSH 服务器终端bash
$ nano ScannerDemo.java

把下面整份源码粘贴进去,按 Ctrl+O、Enter、Ctrl+X 保存退出。

Java 源码ScannerDemo.java
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 运行时。

SSH 服务器终端bash
$ javac -encoding UTF-8 ScannerDemo.java

为了让每次运行结果一致,本节不靠手输,而是用 printf 把两行输入“喂”给程序。printf 是 shell 命令,\n 表示换行;竖线 | 叫管道,把左边命令的输出接到右边命令的标准输入。

SSH 服务器终端bash
$ printf '小安\n18\n' | java ScannerDemo

程序读到的标准输入就是下面的交互输入(示例,只读):

交互输入(只读)input不可复制
小安
18

运行后屏幕上是:

输出示例(只读)output不可复制
请输入姓名:
请输入年龄:
你好,小安,你今年 18 岁。

在终端里手输时,最后一行也要按 Enter,程序才会收到这一行;用 printf 时末尾加 \n 是同样的道理。管道结束(EOF)时 Scanner 通常也能读到最后一个词,但不要依赖这个边界,统一在末尾写 \n 更稳。

java ScannerDemo 单独运行也可以,只是它会停在 next() 那里等你键入;在 SSH 终端里键入 小安 回车、再键入 18 回车,效果一样。但本课程为了可复现,网页给出的运行命令统一带固定输入。

第 3 步:改用输入文件,理解“等待输入”不是卡死

除了管道,还可以把输入存成文件,再用 < 重定向给程序。先保护同名文件,再写入固定内容:

SSH 服务器终端bash
$ 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 等价于你亲手键入这两行,只是不经过键盘。程序从文件得到的标准输入就是下面的交互输入(示例,只读):

交互输入(只读)input不可复制
小安
18

输出和上一步相同:

输出示例(只读)output不可复制
请输入姓名:
请输入年龄:
你好,小安,你今年 18 岁。

现在试着自己解释“卡死”:如果你只运行 java ScannerDemo,终端没有新提示符、光标停在下一行,这不是死机,而是程序在 scanner.next() 或 scanner.nextInt() 处等输入。键入符合类型的内容并回车,它就会继续;不想继续时按 Ctrl+C 取消这次运行。判断方法:看程序是否已经打印了提示语,再看它停在哪一次读取;不要急着重启服务器。

第 4 步:用 nextDouble 读小数并计算

把文件整份替换成下面这份。输入从“姓名/年龄”换成“单价/数量”,用 nextDouble() 读小数、nextInt() 读整数。

SSH 服务器终端bash
$ nano ScannerDemo.java
Java 源码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));
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '12.5\n3\n' | java ScannerDemo

下面是交互输入(示例,只读):

交互输入(只读)input不可复制
12.5
3

输出:

输出示例(只读)output不可复制
请输入单价:
请输入数量:
总价:37.5

nextDouble() 认小数点,本课程环境的 locale 下用 . 作为小数点;locale 是系统的语言/地区设置,会影响小数点是 . 还是 ,。如果你输入 12,5,可能得到 InputMismatchException,先换成 12.5。price * count 是 double * int,结果是 double,所以 37.5 带小数点。括号不是必须的(乘法优先级高于加法),但写上能让“先算总价、再拼接”更清楚。

第 5 步:读两个整数,做和、差、积、商、余数

把文件整份替换成下面这份,读两个整数,把上一节学的算术运算接到输入上。

SSH 服务器终端bash
$ nano ScannerDemo.java
Java 源码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));
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '17\n5\n' | java ScannerDemo

交互输入(示例,只读):

交互输入(只读)input不可复制
17
5

输出:

输出示例(只读)output不可复制
请输入第一个整数:
请输入第二个整数:
和: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。把文件整份替换成:

SSH 服务器终端bash
$ nano ScannerDemo.java
Java 源码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));
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 ScannerDemo.java
$ printf '7\n4\n' | java ScannerDemo

交互输入(示例,只读):

交互输入(只读)input不可复制
7
4

输出:

输出示例(只读)output不可复制
请输入第一个整数:
请输入第二个整数:
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。**

练习

  1. 读两个小数,输出它们的和与平均值。固定输入用 3.5 和 4.5,期望输出 和:8.0、平均:4.0。提示:两次 nextDouble(),平均值除以 2。
  2. 先预测:把第 6 步的固定输入改成 7 和 7,六个比较分别输出 true 还是 false?预测后改 printf 里的数字运行验证。

先自己写、先写预测,再展开答案。

提示 提示

第 1 题用 double first = scanner.nextDouble(); 和 double second = scanner.nextDouble();;两个 double 相加、除以 2,结果仍是 double。第 2 题先想“相等时哪些比较成立”,再注意 >= 和 <= 在相等时都是 true。

参考答案 对答案

第 1 题的一份完整源码:

Java 源码AverageDemo.java
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));
    }
}

交互输入(示例,只读):

交互输入(只读)input不可复制
3.5
4.5

运行输出:

输出示例(只读)output不可复制
请输入第一个小数:
请输入第二个小数:
和: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 Scanner API: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

说明:本站是个人学习整理,不是官方教材;不包含原课程的书本、视频或受版权保护的整本内容。原课程与商标归其权利人。