Skip to content

运算符 ​

Java 初级 · 基础语法

运算符是对数据做运算的符号。表面看 a + b、i++ 很简单,但底层都对应特定字节码指令;而 && 的短路、== 与 .equals() 的区别,更是面试和实战的高频坑。本节把「怎么用」和「底层为什么」一起讲清。

一、运算符全景 ​

类别常见运算符操作数结果
算术+ - * / % ++ --数值/ 整除取整,% 取余
关系> < >= <= == !=数值/对象boolean
逻辑&& || !布尔boolean(带短路)
位运算& | ^ ~ << >> >>>整数(按位)整数
赋值= += -= *= /= %=变量赋值
三元条件 ? 值1 : 值2布尔 + 两值其中之一

二、算术运算与整数除法 ​

/ 对整数类型是整除取整(向零截断),不是四舍五入;% 取余数。这在算法和分页里非常常用:

java
int a = 10 / 3;     // 3,余数被丢弃
int b = 10 % 3;     // 1
int pages = (total + pageSize - 1) / pageSize;  // 经典「向上取整」写法

整数除法陷阱

5 / 2 是 2 不是 2.5。若想要小数,至少让一侧为浮点:5.0 / 2 或 (double)5 / 2。另外 -7 / 2 得 -3(向零截断,不是 floor),做索引/分页时留意负号。

自增 ++ 的前后置(字节码视角) ​

这是初学者最容易错的点。i++ 先取值后加 1,++i 先加 1 后取值:

java
int i = 5;
int j = i++;        // j = 5, i = 6(先用后加)
int k = ++i;        // k = 7, i = 7(先加后用)

底层对应不同字节码(简化):

  • i++:先 iload 把 5 压栈(作为表达式的值),再 iinc 把局部变量槽 +1。
  • ++i:先 iinc 把槽 +1,再 iload 压栈。

所以在 int x = i++ + i++; 这种表达式里,顺序和执行结果完全由「取值时机」决定,可读性极差——生产代码里别在复杂表达式里混用自增。

三、短路逻辑 && / ||:不仅是性能,更是安全 ​

&&(与)、||(或)具有短路(short-circuit)特性:左操作数已能决定整体结果时,右操作数根本不会执行。

java
int x = 0;
boolean r = x != 0 && (10 / x > 1);  // x!=0 为 false,右侧不执行,避免除零崩溃

对比非短路的 & / |:它们会两边全算,既无短路保护,性能也略差。&/| 主要用于位运算(对整数逐位操作),别拿来当逻辑与或。

短路的字节码真相 ​

&& 编译为条件跳转:先算左,若左为 false 直接 goto 到「结果为 false」,右侧的指令被跳过;只有左为 true 才继续。这正是「右侧不执行」的硬件级原因。

顺序写反会出 NPE / 除零

obj != null && obj.field 是对的;写成 obj.field && obj != null 就会先访问 obj.field 触发 NullPointerException。把「可能为 null / 可能除零 / 可能越界」的判断放左边,用短路保安全。

四、关系运算与「== 比的是什么」 ​

对基本类型,== 比的是「值」是否相等;对引用类型,== 比的是「地址(是否同一个对象)」,不是内容。这是和 .equals() 的根本区别:

java
int x = 10, y = 10;
System.out.println(x == y);          // true,比的是值

String s1 = new String("hi");
String s2 = new String("hi");
System.out.println(s1 == s2);        // false!两个不同对象,地址不同
System.out.println(s1.equals(s2));   // true,比的是字符内容

== 在字节码里对基本类型是 if_icmpeq(比较两 int 值),对引用类型是 if_acmpeq(比较两引用地址)。.equals() 则是一次真正的虚方法调用,由对象自己定义「怎样算相等」(详见常用类一节)。

五、位运算:直接操作二进制 ​

位运算把整数当作一串 0/1 逐位处理,在底层、网络协议、权限掩码里很常见。

java
int m = 6;     // 二进制 0110
int n = 3;     // 二进制 0011
int and  = m & n;   // 2  (0010)  按位与
int or   = m | n;   // 7  (0111)  按位或
int xor  = m ^ n;   // 5  (0101)  按位异或
int inv  = ~m;      // -7 按位取反(补码下 ~x = -x-1)
int left = m << 1;  // 12 (1100)  左移 1 位 ≡ 乘 2
int right= m >> 1;  // 3  (0011)  算术右移(保留符号位)≡ 除以 2 向下取整

要点:

  • << n 约等于「乘 2ⁿ」,>> n 约等于「除以 2ⁿ」(向下取整)。但注意溢出:1 << 31 在 int 里会变成负数(符号位被置 1)。
  • >>>(无符号右移)与 >> 的区别:前者高位永远补 0,不保留符号位;对负数结果差异巨大。
  • ^(异或)有个妙用:不借助临时变量交换两数 a ^= b; b ^= a; a ^= b;(了解即可,可读性不如临时变量)。

六、三元表达式与赋值 ​

三元表达式 条件 ? 值1 : 值2 是 if-else 的「表达式版」,能直接作为一个值:

java
int score = 85;
String level = score >= 60 ? "及格" : "不及格";   // 整体返回一个 String

复合赋值 += 隐含一次窄化强转(见变量一节):short s = 1; s += 1; 合法,但 s = s + 1; 会报错(右边是 int,需显式转回 short)。

优先级:不要靠记忆力,靠括号

a + b * c 先乘后加,!a && b || c 则容易读错。复杂表达式一律加括号:(!a && b) || c。括号不仅防错,更让半年后的你(和同事)一眼看懂意图。Java 运算符优先级表很长,记不住就分组加括号,这是工程上的成熟做法。

七、常见误区与注意点 ​

WARNING

  • == 比较引用类型内容:对 String/对象,== 比地址。比内容用 .equals();Integer 超 127 缓存失效后 == 也为 false。
  • 混淆 = 与 ==:if (x = 5) 是赋值(表达式值为 5,在布尔上下文中恒真或报错),应为 if (x == 5)。可写成 if (5 == x) 让编译器帮你防这种笔误。
  • 自增前后置用错:i++ 与 ++i 在独立语句里等价,但在表达式里结果不同;复杂表达式里避免混用。
  • 用 &/| 当逻辑运算:丢失短路保护,可能 NPE/除零;逻辑判断用 &&/||。
  • 忽视 >> 与 >>> 区别:负数右移时符号位处理不同,涉及无符号语义务必用 >>>。

八、最佳实践 ​

  1. 关系比较基本类型用 ==,比较对象内容用 .equals()。
  2. 复杂条件加括号,不依赖记忆中的优先级表。
  3. 善用短路:把「可能为 null / 除零 / 越界」的判断放左侧,用 &&/|| 保安全。
  4. 三元表达式只用于简单的二选一;逻辑一长就拆成 if-else,可读性优先。
  5. 位运算处理标志位/掩码时优先,普通算术别为了「炫技」用移位代替乘除以致难读。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 预测 int a=5, b=++a, c=a++; 执行后 a、b、c 的值,再用 javap -c 验证 ++a 与 a++ 字节码顺序差异(先 iinc 还是先 iload)。
  2. 用三元表达式写 int max(int x, int y) 返回较大值(提示:(x > y) ? x : y)。
  3. 解释 x != null && x.length() > 0 的短路作用:若把顺序写成 x.length() > 0 && x != null,输入 x = null 时会发生什么?
  4. 写出 int m = 12;(二进制 1100)左移 2 位和右移 2 位的结果,并说明左移溢出的风险(提示:符号位)。

本节小结

运算符分算术、关系、逻辑、位运算等。重点记住:整数除法向零截断、i++/++i 取值时机不同(== 对基本类型比值、对引用类型比地址,内容比较用 .equals)、&/| 不短路而 &&/|| 短路(字节码用条件跳转跳过右侧,既能提速又能防 NPE/除零)、位运算直接操作二进制(注意 >> 与 >>> 符号位差异)。复杂表达式一律加括号,别和优先级表较劲。