面向 Java 零基础阶段的知识梳理,重点讲「为什么」而不是只给结论。
从5 / 2为什么等于 2 说起,逐层拆掉运算符里那些反直觉的地方。
一、整数除法:第一个"算错了"的地方
System.out.println(5 / 2); // 2,不是 2.5
很多人第一次看到这个结果,会以为是打印出了问题。其实不是——5 / 2 在 Java 里真的就等于 2。
原因:5 和 2 都是 int,int / int 的结果还是 int,小数部分被直接截断(不是四舍五入)。
System.out.println(5 / 2); // 2
System.out.println(5 % 2); // 1 ← 余数在这
System.out.println(5 / 2.0); // 2.5 ← 有一个是 double,结果就是 double
System.out.println((double) 5 / 2); // 2.5
想让结果带小数,至少让一个操作数变成浮点类型。 这就是为什么算平均值时经常要写 (double) sum / count,光写 sum / count 会白白丢掉小数。
这个特性在什么时候有用
有时候需要的恰好就是整数除法:
// 1234 秒 = 20 分 34 秒
int minutes = 1234 / 60; // 20
int seconds = 1234 % 60; // 34
/ 和 % 配合,就是整除取商、取余数,这个组合在做分页、进制转换、时间换算时天天用。
二、取余的符号:为什么 -10 % 3 是 -1
System.out.println(10 % 3); // 1
System.out.println(-10 % 3); // -1
System.out.println(10 % -3); // 1
System.out.println(-10 % -3); // -1
规律:取余结果的符号,只看被除数(左边的数),和除数无关。
这个规则跟数学上的"模"不完全一样。数学里 -10 mod 3 通常取 2(保证结果非负),但 Java 的 % 是取余不是取模,符号跟着被除数走。
如果业务上需要"永远是正数"的模,得自己处理:
int r = ((a % b) + b) % b;
一个容易写错的例子
System.out.println(-11.5 % 2.0);
凭直觉容易写成 1.5,实际输出是 -1.5。同一个规则——符号跟被除数。小数也可以取余,只是实际业务里极少用。
三、复合赋值:+= 有一手"暗中强转"
int a = 1;
a += 1; // 等价于 a = a + 1
看起来是语法糖,但在小类型上它的行为不一样:
byte b = 10;
b = b + 1; // ❌ 编译错误:从 int 转换到 byte 可能会有损失
b += 1; // ✅ 完全合法
为什么?因为 复合赋值运算符隐含了一次强制类型转换。JLS 规定 b += 1 等价于:
b = (byte) (b + 1);
编译器替你把这个 (byte) 加上了。所以 +=、-=、*=、/=、%= 都能在小类型上过编译。
但代价是:溢出静默发生,编译器一声不吭。
byte b = 127;
b += 1;
System.out.println(b); // -128
这是运算符里最危险的一个坑——报错的代码会被改掉,不报错的代码可能直接上线。
四、++a 和 a++
int a = 10;
int b = ++a;
System.out.println(a + " " + b); // 11 11
int c = 10;
int d = c++;
System.out.println(c + " " + d); // 11 10
口诀:
++a(前缀):先自增,再取值a++(后缀):先取值,再自增
拆开看:
b = ++a; → a = a + 1; b = a;
d = c++; → d = c; c = c + 1;
共同点:变量自己最终都会加 1。 区别只在"参与运算时用的是加之前还是加之后的值"。
所以单独成句时两者完全等价:
int a = 10;
a++; // a 变 11
++a; // a 变 12
一个反直觉的句子
int i = 1;
i = i++;
System.out.println(i); // 1,不是 2
拆解:i++ 先把旧值 1 交出去参与运算,然后 i 自增变成 2;紧接着赋值把交出去的 1 又写回 i,于是 i 被打回 1。
不用记这个结果,只要记住:永远别写 i = i++。 生产代码里这样写会被 review 打回来。
五、关系运算符:3 < a < 5 为什么编译不过
int a = 3;
System.out.println(3 < a < 5); // ❌ 编译错误
报错说:运算符 '<' 不能应用于 boolean,int。
原因:< 是左结合的,3 < a < 5 实际算的是 (3 < a) < 5:
3 < a得到boolean(false)- 再算
false < 5—— 布尔值和数字比大小,Java 不允许
正确写法:
System.out.println(3 < a && a < 5);
这个坑在 C 语言里更阴险:
3 < a < 5在 C 里合法,会被算成(3<a)<5,也就是0 < 5或1 < 5,永远为真。Java 靠强类型在编译期就拦住了。
关系运算符的返回永远是 boolean,这是它能被 if 直接使用的原因。
六、逻辑运算符与短路
boolean r1 = a > b && a == 10; // 两个都真才真
boolean r2 = a > b || a == 10; // 有一个真就真
boolean r3 = !(a > b); // 取反
&& 和 || 的核心特性是短路:
左边已经能决定结果时,右边根本不执行。
int i = 0;
boolean r = (i > 0) && (i++ > 0); // i>0 是 false,i++ 没执行
System.out.println(i); // 0
int j = 10;
boolean r2 = (j > 0) || (j++ > 0); // j>0 是 true,j++ 没执行
System.out.println(j); // 10
& 和 | 也能用在 boolean 上
这是最容易混淆的地方。&、|、^ 对 boolean 也是合法的,区别是不短路:
int i = 0;
boolean r = (i > 0) & (i++ > 0); // 两边都算
System.out.println(i); // 1
所以这个经典写法必须用 &&:
if (str != null && str.length() > 0) { }
如果写成 &,当 str 是 null 时,str.length() 照样执行,直接空指针。
这就是短路存在的意义:它不只是性能优化,也是正确性保障。
七、位运算符
位运算是对二进制每一位逐个操作。
0101 1011
& 0011 0011 & 按位与:两位都是 1 才得 1
-----------
0001 0011
0101 1011
| 0011 0011 | 按位或:有一个 1 就得 1
-----------
0111 1011
0101 1011
^ 0011 0011 ^ 按位异或:两位不同才得 1
-----------
0110 1000
~ 0101 1011 ~ 按位取反:0变1,1变0
-----------
1010 0100
移位运算:>> 和 >>> 的区别
int x = -8;
System.out.println(x >> 1); // -4
System.out.println(x >>> 1); // 2147483644
>>有符号右移:左边补符号位。负数右移左边补 1,所以结果还是负数>>>无符号右移:左边一律补 0,负数会变成一个大正数
左移一位约等于乘 2,右移一位约等于除 2:
System.out.println(5 << 1); // 10
System.out.println(5 >> 1); // 2 (小数被截断)
**别拿它替代
2*。现代编译器会自动把*2优化成移位,可读性远比这点微优化重要。JDK 源码里用它,是因为那里真的一纳秒都要省。
八、条件运算符 ? :
int max = 10 > 20 ? 10 : 20;
System.out.println(max); // 20
格式:条件 ? 值1 : 值2,条件为真取值1,为假取值2。
它有类型统一的规则:
int r = true ? 1 : "hello"; // ❌ 编译错误,int 和 String 无法统一
Object o = true ? 1 : "hello"; // ✅ 统一为公共父类 Object
为什么? 两个分支的类型必须能合并成一个。int 和 String 没有共同的数值父类,只能提到 Object。
这个规则如果两个分支都是数字,会做数值提升:
System.out.println(true ? 1 : 2.0); // 1.0,不是 1
因为 int 和 double 统一成 double,所以 1 被提升成 1.0。这个行为在看输出格式时容易出意外。
九、优先级速查(记不住就加括号)
从高到低:
() [] . 括号、数组、成员访问
++ -- ! ~ (强转) 一元
* / % 乘除取余
+ - 加减
<< >> >>> 移位
< > <= >= instanceof 关系
== != 相等
& 按位与
^ 按位异或
| 按位或
&& 短路与
|| 短路或
? : 条件
= += -= *= /= %= 赋值
实际写代码只记三条:
&&优先级高于||,所以a || b && c是a || (b && c)- 赋值运算符优先级最低,所以
a = b + c一定是先算右边 - 拿不准就加括号 —— 括号不加分,但能省一次 bug
十、常见误区与踩坑记录
5 / 2 = 2,整数除法截断,要小数必须让操作数变浮点-10 % 3 = -1,取余符号跟被除数走;-11.5 % 2.0容易误写成 1.5,实际是 -1.5byte b = 127; b += 1;结果是 -128,编译器不报错3 < a < 5编译不过,必须拆成3 < a && a < 5&&是短路,&不短路——判空那个场景用错就是空指针>>>对负数会变成大正数true ? 1 : 2.0结果是1.0,因为类型被统一成 double
十一、自测
5 / 2和5 / 2.0分别输出什么?5 % 2呢?-7 % 3等于多少?为什么?int i = 1; i = i++;之后 i 是几?byte b = 127; b += 1;输出什么?为什么不报错?str != null & str.length() > 0有什么风险?-8 >>> 1和-8 >> 1结果差多少?为什么?true ? 1 : 2.0输出1还是1.0?
相关阅读:Java 八种基本数据类型详解:类型提升、String 本质与常见踩坑——byte + byte 为什么不能赋给 byte,以及 b += 1 静默溢出背后的类型规则。
本文深入剖析Java运算符中易被忽视的关键机制:整数除法截断、取余符号规则、复合赋值隐式类型转换、自增自减执行时序、关系运算符左结合性、逻辑短路保障空安全、有/无符号右移差异、条件运算符类型统一规则,以及优先级误区。重点解释'为什么'而非仅结论,覆盖JLS规范行为与典型生产踩坑场景。
159

被折叠的 条评论
为什么被折叠?



