运算符
字数: 0 字 时长: 0 分钟
1. 概述
运算符在 Java 中占据着重要的位置,用于对数据进行计算、比较、判断和赋值。除了常见的加减乘除,还有许多其他类型的运算符,来看下面这张思维导图。
Java 中的运算符种类比较多,根据作用不同,通常可以分为以下几类:
- 算术运算符:用于加、减、乘、除、取余等数学运算。
- 关系运算符:用于比较两个值的大小或是否相等。
- 逻辑运算符:用于组合多个布尔条件。
- 位运算符:直接对整数对应的二进制位进行操作。
- 赋值运算符:用于给变量赋值或进行复合赋值。
- 三元运算符:根据条件在两个结果之间进行选择。
- 自增与自减运算符:对变量进行加一或减一操作。

2. 算术运算符
算术运算符除了最常见的加减乘除,还有一个取余的运算符,用于得到除法运算后的余数,来串代码感受下。
int a = 10;
int b = 5;
// 加法:10 + 5 = 15
System.out.println(a + b);
// 减法:10 - 5 = 5
System.out.println(a - b);
// 乘法:10 * 5 = 50
System.out.println(a * b);
// 整数除法:10 / 5 = 2
System.out.println(a / b);
// 取余:10 % 5 = 0,因为 10 可以被 5 整除
System.out.println(a % b);
// 修改 b 的值
b = 3;
// 加法:10 + 3 = 13
System.out.println(a + b);
// 减法:10 - 3 = 7
System.out.println(a - b);
// 乘法:10 * 3 = 30
System.out.println(a * b);
// 整数除法:10 / 3 = 3
// 因为 a 和 b 都是 int 类型,小数部分会被直接舍弃
System.out.println(a / b);
// 取余:10 % 3 = 1
// 表示 10 除以 3 后,余数为 1
System.out.println(a % b);对于初学者来说,加法(+)、减法(-)、乘法(*)很好理解,但除法(/)和取余(%)会有一点点疑惑。在以往的认知里,10/3 是除不尽的,结果应该是 3.333333...,而不应该是 3。相应的,余数也不应该是 1。
因为数字在程序中可以分为两种,一种是整型,一种是浮点型。整型和整型的运算结果就是整型,不会出现浮点型。否则,就会出现浮点型。
int a = 10;
float c = 3.0f;
double d = 3.0;
// int 和 float 运算时,int 会自动提升为 float
// 结果为 float 类型,精度相对较低
System.out.println(a / c);
// int 和 double 运算时,int 会自动提升为 double
// 结果为 double 类型,精度比 float 更高
System.out.println(a / d);
// 浮点数也支持取余运算
// 10 除以 3.0f,商为 3,余数为 1.0
System.out.println(a % c);
// double 同样支持取余运算
// 10 除以 3.0,余数为 1.0
System.out.println(a % d);需要注意的是,当浮点数除以 0 的时候,结果为 Infinity 或者 NaN。
// 浮点数除以 0,不会抛出异常
// 正数除以 0.0,结果为正无穷大 Infinity
System.out.println(10.0 / 0.0);
// 0.0 除以 0.0 没有确定的数学结果
// Java 会返回 NaN,表示 Not a Number
System.out.println(0.0 / 0.0);Infinity 的中文意思是无穷大
NaN 的中文意思是这不是一个数字(Not a Number)。
当整数除以 0 的时候(10 / 0),会抛出异常(手动制造异常很喜欢用这个哈哈哈哈)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.kerwim.ArithmeticOperator.main(ArithmeticOperator.java:32)所以整数在进行除法运算时,需要先判断除数是否为 0,以免程序抛出异常。
算术运算符中还有两种特殊的运算符,自增运算符(++)和自减运算符(--),它们也叫做一元运算符,只有一个操作数。
int x = 10;
// 后自增:先使用当前值,再执行 x = x + 1
// 当前输出 10,执行结束后 x 变为 11
System.out.println(x++);
// 前自增:先执行 x = x + 1,再使用新值
// x 从 11 变为 12,因此输出 12
System.out.println(++x);
// 后自减:先使用当前值,再执行 x = x - 1
// 当前输出 12,执行结束后 x 变为 11
System.out.println(x--);
// 前自减:先执行 x = x - 1,再使用新值
// x 从 11 变为 10,因此输出 10
System.out.println(--x);一元运算符可以放在数字的前面或者后面,放在前面叫前自增(前自减),放在后面叫后自增(后自减)。
前自增和后自增是有区别的,拿 int y = ++x 这个表达式来说(x = 10),它可以拆分为 x = x+1 = 11; y = x = 11,所以表达式的结果为 x = 11, y = 11。拿 int y = x++ 这个表达式来说(x = 10),它可以拆分为 y = x = 10; x = x+1 = 11,所以表达式的结果为 x = 11, y = 10
int x = 10;
// 前自增:先执行 x = x + 1,再将新值赋给 y
// x 从 10 变为 11,随后 y 得到 11
int y = ++x;
// 输出:11 11
System.out.println(y + " " + x);
x = 10;
// 后自增:先将 x 当前的值赋给 y,再执行 x = x + 1
// y 先得到 10,随后 x 从 10 变为 11
y = x++;
// 输出:10 11
System.out.println(y + " " + x);3. 关系运算符
关系运算符用于比较两个操作数之间的大小、相等或不等关系,运算结果始终为布尔值 true 或 false,通常用于条件判断和流程控制。

来看示例:
int a = 10;
int b = 20;
// 判断 a 是否等于 b
// 10 不等于 20,因此结果为 false
System.out.println(a == b);
// 判断 a 是否不等于 b
// 10 不等于 20,因此结果为 true
System.out.println(a != b);
// 判断 a 是否大于 b
// 10 不大于 20,因此结果为 false
System.out.println(a > b);
// 判断 a 是否小于 b
// 10 小于 20,因此结果为 true
System.out.println(a < b);
// 判断 a 是否大于或等于 b
// 10 既不大于也不等于 20,因此结果为 false
System.out.println(a >= b);
// 判断 a 是否小于或等于 b
// 10 小于 20,因此结果为 true
System.out.println(a <= b);关系运算符的结果始终是 boolean 类型,即 true 或 false。
4. 位运算符
位运算符是直接对整数的二进制位进行操作的运算符,常用于底层计算、状态标记、权限控制以及部分性能优化场景。 Java 中常见的位运算符包括:
| 运算符 | 名称 | 说明 |
|---|---|---|
& | 按位与 | 对应位都为 1 时结果为 1,否则为 0 |
| | 按位或 | 对应位只要有一个为 1,结果就是 1 |
^ | 按位异或 | 对应位相同时为 0,不同时为 1 |
~ | 按位取反 | 将二进制中的 0 变为 1,1 变为 0 |
<< | 左移 | 二进制整体向左移动指定的位数,右侧补 0 |
>> | 右移 | 二进制整体向右移动指定的位数,左侧按符号位补齐 |
>>> | 无符号右移 | 二进制整体向右移动指定的位数,左侧始终补 0 |

在学习位运算符之前,需要先学习一下二进制(科班应该学 C 语言的时候就已经懂了),因为位运算符操作的不是整型数值(int、long、short、char、byte)本身,而是整型数值对应的二进制。
// 将十进制整数 60 转换为二进制字符串
// 60 的二进制表示为 111100
System.out.println(Integer.toBinaryString(60));
// 将十进制整数 13 转换为二进制字符串
// 13 的二进制表示为 1101
System.out.println(Integer.toBinaryString(13));从程序的输出结果可以看得出来,60 的二进制是 0011 1100(用 0 补到 8 位),13 的二进制是 0000 1101。
PS:现代的二进制记数系统由戈特弗里德·威廉·莱布尼茨于 1679 年设计。莱布尼茨是德意志哲学家、数学家,历史上少见的通才。
来看示例:
int a = 60;
int b = 13;
// 查看 a 和 b 的二进制表示
System.out.println("a 的二进制:" + Integer.toBinaryString(a));
System.out.println("b 的二进制:" + Integer.toBinaryString(b));
// 按位与:对应位都为 1 时结果才为 1
// 60 & 13 = 12
int c = a & b;
System.out.println("a & b:" + c + ",二进制:" + Integer.toBinaryString(c));
// 按位或:对应位只要有一个为 1,结果就是 1
// 60 | 13 = 61
c = a | b;
System.out.println("a | b:" + c + ",二进制:" + Integer.toBinaryString(c));
// 按位异或:对应位相同为 0,不同为 1
// 60 ^ 13 = 49
c = a ^ b;
System.out.println("a ^ b:" + c + ",二进制:" + Integer.toBinaryString(c));
// 按位取反:0 变 1,1 变 0
// int 是 32 位有符号整数,因此 ~60 的结果为 -61
c = ~a;
System.out.println("~a:" + c + ",二进制:" + Integer.toBinaryString(c));
// 左移 2 位:二进制整体向左移动 2 位,右侧补 0
// 60 << 2 = 240
c = a << 2;
System.out.println("a << 2:" + c + ",二进制:" + Integer.toBinaryString(c));
// 右移 2 位:二进制整体向右移动 2 位
// 60 >> 2 = 15
c = a >> 2;
System.out.println("a >> 2:" + c + ",二进制:" + Integer.toBinaryString(c));
// 无符号右移 2 位:右移时左侧始终补 0
// a 是正数,因此这里的结果与 >> 相同
// 60 >>> 2 = 15
c = a >>> 2;
System.out.println("a >>> 2:" + c + ",二进制:" + Integer.toBinaryString(c));对于初学者来说,位运算符无法从直观上去计算出结果,不像加减乘除那样。因为我们日常接触的都是十进制,位运算的时候需要先转成二进制,然后再计算出结果。
鉴于此,初学者在写代码的时候其实很少会用到位运算。对于编程高手来说,为了提高程序的性能,会在一些地方使用位运算。比如说,HashMap 在计算哈希值的时候:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}这里没有把笔记做到HashMap 就不深入了,其实我一年半的经验,我也是第一次看到这样写的。
如果对位运算一点都不懂的话,遇到这样的源码就很吃力。所以说,虽然位运算用的少,但还是要懂
1)按位左移运算符:
// 左移 2 位,相当于乘以 2²
// 10 × 4 = 40
System.out.println(10 << 2);
// 左移 3 位,相当于乘以 2³
// 10 × 8 = 80
System.out.println(10 << 3);
// 左移 2 位,相当于乘以 2²
// 20 × 4 = 80
System.out.println(20 << 2);
// 左移 4 位,相当于乘以 2⁴
// 15 × 16 = 240
System.out.println(15 << 4);10<<2 等于 10 乘以 2 的 2 次方;10<<3 等于 10 乘以 2 的 3 次方。
2)按位右移运算符:
// 右移 2 位,相当于除以 2²
// 10 ÷ 4 = 2
System.out.println(10 >> 2);
// 右移 2 位,相当于除以 2²
// 20 ÷ 4 = 5
System.out.println(20 >> 2);
// 右移 3 位,相当于除以 2³
// 20 ÷ 8 = 2
// 整数运算会舍弃小数部分
System.out.println(20 >> 3);10>>2 等于 10 除以 2 的 2 次方;20>>2 等于 20 除以 2 的 2 次方。
对于正整数,右移 n 位通常可以理解为除以 2ⁿ,结果仍然是整数。
5. 逻辑运算符
逻辑与运算符(&&):多个条件中只要有一个为 false 结果就为 false。
逻辑或运算符(||):多个条件只要有一个为 true 结果就为 true。
int a = 10;
int b = 5;
int c = 20;
// 逻辑与 &&:两个条件都为 true,结果才为 true
// a < b 为 false,a < c 为 true
// false && true = false
System.out.println(a < b && a < c);
// 逻辑或 ||:两个条件只要有一个为 true,结果就是 true
// a > b 为 true,a < c 为 true
// true || true = true
System.out.println(a > b || a < c);&& 表示逻辑与,要求所有条件都成立;|| 表示逻辑或,只要有一个条件成立即可。
逻辑非运算符(!):用来反转条件的结果,如果条件为 true,则逻辑非运算符将得到 false。
单逻辑与运算符(&):很少用,因为不管第一个条件为 true 还是 false,依然会检查第二个。
单逻辑或运算符(|):也会检查第二个条件。
也就是说,& 和 | 性能不如 && 和 ||,但用法一样:
int a = 10;
int b = 5;
int c = 20;
// 单逻辑与 &:无论左侧条件结果如何,右侧条件都会继续计算
// a < b 为 false,a < c 为 true
// false & true = false
System.out.println(a < b & a < c);
// 单逻辑或 |:无论左侧条件结果如何,右侧条件都会继续计算
// a > b 为 true,a < c 为 true
// true | true = true
System.out.println(a > b | a < c);& 和 | 用在布尔表达式中时,作用分别类似 && 和 ||,但不会发生短路,左右两边都会执行。通常条件判断更常用 && 和 ||。
6. 赋值运算符
赋值操作符恐怕是 Java 中使用最频繁的操作符了,它就是把操作符右侧的值赋值给左侧的变量。来看示例:
int a = 10;
int b = 20;
// 复合赋值运算:等价于 a = a + 4
// a 从 10 变为 14
a += 4;
// 复合赋值运算:等价于 b = b - 4
// b 从 20 变为 16
b -= 4;
// 输出 14
System.out.println(a);
// 输出 16
System.out.println(b);+=、-= 属于复合赋值运算符,可以把“运算 + 赋值”合并成一步,使代码更简洁。
不过在进行数值的赋值时,需要小点心,比如说下面这种情况:

编译器之所以提示错误,是因为 = 右侧的算术表达式默认为 int 类型,左侧是 short 类型的时候需要进行强转。
short a = 10;
short b = 10;
// a + b 运算时,short 会先自动提升为 int
// 因此不能直接写:a = a + b
// 需要将计算结果强制转换回 short
a = (short) (a + b);
// 输出 20
System.out.println(a);short、byte、char 参与算术运算时,通常会先提升为 int。所以 a + b 的结果是 int 类型,赋值给 short 时需要进行强制类型转换。
除此之外,还会有边界问题,比如说,两个非常大的 int 相乘,结果可能就超出了 int 的范围:
int a = Integer.MAX_VALUE;
int b = 10000;
// Integer.MAX_VALUE 是 int 类型能够表示的最大值
// a * b 的计算结果远远超过 int 的取值范围
// 因此发生整数溢出,最终得到错误结果 -10000
int c = a * b;
// 输出 -10000
System.out.println(c);int 运算发生溢出时不会自动报错,而是按照 32 位二进制结果截断,因此可能得到完全错误的数值。
程序输出的结果为 -10000,这个答案很明显不是我们想要的结果,虽然可以通过右侧表达式强转 long 的方法解决:
int a = Integer.MAX_VALUE;
int b = 10000;
// 先将 a 强制转换为 long
// 此时表达式会按照 long 类型进行计算,避免 int 运算溢出
long c = (long) a * b;
// 输出 21474836470000
System.out.println(c);只要参与运算的操作数中有一个是 long 类型,另一个 int 会自动提升为 long,最终结果也会按照 long 类型计算。
但尽量不要这样做,结果非常大的时候,尽量提前使用相应的类型进行赋值。
long a = Integer.MAX_VALUE - 1;
long b = 10000;
// a 和 b 本身就是 long 类型
// 因此乘法运算会直接按照 long 类型进行计算,不会发生 int 溢出
long c = a * b;
// 输出 21474836460000
System.out.println(c);如果预期计算结果可能超出 int 的取值范围,最好从一开始就使用 long 类型,而不是等到运算时再进行强制类型转换。
7. 三元运算符
三元运算符用于替代 if-else,可以使用一行代码完成条件判断的要求。来看示例:
int a = 2;
int b = 5;
// 三元运算符:条件 ? 条件成立时的结果 : 条件不成立时的结果
// a < b 为 true,因此返回 a 的值
int min = (a < b) ? a : b;
// 输出 2
System.out.println(min);三元运算符适合处理简单的二选一逻辑,条件 ? 值1 : 值2。条件为 true 时返回 值1,否则返回 值2。
如果 ? 前面的条件为 true,则结果为 : 前的值,否则为 : 后的值。