运算符
上一章我们认识了 Java 的”咖啡豆”——变量与数据类型。但咖啡豆自己不会变成咖啡,它需要经过研磨、萃取、调味等一道道工序。同样,数据本身是静止的,需要通过运算符(Operator)才能被加工、计算、比较,最终形成有意义的逻辑。
运算符是程序员手中的工具,就像咖啡师手中的研磨机、拉花缸、温度计。掌握每一种运算符的特性与脾气,你才能写出既高效又优雅的代码。
一、算术运算符
算术运算符是最常见的运算符,用于执行数学运算。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 5 / 3 | 1(整数除法) |
% | 取模(取余) | 5 % 3 | 2 |
++ | 自增 | a++ 或 ++a | a 的值加 1 |
-- | 自减 | a-- 或 --a | a 的值减 1 |
1.1 整数除法的陷阱
注意:两个整数相除,结果仍然是整数,小数部分被直接截断(不是四舍五入):
int a = 5 / 3; // 结果是 1,而非 1.666...
double b = 5 / 3; // 结果是 1.0!因为 5/3 先按整数算得 1,再转为 double
double c = 5.0 / 3; // 结果才是 1.666...,因为 5.0 是浮点数
要想得到精确的浮点结果,至少有一个操作数要是浮点数。
1.2 自增自减的前后之别
++ 和 -- 有两种形式:前缀(++a)和后缀(a++)。它们的区别在于返回值:
++a(前缀):先加 1,再返回新值。a++(后缀):先返回旧值,再加 1。
int a = 5;
int b = a++; // b = 5, a = 6(先赋值,再自增)
int c = ++a; // c = 7, a = 7(先自增,再赋值)
💡 在复杂的表达式中混用自增自减会让代码难以理解,建议单独成行使用。
1.3 整数溢出陷阱
这是 Java 初学者最容易踩的坑之一。int 类型的最大值是 2,147,483,647(约 21 亿),如果超出这个范围,会发生溢出(Overflow),结果会”绕回”到负数:
int max = 2147483647;
int overflow = max + 1;
System.out.println(overflow); // 输出 -2147483648!
这就像咖啡杯的容量有限,倒满了再倒就会溢出——而在计算机里,溢出后”绕回”负数,而非报错。这是二进制补码表示法的特性。
对策:若预计数值可能很大,使用 long 类型;或使用 Math.addExact(a, b),它在溢出时会抛出 ArithmeticException,避免静默错误。
二、关系运算符
关系运算符(Relational Operator)用于比较两个值,结果是 boolean 类型(true 或 false)。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
== | 等于 | 5 == 5 | true |
!= | 不等于 | 5 != 3 | true |
> | 大于 | 5 > 3 | true |
< | 小于 | 5 < 3 | false |
>= | 大于等于 | 5 >= 5 | true |
<= | 小于等于 | 5 <= 3 | false |
⚠️ 重要陷阱:
==用于比较基本类型时比较的是”值”,但用于引用类型(如String)时比较的是”引用地址”——即两个变量是否指向同一个对象,而非内容是否相同。比较字符串内容应使用equals()方法:String s1 = new String("Java"); String s2 = new String("Java"); System.out.println(s1 == s2); // false(不同对象) System.out.println(s1.equals(s2)); // true(内容相同)
三、逻辑运算符
逻辑运算符(Logical Operator)用于组合多个布尔表达式,是条件判断的核心。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
&& | 逻辑与(短路与) | true && false | false |
|| | 逻辑或(短路或) | true || false | true |
! | 逻辑非 | !true | false |
& | 逻辑与(不短路) | true & false | false |
| | 逻辑或(不短路) | true | false | true |
3.1 短路逻辑:&& 与 || 的智慧
&& 和 || 是短路运算符(Short-circuit),它们在能够确定结果时就不再计算右侧表达式——这是一种性能优化,也常用于避免空指针异常。
&&(短路与):如果左侧为 false,整个表达式必定为 false,右侧不再计算。
String name = null;
// 若 name 为 null,则右侧的 name.length() 不会被执行,避免空指针异常
if (name != null && name.length() > 5) {
System.out.println("名字合法");
}
||(短路或):如果左侧为 true,整个表达式必定为 true,右侧不再计算。
int count = 10;
// 若 count > 0 为真,右侧不会计算
if (count > 0 || expensiveCheck()) {
System.out.println("条件成立");
}
3.2 & 与 && 的区别
& 是不短路的”与”:无论左侧结果如何,右侧都会被执行。| 同理。它们通常用于位运算(见下节),在布尔逻辑中较少使用,但在需要确保副作用执行时偶尔有用。
四、位运算符
位运算符(Bitwise Operator)直接操作整数的二进制位。它就像咖啡师在分子级别操控咖啡——虽然日常较少使用,但在底层编程、性能优化、权限控制等场景中非常强大。
| 运算符 | 描述 | 示例(假设 a=60, b=13) |
|---|---|---|
& | 按位与 | a & b = 12 |
| | 按位或 | a | b = 61 |
^ | 按位异或 | a ^ b = 49 |
~ | 按位取反 | ~a = -61 |
<< | 左移 | a << 2 = 240 |
>> | 右移(带符号) | a >> 2 = 15 |
>>> | 无符号右移 | a >>> 2 = 15 |
4.1 位运算详解
以 a = 60(二进制 0011 1100)和 b = 13(二进制 0000 1101)为例:
- 按位与
&:对应位都为 1 才为 1。0011 1100 & 0000 1101 = 0000 1100(12)。常用于”掩码”——取出某些位。 - 按位或
|:对应位有一个为 1 即为 1。0011 1100 | 0000 1101 = 0011 1101(61)。常用于”设置”某些位。 - 按位异或
^:对应位不同为 1,相同为 0。0011 1100 ^ 0000 1101 = 0011 0001(49)。特性:a ^ a = 0,a ^ 0 = a。 - 按位取反
~:0 变 1,1 变 0。~0011 1100 = 1100 0011(-61,因为补码表示)。
4.2 移位运算
- 左移
<<:二进制位整体左移,右侧补 0。每左移一位相当于乘以 2。60 << 2 = 240(60 × 4)。 - 右移
>>:二进制位整体右移,左侧补符号位(正数补 0,负数补 1)。每右移一位相当于除以 2(向下取整)。60 >> 2 = 15(60 / 4)。 - 无符号右移
>>>:二进制位整体右移,左侧始终补 0,忽略符号位。它将负数视为大正数。
4.3 位运算的实际应用
判断奇偶:n & 1,结果为 0 是偶数,为 1 是奇数——比 n % 2 更快。
交换两个变量(无需临时变量):
a = a ^ b;
b = a ^ b;
a = a ^ b;
// 现在 a 和 b 的值已交换
权限控制:用位表示不同权限,| 组合,& 检查:
int READ = 1; // 0001
int WRITE = 2; // 0010
int EXEC = 4; // 0100
int permission = READ | WRITE; // 0011,拥有读和写权限
boolean canRead = (permission & READ) != 0; // true
五、赋值运算符与复合赋值
赋值运算符 = 用于将右侧的值赋给左侧的变量。Java 还提供了一系列复合赋值运算符,将运算与赋值合二为一:
| 运算符 | 等价于 | 示例 |
|---|---|---|
+= | a = a + b | a += 5 |
-= | a = a - b | a -= 5 |
*= | a = a * b | a *= 5 |
/= | a = a / b | a /= 5 |
%= | a = a % b | a %= 5 |
&=、|=、^=、<<=、>>=、>>>= | 对应位运算 | a &= 5 |
💡 隐藏特性:复合赋值运算符自带强制类型转换!
byte b = 10; b = b + 5; // 编译错误:b+5 是 int,不能直接赋给 byte b += 5; // 正确!相当于 b = (byte)(b + 5),自动强转这意味着复合赋值可能会有隐式截断,使用时要注意数值范围。
六、三元运算符
三元运算符(Ternary Operator)? : 是 Java 中唯一的三目运算符,它是一种简洁的条件表达式:
条件 ? 表达式1 : 表达式2
若条件为 true,返回表达式1的值;否则返回表达式2的值。
int score = 85;
String result = score >= 60 ? "及格" : "不及格";
// 等价于:
// String result;
// if (score >= 60) result = "及格";
// else result = "不及格";
三元运算符让简单的条件赋值变得简洁,但不要嵌套过深——以下代码虽然合法,但可读性极差:
String level = score >= 90 ? "优秀" : score >= 80 ? "良好" : score >= 60 ? "及格" : "不及格";
遇到复杂条件,还是用 if-else 更清晰。
七、运算符优先级
当一个表达式包含多种运算符时,谁先算?这就是优先级(Precedence)问题。下表从高到低列出(同一行优先级相同,按从左到右结合):
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 1 | ()、[]、. | 括号、数组下标、成员访问 |
| 2 | !、~、++、--、+(正)、-(负) | 一元运算符 |
| 3 | *、/、% | 乘除取模 |
| 4 | +、- | 加减 |
| 5 | <<、>>、>>> | 移位 |
| 6 | <、<=、>、>=、instanceof | 关系 |
| 7 | ==、!= | 相等 |
| 8 | & | 按位与 |
| 9 | ^ | 按位异或 |
| 10 | | | 按位或 |
| 11 | && | 逻辑与 |
| 12 | || | 逻辑或 |
| 13 | ?: | 三元 |
| 14 | =、+=、-=、*=、… | 赋值 |
记忆口诀:一元 > 算术 > 移位 > 关系 > 逻辑 > 三元 > 赋值。
💡 实际开发中,不必死记硬背。多用括号明确意图,既能避免优先级错误,又能提升可读性。
a + b * c和(a + b) * c谁更清晰?显然是后者。
八、instanceof 运算符
instanceof 用于判断一个对象是否是某个类(或其子类、接口实现)的实例:
String s = "Java";
boolean isString = s instanceof String; // true
Object obj = "Hello";
if (obj instanceof String) {
String str = (String) obj; // 安全的强制转换
System.out.println(str.length());
}
从 Java 16 开始,instanceof 支持模式匹配(Pattern Matching),可以同时判断并绑定变量,省去显式强转:
// Java 16+
Object obj = "Hello";
if (obj instanceof String str) {
System.out.println(str.length()); // str 已自动绑定
}
九、综合演示:位运算与三元运算
让我们用一个程序来演示位运算和三元运算符的实际效果:
十、本章要点回顾
| 类别 | 运算符 | 注意事项 |
|---|---|---|
| 算术 | + - * / % ++ -- | 整数除法截断;注意溢出 |
| 关系 | == != > < >= <= | == 比较引用类型时比较地址 |
| 逻辑 | && || ! & | | && ` |
| 位运算 | & | ^ ~ << >> >>> | >>> 无符号右移;位运算效率高 |
| 赋值 | = += -= *= ... | 复合赋值自带强制类型转换 |
| 三元 | ?: | 不要嵌套过深 |
| 类型 | instanceof | Java 16+ 支持模式匹配 |
结语
运算符是程序员手中的瑞士军刀,每一种都有其用武之地。算术运算处理数值,关系运算比较大小,逻辑运算组合条件,位运算深入底层。理解它们的特性和优先级,你就能像咖啡师熟练操控各种工具一样,从容地构建复杂的程序逻辑。
但运算符只是”加工”数据的工具,程序还需要”决策”的能力——根据条件选择不同的执行路径。下一章,我们将学习 Java 的控制流程:if-else、switch、for、while……让你的程序学会”思考”与”重复”。届时我们还会用这些工具写一个有趣的”猜数字”游戏。