Java 运算符易混点:&&、||、&、| 与位运算到底差在哪
- 一、先问一句:你在判断条件,还是在处理二进制位?
- 二、`&&` 和 `||`:短路的意义是“右边可能不执行”
- 三、`&` 和 `|` 放在 boolean 上:能用,但不短路
- 四、`&`、`|`、`^`、`~` 放在整数上:逐位计算
- 1. 按位与 `&`
- 2. 按位或 `|`
- 3. 按位异或 `^`
- 4. 按位取反 `~`
- 五、移位运算:移动的是二进制位
- 六、优先级:能加括号就别考验读者
- 七、实战判断口诀
🎬 博主名称:超级苦力怕
🔥 个人专栏:《基本功修炼大全》
🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!
文章元信息:
- 适合读者:Java 初学者、正在复习运算符和条件判断的后端入门读者
- 前置知识:Java 基本语法、boolean 表达式、整数二进制表示的基本概念
如果你经常把 && 和 &、|| 和 | 记混,这篇从“条件判断”和“二进制位处理”两个场景切开讲,顺着示例看会轻松很多。
一、先问一句:你在判断条件,还是在处理二进制位?
&&、||、&、|长得太像,所以最容易被混在一起记。真正的分界不是“一个符号还是两个符号”,而是你现在想做什么:
- 如果是在
if、while里组合条件,通常要的是短路逻辑:&&、||。 - 如果是在 boolean 表达式里强制两边都执行,才会用非短路布尔运算:
&、|、^。 - 如果操作的是整数、标志位、掩码、哈希、权限位,才是在做按位运算:
&、|、^、~、<<、>>、>>>。
同一个符号在不同操作数类型下,含义会变:
| 运算符 | 操作数类型 | 含义 | 是否短路 |
|---|---|---|---|
&& | boolean | 短路逻辑与 | 是,左边为false时右边不执行 |
|| | boolean | 短路逻辑或 | 是,左边为true时右边不执行 |
& | boolean | 非短路逻辑与 | 否,两边都会执行 |
| | boolean | 非短路逻辑或 | 否,两边都会执行 |
^ | boolean | 逻辑异或 | 否,两边都会执行 |
& | 整数类型 | 按位与 | 不属于短路逻辑 |
| | 整数类型 | 按位或 | 不属于短路逻辑 |
^ | 整数类型 | 按位异或 | 不属于短路逻辑 |
~ | 整数类型 | 按位取反 | 不属于短路逻辑 |
这里的“整数类型”包括byte、short、char、int、long。其中byte、short、char参与位运算时会先提升成int。
二、&&和||:短路的意义是“右边可能不执行”
&&是短路逻辑与:左边已经是false时,整个表达式一定是false,右边不会再算。
示例代码(Java):短路与避免空指针
Strings=null;System.out.println(s!=null&&s.length()>0);// false这段代码不会抛空指针异常,因为s != null已经是false,s.length()根本不会执行。
||是短路逻辑或:左边已经是true时,整个表达式一定是true,右边不会再算。
示例代码(Java):短路或跳过右侧判断
intscore=95;if(score>=90||score==100){System.out.println("excellent");}这里score >= 90已经为true,后面的score == 100不需要再判断。短路不是语法小优化,而是很多保护性写法成立的原因。
常见写法:
示例代码(Java):常见保护性判断
if(user!=null&&user.isActive()){// 先防 null,再访问对象方法}if(list==null||list.isEmpty()){// list 为 null 时,不会继续调用 isEmpty()}判断条件时,默认选择&&、||。这不是因为它们“更高级”,而是它们更符合条件判断的预期:前面的条件已经能决定结果时,后面的条件就不应该再制造副作用或异常。
三、&和|放在 boolean 上:能用,但不短路
&、|不只属于位运算。它们也可以作用在 boolean 上:
示例代码(Java):boolean 上的非短路运算
booleana=false;booleanb=true;System.out.println(a&b);// falseSystem.out.println(a|b);// true结果看起来和&&、||很像,但执行过程不一样:&、|会把左右两边都算完。
示例代码(Java):单个&不会短路
Strings=null;System.out.println(s!=null&s.length()>0);这段代码会抛NullPointerException。虽然左边s != null是false,但单个&不短路,右边的s.length()仍然会执行。
|也一样:
示例代码(Java):|会执行右侧方法
staticbooleanlogAndReturnTrue(){System.out.println("right side executed");returntrue;}publicstaticvoidmain(String[]args){booleanleft=true;System.out.println(left||logAndReturnTrue());// 右边不执行System.out.println(left|logAndReturnTrue());// 右边会执行}所以在普通条件判断里,看到单个&、|要先警惕:它可能不是作者想要的短路逻辑。
^放在 boolean 上表示逻辑异或:左右刚好一个为true,结果才是true。
示例代码(Java):boolean 异或
System.out.println(true^false);// trueSystem.out.println(true^true);// false它也不会短路,因为异或必须知道左右两边的值才能判断“是否不同”。
四、&、|、^、~放在整数上:逐位计算
位运算不是在比较“整个数真不真”,而是在比较每一位二进制。
1. 按位与&
对应位都为1,结果位才是1。
示例代码(Java):按位与
System.out.println(3&5);// 1二进制过程(text):3 & 5 的逐位结果
3 -> 0011 5 -> 0101 & 0001 -> 1常见用途是检查某一位是否存在:
示例代码(Java):用掩码检查权限位
intREAD=1;// 0001intWRITE=1<<1;// 0010intpermission=READ|WRITE;booleancanRead=(permission&READ)!=0;注意括号不能省。!=的优先级高于&,如果写成permission & READ != 0,会先算READ != 0,表达式就变成int & boolean,直接编译失败。
2. 按位或|
对应位只要有一个是1,结果位就是1。
示例代码(Java):按位或
System.out.println(6|2);// 6二进制过程(text):6 | 2 的逐位结果
6 -> 0110 2 -> 0010 | 0110 -> 6常见用途是合并标志位:
示例代码(Java):合并多个标志位
intREAD=1;// 0001intWRITE=1<<1;// 0010intEXEC=1<<2;// 0100intpermission=READ|WRITE;// 同时拥有 READ 和 WRITEpermission=permission|EXEC;3. 按位异或^
对应位不同,结果位才是1。
示例代码(Java):按位异或
System.out.println(5^9);// 12二进制过程(text):5 ^ 9 的逐位结果
5 -> 0101 9 -> 1001 ^ 1100 -> 12异或常见于“切换某一位”的场景:同一位异或1会翻转,异或0会保持不变。
示例代码(Java):用异或切换某一位
intflag=0b0101;intmask=0b0001;System.out.println(flag^mask);// 0b01004. 按位取反~
~会把每一位都翻转,0变1,1变0。
示例代码(Java):按位取反
System.out.println(~5);// -6这个结果看起来反直觉,是因为 Java 的int使用 32 位二进制补码表示。对任意int x,都有:
规律说明(text):按位取反和负数的关系
~x == -x - 1所以~5等于-6。
五、移位运算:移动的是二进制位
移位运算只用于整数类型。
| 运算符 | 名称 | 高位或低位如何补 |
|---|---|---|
<< | 左移 | 右侧补0 |
>> | 有符号右移 | 左侧补符号位,正数补0,负数补1 |
>>> | 无符号右移 | 左侧一律补0 |
左移常能看成乘以2的若干次方:
示例代码(Java):左移两位
System.out.println(5<<2);// 20二进制过程(text):左移后的位变化
0000 0101 -> 0001 0100右移要更谨慎。对非负数,>> n通常像除以2^n:
示例代码(Java):非负数右移
System.out.println(15>>2);// 3但对负数,不要简单等同于/。Java 的整数除法向0截断,而>>会保留符号位:
示例代码(Java):负数右移和整数除法的差异
System.out.println(-5/2);// -2System.out.println(-5>>1);// -3>>>不保留符号位,左侧永远补0。因此负数无符号右移后,常会变成很大的正数:
示例代码(Java):有符号右移和无符号右移
System.out.println(-6>>3);// -1System.out.println(-6>>>3);// 536870911初学阶段不要为了“看起来更底层”而用移位替代乘除。只有在处理掩码、哈希、编码、底层协议、集合源码这类明确依赖二进制布局的场景里,移位才是自然表达。
六、优先级:能加括号就别考验读者
逻辑运算和位运算的优先级大致可以记成:
优先级速查(text):逻辑与位运算从高到低
~、! // 一元运算,优先级高 <<、>>、>>> // 移位 <、<=、>、>= // 关系比较 ==、!= // 相等比较 & // 按位与 / 非短路逻辑与 ^ // 异或 | // 按位或 / 非短路逻辑或 && // 短路逻辑与 || // 短路逻辑或这能解释一些表达式为什么能按预期运行:
示例代码(Java):不写括号时的表达式
booleanresult=x<y&&!z;它等价于:
示例代码(Java):加括号后的等价表达式
booleanresult=(x<y)&&(!z);但工程代码里,不建议把优先级当成炫技空间。尤其是位运算和比较混在一起时,括号会让语义清楚很多:
示例代码(Java):位运算和比较混用时加括号
booleanhasRead=(permission&READ)!=0;intcombined=READ|WRITE|EXEC;intthirdBit=value&(1<<2);判断规则很简单:如果读者需要停下来回忆优先级表,括号就应该出现。
七、实战判断口诀
遇到&&、||、&、|、^、~、移位运算时,可以按这几个问题判断:
- 是不是条件判断?
- 是:优先使用
&&、||。
- 是:优先使用
- 右边是否可能有空指针、数组越界、方法副作用?
- 是:更要使用短路逻辑。
- 操作数是不是整数,而且你关心的是二进制的某一位?
- 是:使用位运算。
- 是不是要表示权限、状态、掩码、哈希扰动、编码解码?
- 是:位运算可能合适。
- 只是想把两个 boolean 条件连起来?
- 不要随手写单个
&、|。
- 不要随手写单个
最容易记错的一句话是:
记忆口诀(text):一句话区分短路逻辑和位运算
短路逻辑看“要不要继续算”,位运算看“每一位怎么算”。&&、||解决的是控制流问题;&、|、^、~、<<、>>、>>>解决的是二进制表示问题。把这个边界立住,空指针保护、条件副作用、权限掩码和移位计算就不会混成一团。