大二上学期 Java 第三课:运算符与键盘输入——从"算得对"到"读得进"
第二课解决了"数据怎么存",第三课解决两件事:存进去的数据怎么运算,以及怎么让程序从键盘读数据进来。这一课看似简单,但输入类的那个经典 bug 能卡住一半人,运算符的坑也全在细节里。
一、运算符的分类:先建立地图
Java 的运算符按功能分五类:算术、赋值、比较、逻辑、位运算(位运算大二上学期一般只要求认识,不深究)。这一课的重点是前三类加上自增自减,逻辑运算符要理解短路特性。
二、算术运算符:整数除法和取模是重灾区
加减乘和数学里一样,真正要盯住的是除法、取模和自增自减。
2.1 整数除法会"丢小数"
两个整数相除,结果仍然是整数,小数部分直接舍弃,不是四舍五入。想得到小数结果,必须让参与运算的至少一方是浮点类型。这个规则在第二课的类型提升基础上理解:整数运算结果还是整数,不会自动变成小数。
经典陷阱:求平均分时,如果总分和人数都是整数,直接相除会丢掉小数。正确做法是把其中一个转成浮点,或者乘以 1.0 再除。
2.2 取模的符号问题
取模(求余数)的结果符号跟被除数一致,和除数无关。这一点和数学里的"余数"直觉不同,负数的取模结果经常让人算错。做循环取余、判断奇偶时问题不大,但一旦涉及负数就要格外小心。
2.3 自增自减:前缀和后缀的区别
自增自减分前缀和后缀两种写法。单独成句时两者完全等价,都是让变量加一或减一。一旦参与表达式,区别就出来了:
· 前缀形式:先自增,再把新值参与运算;
· 后缀形式:先把旧值参与运算,再自增。
课堂最爱的考题就是把自增藏在表达式里,让你算最终结果。大二阶段的建议是:实际写代码时永远不要让自增自减混在复杂表达式里,单独成行写,可读性最高,也不会算错。考试归考试,工程归工程。
三、赋值运算符:复合赋值的隐式转换
复合赋值运算符(加等、减等、乘等、除等、模等)本质是"运算加赋值"的缩写。但有一个隐藏细节:复合赋值自带隐式强制类型转换。
也就是说,两个 byte 相加再赋回 byte,用普通写法会因为自动提升为 int 而报错,但用复合赋值写法就能编译通过,因为编译器在赋值那一步自动帮你转了。这个特性知道即可,不建议依赖它,因为它悄悄吞掉了类型转换,容易埋下溢出隐患。
四、比较运算符:结果一定是布尔值
大于、小于、等于、不等于、大于等于、小于等于,这六种比较的结果永远是布尔值,要么真要么假。
这里有两个和第二课呼应的点:
第一,判断两个值是否相等用双等号,单等号是赋值。初学者把条件里的双等号写成单等号,是编译错误里最常见的一类,好在 Java 会直接报错,不像 C 那样悄悄赋值然后逻辑全错。
第二,浮点数不要直接用双等号比较相等。因为小数在二进制里往往不能精确表示,两个理论上相等的浮点数,实际存储可能有极小误差。判断浮点数相等要用"差的绝对值小于某个极小值"的方式。这个问题在做实验课的计算题时一定会碰到。
五、逻辑运算符:短路特性是重点
逻辑与、逻辑或、逻辑非是三个基本逻辑运算符。大二上学期最该掌握的是"短路"特性:
· 逻辑与:左边为假时,右边不再执行,直接判定整个表达式为假;
· 逻辑或:左边为真时,右边不再执行,直接判定整个表达式为真。
为什么重要? 因为短路能保护代码。比如"数组不为空 且 数组长度大于零"这种判断,如果左边为假,右边就不会去取长度,避免了空指针。反过来,如果把顺序写反,就可能先访问空对象直接崩溃。逻辑运算符的书写顺序,本身就是一种防御性编程,这个意识从第三课就要建立。
另外要区分"逻辑与"和"按位与":前者操作布尔值且有短路,后者操作整数、按位运算、没有短路。考试里经常放在一起混淆。
六、键盘输入:本课最大的坑
6.1 基本流程
Java 从键盘读数据,用的是 JDK 自带的输入类。流程固定三步:先创建输入对象(绑定标准输入流),再调用对应方法读数据(读整数、读小数、读字符串各有各的方法),最后用完关闭。
注意:读整数和读整行字符串用的是不同的方法,这一点是下面那个 bug 的根源。
6.2 经典 bug:先读数字再读整行,整行被"跳过"
这是第三课必讲的坑,几乎每个人都会踩一次。
现象:程序先让你输入一个数字,你敲了数字按回车;接着程序让你输入一行字符串,你还没敲,它就直接跳过了,好像没等你输入一样。
原因:你敲数字时按下的那个回车,被留在了输入缓冲区里。读数字的方法只把数字读走了,回车符还留在缓冲区。接下来读整行的方法一执行,发现缓冲区里已经有一个回车,就把它当成"用户输入了一个空行",直接返回空字符串,于是看起来就像被跳过了。
解决办法(任选其一,理解原理最重要):
· 在读完整行之前,先额外调用一次读整行的方法,把残留的回车"吃掉";
· 或者干脆全程都用读整行的方式,读到之后再自己转成数字;
· 或者在读数字之后、读整行之前,用读整行方法清一次缓冲区。
这个 bug 的本质是"输入缓冲区"的概念,理解了缓冲区,后面学文件读写、网络流都是同一个思路。老师上课如果现场演示这个现象,实验报告里大概率要写原因分析。
6.3 输入的其他注意点
· 读小数的方法对本地化敏感,某些环境下要求用逗号而不是小数点,遇到"输入小数报错"先想到这个;
· 输入的数据类型不匹配(比如让读整数时敲了字母),会抛异常,第三课阶段先知道"会崩",异常处理留到后面讲;
· 输入对象用完要关闭,虽然小程序不关也不影响,但养成习惯。
七、运算符优先级:不必背全表,记住三条
优先级表很长,大二阶段不用全背,记住三条实用规则:
1. 括号优先级最高,拿不准就加括号,可读性也更好;
2. 算术 > 比较 > 逻辑 > 赋值,这个大体顺序要清楚;
3. 自增自减、逻辑非这类一元运算符优先级很高,容易和后面的运算结合错。
工程上的建议很简单:复杂表达式一律加括号。省下的那点打字时间,远不如排查优先级 bug 花的时间多。
八、第三课就该养成的习惯
习惯一:输入之后立刻校验。 读进来的数据可能是用户乱敲的,第三课虽然还不讲异常处理,但要有"输入不可信"的意识,这是后面所有交互程序的基础。
习惯二:不要让自增自减混进表达式。 单独成行,代码清晰,也避免考试和实战两套思维打架。
习惯三:逻辑判断把"最可能为假、最危险"的条件放左边。 利用短路特性做保护,这是第三课就能开始练的防御性编程思维。
九、课后自测(答案在下一课笔记里)
1. 两个整数相除想得到小数结果,有哪几种改法?
2. 逻辑与和按位与,在操作数类型和是否短路这两点上分别有什么区别?
3. 描述一下"先读数字再读整行被跳过"的完整原因,以及至少两种解决办法。
---
下一篇预告:流程控制——分支与循环。会讲 if-else 的悬挂问题、switch 的穿透现象,以及 for 循环里最容易写错的边界条件。第三课输入部分的坑,到第四课做菜单程序时会集中爆发。