运算符

上一章我们认识了 Java 的”咖啡豆”——变量与数据类型。但咖啡豆自己不会变成咖啡,它需要经过研磨、萃取、调味等一道道工序。同样,数据本身是静止的,需要通过运算符(Operator)才能被加工、计算、比较,最终形成有意义的逻辑。

运算符是程序员手中的工具,就像咖啡师手中的研磨机、拉花缸、温度计。掌握每一种运算符的特性与脾气,你才能写出既高效又优雅的代码。

一、算术运算符

算术运算符是最常见的运算符,用于执行数学运算。

运算符描述示例结果
+加法5 + 38
-减法5 - 32
*乘法5 * 315
/除法5 / 31(整数除法)
%取模(取余)5 % 32
++自增a++++aa 的值加 1
--自减a----aa 的值减 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 类型(truefalse)。

运算符描述示例结果
==等于5 == 5true
!=不等于5 != 3true
大于5 > 3true
小于5 < 3false
>=大于等于5 >= 5true
<=小于等于5 <= 3false

⚠️ 重要陷阱== 用于比较基本类型时比较的是”值”,但用于引用类型(如 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 && falsefalse
||逻辑或(短路或)true || falsetrue
!逻辑非!truefalse
&逻辑与(不短路)true & falsefalse
|逻辑或(不短路)true | falsetrue

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 = 0a ^ 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 + ba += 5
-=a = a - ba -= 5
*=a = a * ba *= 5
/=a = a / ba /= 5
%=a = a % ba %= 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 已自动绑定
}

九、综合演示:位运算与三元运算

让我们用一个程序来演示位运算和三元运算符的实际效果:

Java · 在线运行

十、本章要点回顾

类别运算符注意事项
算术+ - * / % ++ --整数除法截断;注意溢出
关系== != > < >= <=== 比较引用类型时比较地址
逻辑&& || ! & |&& `
位运算& | ^ ~ << >> >>>>>> 无符号右移;位运算效率高
赋值= += -= *= ...复合赋值自带强制类型转换
三元?:不要嵌套过深
类型instanceofJava 16+ 支持模式匹配

结语

运算符是程序员手中的瑞士军刀,每一种都有其用武之地。算术运算处理数值,关系运算比较大小,逻辑运算组合条件,位运算深入底层。理解它们的特性和优先级,你就能像咖啡师熟练操控各种工具一样,从容地构建复杂的程序逻辑。

但运算符只是”加工”数据的工具,程序还需要”决策”的能力——根据条件选择不同的执行路径。下一章,我们将学习 Java 的控制流程if-elseswitchforwhile……让你的程序学会”思考”与”重复”。届时我们还会用这些工具写一个有趣的”猜数字”游戏。