Day5|Java运算符与表达式详解
运算符是Java程序中最基础的逻辑单元,直接影响代码的正确性和效率。
无论是简单的数学计算,还是复杂的条件判断,都离不开对运算符的精准掌握。
无论你是编程新手还是希望巩固基础,本文将通过代码示例和原理拆解,助你彻底掌握运算符的“明规则”与“潜规则”。
一、Java 运算符全景图
1. 运算符分类与核心用途
类别 | 运算符示例 | 核心用途 | 行为特点 |
---|---|---|---|
数值类 | + - * / % ++ -- | 数学计算 | 类型自动提升、整除舍入 |
比较类 | > < >= <= == != | 条件判断 | 返回 boolean 值 |
逻辑类 | && || ! | 布尔逻辑组合 | 短路求值(&&、||) |
位操作类 | & | ^ ~ << >> >>> | 二进制位操作 | 高效底层控制 |
赋值类 | = += -= *= /= %= | 变量赋值与复合运算 | 隐含类型转换风险 |
条件类 | ?: | 简化条件逻辑 | 唯一的三目运算符 |
二、数值运算符
1. 基础算术运算
基础算术运算比较好理解,无非就是加减乘除,再就是取模,自增自减,注意一些细节即可。
int a = 10, b = 3;
System.out.println(a / b); // 3(整型除法舍去小数)
System.out.println(a % b); // 1(取余)
System.out.println(5 / 2.0); // 2.5(浮点参与保留小数)
TIP
整数除法直接截断小数,不四舍五入!
2. 自增/自减运算符
前置与后置的本质区别:
a++:先返回原值,再自增(对应JVM的iload + iinc指令)
++a:先自增,再返回新值
int a = 5;
int b = a++; // b=5, a=6
int c = ++a; // a=7, c=7
Thread类的sleep方法中有"a++"的应用:
ArrayListSpliterator类(定义在ArrayList中)的forEachRemaining方法有"++a**"的应用:**
小练一下:
int x = 3;
System.out.println(x++ + ++x); // 结果是什么?
按步分解:
int x = 3;
// 步骤分解:
// 1. x++ → 返回3,x=4
// 2. ++x → x=5,返回5
// 3. 3 + 5 = 8
System.out.println(x++ + ++x); // 输出8
TIP
这样的表达式在实际开发中几乎不可能使用!因为可读性极差。之所以给出这种案例,只是为了理解前置/后置自增。
三、比较与逻辑运算符
1. 比较运算符
运算符 | 描述 | 示例(假设 a=5, b=10) | 结果 |
---|---|---|---|
== | 等于 | a == b | false |
!= | 不等于 | a != b | true |
> | 大于 | a > b | false |
< | 小于 | a < b | true |
>= | 大于等于 | a >= 5 | true |
<= | 小于等于 | b <= 10 | true |
int a = 5;
int b = 10;
// 比较运算符
System.out.println("a > b: " + (a > b)); // false
System.out.println("a < b: " + (a < b)); // true
System.out.println("a >= b: " + (a >= b)); // false
System.out.println("a <= b: " + (a <= b)); // true
System.out.println("a == b: " + (a == b)); // false
System.out.println("a != b: " + (a != b)); // true
TIP
所有比较运算符返回boolean值,不可直接用于赋值(如if(1)非法)
2. 逻辑运算符
运算符 | 行为 | 示例 |
---|---|---|
&& | 左边结果为 false ,右侧逻辑不执行 | false && (x/0==0) 无异常 |
& | 始终执行两侧 | false & (x/0==0) 抛出异常 |
|| | 左边为 true ,右侧不执行 | true || (x/0==0) 无异常 |
| | 始终执行两侧 | true | (x/0==0) 抛出异常 |
"&&"的短路应用:
// ArrayList
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;
grow(minCapacity);
}
}
如果minCapacity > elementData.length条件不成立,那么右侧的就没有执行的必要了。
"||"的短路应用:
// ArrayList
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
如果oldCapacity > 0的条件成立,那么右侧的逻辑也就没有执行的必要了。
实际开发场景:
if (list != null && !list.isEmpty()) { ... }
避免空指针:先判空再调用方法。
"&"和"|"是非短路的逻辑运算符:
&(逻辑与):两个操作数均为true时结果为true,但强制计算左右两侧表达式(没有短路特性)。
|(逻辑或):至少一个操作数为true时结果为true,同样强制计算两侧表达式(没有短路特性)。
boolean a = false;
boolean b = true;
// 逻辑与:无论 a 是否为 false,右侧表达式都会被计算
boolean result1 = a & (b || someMethod()); // 右侧的 `someMethod()` 会被调用
// 逻辑或:无论 a 是否为 true,右侧表达式都会被计算
boolean result2 = a | (b && someMethod()); // 右侧的 `someMethod()` 会被调用
四、位运算符
1. 基础位操作
运算符 | 示例 | 二进制操作 | 结果 |
---|---|---|---|
& | 5 & 3 (0101 & 0011) | 按位与 | 0001 (1) |
| | 5 | 3 | 按位或 | 0111 (7) |
^ | 5 ^ 3 | 异或(相同为0,不同为1) | 0110 (6) |
~ | ~5 | 取反(包括符号位) | 11111010 (-6) |
TIP
"&"和“|”怎么又出现了? 当操作数是布尔值(boolean)时,& 和 | 是 非短路的逻辑运算符。 当操作数是整数类型(int, long, byte, short, char)时,& 和 | 是 位运算符
按位与(&)
int a = 5;
int b = 3;
System.out.println(a & b); // 输出结果1
// 5 & 3 0001 转换成十进制就是1
按位或(|)
int a = 5;
int b = 3;
System.out.println(a | b); // 输出结果7
// 5 | 3 0111 转换成十进制就是7
异或(^)
int a = 5;
int b = 3;
System.out.println(a ^ b); //输出结果6
// 5 ^ 3 0110 转换成十进制就是6
取反(~)
int a = 5;
System.out.println(~ a);// -6
// ~5 1010 转换成十进制就是6
TIP
为什么实际运行结果是-6,但是按照我们推理结果却是6?
int类型的5实际的32位二进制是00000000 00000000 00000000 00000101(之前我们只使用4位是因为前面都是0就没列出来)。
将00000000 00000000 00000000 00000101按位取反的结果是:11111111 11111111 11111111 11111010
最高位变成了1,表示这是一个负数。
剩下的31位数值位表示的是绝对值的补码。
补码转原码的逻辑:取反后加1。
补码: 11111111 11111111 11111111 11111010
取反: 00000000 00000000 00000000 00000101
加1: 00000000 00000000 00000000 00000110(十进制6)
符号位是1为负数,数值部分是6,所以结果是-6。
2. 位移运算
在Java中,位移运算符用于对整数类型(如 int、long)的二进制位进行移动操作。
运算符 | 方向 | 填充位规则 | 适用场景 |
---|---|---|---|
<< | 左移 | 右侧补 0 | 快速乘以 2 的幂,位掩码操作 |
>> | 右移 | 左侧补 符号位 | 处理有符号整数的除法 |
>>> | 右移 | 左侧补 0 | 无符号整数处理或强制转正数 |
左移运算符(<<)
将二进制位向左移动指定位数,右侧空位补0。
int a = 5; // 二进制: 0000 01012
int b = a << 2; // 左移2位 → 0001 0100(十进制 20)
等效于乘以 2n(a << n ≈ a * 2^n)。
可能导致符号位变化(如正数变负数)。
带符号右移运算符(>>)
将二进制位向右移动指定位数,左侧空位补 符号位(正数补 0,负数补 1)。
int a = 20; // 二进制: 0001 0100
int b = a >> 2; // 右移2位 → 0000 0101(十进制 5)
int c = -20; // 二进制补码: 1111 1111 1111 1111 1111 1111 1110 1100
int d = c >> 2; // 右移2位 → 1111 1111 1111 1111 1111 1111 1111 1011(十进制 -5)
等效于整数除法(向下取整)(a >> n ≈ a / 2^n)。
保留符号位,适合处理有符号整数。
无符号右移运算符(>>>)
将二进制位向右移动指定位数,左侧空位补 0(无论正负)。
int a = 20; // 二进制: 0001 0100
int b = a >>> 2; // 无符号右移2位 → 0000 0101(十进制 5)
int c = -20; // 二进制补码: 1111 1111 1111 1111 1111 1111 1110 1100
int d = c >>> 2; // 无符号右移2位 → 0011 1111 1111 1111 1111 1111 1111 1011(结果为正数)
仅适用于 int 和 long 类型。
强制左侧补 0,可能导致负数变为正数。
位移应用:
// ArrayList
private static long[] nBits(int n) {
return new long[((n - 1) >> 6) + 1];
}
private static void setBit(long[] bits, int i) {
bits[i >> 6] |= 1L << i;
}
private static boolean isClear(long[] bits, int i) {
return (bits[i >> 6] & (1L << i)) == 0;
}
五、赋值与条件运算符
1. 基本赋值运算符(=)
把右侧的值或表达式结果赋给左侧变量。
int a = 10; // 将 10 赋值给 a
String s = "Hello";
2.算术运算复合赋值符
运算符 | 等价表达式 | 示例 | 说明 |
---|---|---|---|
+= | a = a + b | a += 5 → a = a + 5 | 加法赋值 |
-= | a = a - b | a -= 3 → a = a - 3 | 减法赋值 |
*= | a = a * b | a *= 2 → a = a * 2 | 乘法赋值 |
/= | a = a / b | a /= 4 → a = a / 4 | 除法赋值 |
%= | a = a % b | a %= 3 → a = a % 3 | 取模赋值 |
3.位运算复合赋值符
运算符 | 等价表达式 | 示例 | 说明 |
---|---|---|---|
&= | a = a & b | a &= 0xFF | 按位与赋值 |
| = | a = a | b | ||
^= | a = a ^ b | a ^= mask | 按位异或赋值 |
<<= | a = a << b | a <<= 2 → 左移两位 | 左移赋值 |
>>= | a = a >> b | a >>= 1 → 带符号右移 | 带符号右移赋值 |
>>>= | a = a >>> b | a >>>= 3 → 无符号右移 | 无符号右移赋值 |
4. 三目运算符
三目运算符是一种简洁的条件表达式,用于替代简单的 if-else 逻辑。
// 条件 ? 表达式1 : 表达式2
// 如果 条件 为 true,返回 表达式1 的值。
// 如果 条件 为 false,返回 表达式2 的值。
int score = 85;
String result = (score >= 60) ? "及格" : "补考";
TIP
表达式1与表达式2必须类型兼容!
六、JLS 规范与优先级总表
Java语言规范(JLS)第15章明确定义了运算符的优先级和结合性。以下是根据JLS17整理的优先级顺序表及关键说明:
1. 运算符优先级
优先级 | 运算符类别 | 运算符 | 结合性 | 示例 |
---|---|---|---|---|
1 | 后缀表达式 | expr++, expr--, (), [], . | 左→右 | array[i].toString() |
2 | 一元表达式 | +, -, ++expr, --expr, ~, ! | 右→左 | -a, ++b, !flag |
3 | 乘除模 | *, /, % | 左→右 | a * b / c |
4 | 加减 | +, - | 左→右 | a + b - c |
5 | 移位 | <<, >>, >>> | 左→右 | x << 2 |
6 | 关系运算符1 | <, >, <=, >= | 左→右 | a >= 10 |
7 | 关系运算符2 | instanceof | 左→右 | obj instanceof String |
8 | 相等运算符 | ==, != | 左→右 | a == b |
9 | 按位与 | & | 左→右 | a & b |
10 | 按位异或 | ^ | 左→右 | a ^ b |
11 | 按位或 | | | 左→右 | a | b |
12 | 逻辑与(短路) | && | 左→右 | a && b |
13 | 逻辑或(短路) | || | 左→右 | a || b |
14 | 三元运算符 | ?: | 右→左 | a > b ? x : y |
15 | 赋值类 | =, +=, -=, *=, /=, %= 等 | 右→左 | a += b,x = y = z + 1 |
16 | 逗号运算符 | , | 左→右 | (a = 1, b = 2) |
七、随手写了几个运算符问题,试一试吧
- Integer i = 128; Integer j = 128; System.out.println(i == j);
- byte b = 1; b = b + 1; 会不会报错?为什么?
- System.out.println(5 / 2); 和 5 / 2.0 的区别?
- int x = 1; x = x++ + ++x; 最终结果?
- a = a++; 和 a = ++a; 有什么差异?
结语
编程的本质是逻辑的精确表达,而运算符正是这表达的核心工具。
建议多动手实践文中的代码示例,尝试挑战综合练习题,将知识内化为本能。
唯有在编码中踩过坑、解过惑,才能真正领悟“纸上得来终觉浅,绝知此事要躬行”的真谛。