1. 现实问题:为什么输入的 2 有时会变成 23
想做一个控制台购物小程序:用户输入商品名、单价和数量,程序计算小计。表面上只有一条乘法,真正写时却会碰到三个问题:键盘输入最初是什么类型,数字该放进哪种变量,+ 到底是在相加还是在拼文字。
例如,文字 "2" + "3" 的结果是 "23",整数 2 + 3 的结果才是 5。程序不会猜你的意思,它只按照类型和运算符规则执行。类型决定一份数据能做什么,变量只是给这份数据起名字。
这一课先把基础规则学扎实,不提前要求你掌握异常、BigDecimal、记录模式或复杂分层。金额在真实项目里会用 BigDecimal,但那是第 8 课的内容;本课先用 double 练习输入和运算,并明确它不适合最终的财务计算。
2. 最小可运行示例:先让输入真正参与计算
把下面内容保存为 ConsoleOrder.java,编译后运行:
import java.util.Scanner;
public class ConsoleOrder {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("商品名:");
String name = scanner.nextLine();
System.out.print("单价:");
double unitPrice = scanner.nextDouble();
System.out.print("数量:");
int quantity = scanner.nextInt();
double total = unitPrice * quantity;
System.out.println(name + " × " + quantity + " = " + total);
}
}
商品名:Java 入门书
单价:39.9
数量:2
Java 入门书 × 2 = 79.8
先不要急着加校验。第一次运行只观察四件事:name 保存文字,unitPrice 保存小数,quantity 保存整数,乘法产生新的 total。如果这四个变量的类型还说不清,后面的判断和业务对象都会变成死记硬背。
3. 语法拆解:变量、八种基本类型和表达式
变量声明的完整结构是:
数据类型 变量名 = 初始值;
int quantity = 2;
double unitPrice = 39.9;
boolean paid = false;
char level = 'A';
等号右边先计算,再把结果交给左边变量。= 是赋值,不是数学里的“相等”;比较相等要写 ==。
Java 有八种基本类型:
| 类型 | 常见用途 | 示例 | 这一课怎么记 |
|---|---|---|---|
byte | 很小范围整数、原始字节 | byte level = 10; | 先认识,不作为普通计数默认选择 |
short | 较小范围整数 | short year = 2026; | 先认识,业务代码较少直接使用 |
int | 普通整数和计数 | int quantity = 2; | 整数默认优先想到它 |
long | 更大范围整数、编号、毫秒值 | long id = 9_000_000_000L; | 大整数末尾常加 L |
float | 单精度小数 | float rate = 0.5F; | 末尾加 F,业务代码较少默认使用 |
double | 普通小数计算 | double price = 39.9; | 本课用它练习,不用于最终财务金额 |
char | 单个字符 | char grade = 'A'; | 单引号,只能放一个字符 |
boolean | 真或假 | boolean valid = true; | 只能是 true 或 false |
String 不是第九种基本类型,它是引用类型,用双引号表示文字。此时只需要会声明和拼接:
String name = "Java 入门书";
System.out.println("商品:" + name);
算术、比较和逻辑运算
int a = 7;
int b = 3;
System.out.println(a + b); // 10
System.out.println(a - b); // 4
System.out.println(a * b); // 21
System.out.println(a / b); // 2,两个 int 相除仍是 int
System.out.println(a % b); // 1,余数
System.out.println(a > b); // true
System.out.println(a == b); // false
System.out.println(a > 0 && b > 0); // true
整数除法会丢掉小数部分。想得到 2.333...,至少让一边成为小数:
double result = (double) a / b;
这里的 (double) 是显式类型转换。它先把 a 当作 double,然后进行小数除法。反方向把 double 转成 int 可能丢失小数,所以 Java 要求你明确写出转换并承担结果。
+ 为什么一会儿相加、一会儿拼接
System.out.println(10 + 20 + "元"); // 30元
System.out.println("合计" + 10 + 20); // 合计1020
表达式从左到右计算。第一行先得到整数 30,遇到字符串后再拼接;第二行一开始就是字符串,后面的数字都转成文字继续拼。看不清时加括号,不要靠背输出答案。
4. Scanner 调用链:键盘字符怎样进入变量
Scanner scanner = new Scanner(System.in); 可以拆成三步:
System.in代表标准输入,提供从键盘进入的字节。new Scanner(System.in)创建一个 Scanner 对象,负责按规则切分和解析输入。scanner保存这个对象的引用,后面通过它调用nextInt()、nextDouble()或nextLine()。
一次 int quantity = scanner.nextInt(); 的完整变化是:
键盘按下 2 和回车
→ Scanner 缓冲区收到字符 "2\n"
→ nextInt() 读取数字 token 并解析
→ 得到 int 值 2
→ 赋值给 quantity
各读取方法不是同一个意思:
| 方法 | 读取到哪里 | 常见用途 |
|---|---|---|
nextInt() | 读取下一个整数 token | 数量、年龄、菜单编号 |
nextDouble() | 读取下一个小数 token | 本课的小数练习 |
next() | 读取到空格前 | 不含空格的单词 |
nextLine() | 读取到本行结束 | 商品名、完整句子 |
最常见的坑是先 nextInt(),再 nextLine():整数读走了,回车还留在缓冲区,下一次 nextLine() 可能只读走这个回车,于是得到空字符串。
int quantity = scanner.nextInt();
scanner.nextLine(); // 只消费上一行剩下的回车
String remark = scanner.nextLine();
这不是 Scanner “抽风”,而是两个方法的结束规则不同。排查输入问题时,先写出缓冲区还剩哪些字符,比反复换方法更可靠。
5. 为什么这样设计:类型是编译器替你守住的边界
如果把数量写成 String quantity = "2",它可以显示,却不能直接参与可靠的数量比较;如果把是否付款写成 String paid = "yes",程序还要猜 yes、YES、1 是否代表同一件事。换成 int 和 boolean 后,错误输入会在转换边界暴露,后面的代码只处理明确值。
基本类型变量直接保存简单值,String、Scanner 等引用变量保存对象的引用。现阶段不需要背 JVM 内存图,只要能回答:变量里是整数值、真假值,还是指向一个对象的引用。
自动类型提升也来自安全边界:小范围整数参与计算时常先提升为 int;把大范围或小数结果塞回小类型,编译器会要求显式转换。编译器阻止的不是“写法不漂亮”,而是可能静默丢数据的操作。
6. 项目落点:今天的控制台输入以后会变成 HTTP 参数
控制台里的 Scanner 以后会被 Spring MVC 的参数绑定替代,但数据路线没有变:
键盘文字 → Scanner → int/double/String
HTTP 文字或 JSON → Spring 参数绑定 → Integer/BigDecimal/String
边界层负责把外部文字变成 Java 类型,业务层再计算和判断。不要把 Scanner 传进 Service,也不要让 Repository 接收一堆尚未解析的原始文字。这样输入方式从控制台换成网页时,业务计算仍能复用。
真实金额将在第 8 课换成 BigDecimal,因为 double 使用二进制近似表达小数,0.1 + 0.2 可能不是精确的 0.3。现在知道这个边界即可,不需要提前背 setScale 和舍入模式。
7. 易错排查与动手练习
遇到问题按“输入 → 类型 → 运算 → 输出”的顺序查:
- 输入姓名只有第一个词:用了
next(),需要整行时改用nextLine()。 nextLine()立刻得到空字符串:前面的nextInt()留下了回车,先消费一次剩余行。7 / 3得到2:两边都是整数;需要小数时把一边转换成double。- 输出变成
合计1020:字符串过早参与+,用括号先完成数值加法。 - 大整数编译失败:超出
int范围时使用long,整数字面量末尾加L。 - 小数转整数编译失败:这是可能丢精度的窄化转换,必须明确写
(int)并说明为什么允许丢失。
请独立完成一个“旅行费用计算器”:读取目的地、每天住宿费、天数和交通费,输出总费用。要求至少使用 String、double、int、乘法和加法。然后再加三组实验:
- 把
7 / 3和(double) 7 / 3同时输出,解释差异。 - 输出
"总计:" + 10 + 20与"总计:" + (10 + 20),解释计算顺序。 - 先读取天数,再读取带空格备注,复现并修复换行问题。
练习完成的标准不是“最后数字对了”,而是你能指着每个变量说出它的类型、值从哪里来、参与了哪个表达式、结果交给了谁。
8. 一页复习
- 声明变量:
类型 变量名 = 初始值;。 - 八种基本类型中,普通整数先想到
int,大整数用long,真假用boolean,单字符用char。 String是引用类型;双引号表示字符串,单引号表示单个字符。=是赋值,==是比较;比较和逻辑表达式的结果是boolean。- 两个
int相除仍是整数;需要小数结果时让至少一边成为double。 +只要遇到字符串就进入拼接;复杂表达式用括号明确顺序。Scanner是对象;nextInt、nextDouble、next、nextLine的结束规则不同。- 学完这一课,你应当能从键盘输入一路追到变量、表达式和最终输出,而不是只记住一个完整示例。
本课按「JavaBaseDay02 的运算符与 Scanner 范围;正文和示例为原创重写」的学习范围组织,正文与示例均为本站原创整理。