java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java运算符&&、||、&、|与位运算

Java运算符易混点之&&、||、&、|与 到底差在哪详解

作者:超级苦力怕

这篇文章主要介绍了Java运算符易混点之&&、||、&、|与 到底差在哪的相关资料,强调在条件判断和位运算场景下的正确使用法,并提供示例代码帮助理解大家理解,需要的朋友可以参考下

前言

如果你经常把 && 和 &、|| 和 | 记混,这篇从“条件判断”和“二进制位处理”两个场景切开讲,顺着示例看会轻松很多。

一、先问一句:你在判断条件,还是在处理二进制位?

&&||&| 长得太像,所以最容易被混在一起记。真正的分界不是“一个符号还是两个符号”,而是你现在想做什么:

同一个符号在不同操作数类型下,含义会变:

运算符操作数类型含义是否短路
&&boolean短路逻辑与是,左边为 false 时右边不执行
||boolean短路逻辑或是,左边为 true 时右边不执行
&boolean非短路逻辑与否,两边都会执行
|boolean非短路逻辑或否,两边都会执行
^boolean逻辑异或否,两边都会执行
&整数类型按位与不属于短路逻辑
|整数类型按位或不属于短路逻辑
^整数类型按位异或不属于短路逻辑
~整数类型按位取反不属于短路逻辑

这里的“整数类型”包括 byteshortcharintlong。其中 byteshortchar 参与位运算时会先提升成 int

二、&&和||:短路的意义是“右边可能不执行”

&& 是短路逻辑与:左边已经是 false 时,整个表达式一定是 false,右边不会再算。

示例代码(Java):短路与避免空指针

String s = null;

System.out.println(s != null && s.length() > 0); // false

这段代码不会抛空指针异常,因为 s != null 已经是 falses.length() 根本不会执行。

|| 是短路逻辑或:左边已经是 true 时,整个表达式一定是 true,右边不会再算。

示例代码(Java):短路或跳过右侧判断

int score = 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 上的非短路运算

boolean a = false;
boolean b = true;

System.out.println(a & b); // false
System.out.println(a | b); // true

结果看起来和 &&|| 很像,但执行过程不一样:&| 会把左右两边都算完。

示例代码(Java):单个 & 不会短路

String s = null;

System.out.println(s != null & s.length() > 0);

这段代码会抛 NullPointerException。虽然左边 s != nullfalse,但单个 & 不短路,右边的 s.length() 仍然会执行。

| 也一样:

示例代码(Java):| 会执行右侧方法

static boolean logAndReturnTrue() {
    System.out.println("right side executed");
    return true;
}

public static void main(String[] args) {
    boolean left = true;

    System.out.println(left || logAndReturnTrue()); // 右边不执行
    System.out.println(left | logAndReturnTrue());  // 右边会执行
}

所以在普通条件判断里,看到单个 &| 要先警惕:它可能不是作者想要的短路逻辑。

^ 放在 boolean 上表示逻辑异或:左右刚好一个为 true,结果才是 true

示例代码(Java):boolean 异或

System.out.println(true ^ false); // true
System.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):用掩码检查权限位

int READ = 1;      // 0001
int WRITE = 1 << 1; // 0010

int permission = READ | WRITE;

boolean canRead = (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):合并多个标志位

int READ = 1;       // 0001
int WRITE = 1 << 1; // 0010
int EXEC = 1 << 2;  // 0100

int permission = READ | WRITE; // 同时拥有 READ 和 WRITE
permission = permission | EXEC;

3. 按位异或^

对应位不同,结果位才是 1

示例代码(Java):按位异或

System.out.println(5 ^ 9); // 12

二进制过程(text):5 ^ 9 的逐位结果

5  -> 0101
9  -> 1001
^     1100  -> 12

异或常见于“切换某一位”的场景:同一位异或 1 会翻转,异或 0 会保持不变。

示例代码(Java):用异或切换某一位

int flag = 0b0101;
int mask = 0b0001;

System.out.println(flag ^ mask); // 0b0100

4. 按位取反~

~ 会把每一位都翻转,0110

示例代码(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);  // -2
System.out.println(-5 >> 1); // -3

>>> 不保留符号位,左侧永远补 0。因此负数无符号右移后,常会变成很大的正数:

示例代码(Java):有符号右移和无符号右移

System.out.println(-6 >> 3);  // -1
System.out.println(-6 >>> 3); // 536870911

初学阶段不要为了“看起来更底层”而用移位替代乘除。只有在处理掩码、哈希、编码、底层协议、集合源码这类明确依赖二进制布局的场景里,移位才是自然表达。

六、优先级:能加括号就别考验读者

逻辑运算和位运算的优先级大致可以记成:

优先级速查(text):逻辑与位运算从高到低

~、!                  // 一元运算,优先级高
<<、>>、>>>            // 移位
<、<=、>、>=           // 关系比较
==、!=                // 相等比较
&                     // 按位与 / 非短路逻辑与
^                     // 异或
|                     // 按位或 / 非短路逻辑或
&&                    // 短路逻辑与
||                    // 短路逻辑或

这能解释一些表达式为什么能按预期运行:

示例代码(Java):不写括号时的表达式

boolean result = x < y && !z;

它等价于:

示例代码(Java):加括号后的等价表达式

boolean result = (x < y) && (!z);

但工程代码里,不建议把优先级当成炫技空间。尤其是位运算和比较混在一起时,括号会让语义清楚很多:

示例代码(Java):位运算和比较混用时加括号

boolean hasRead = (permission & READ) != 0;
int combined = READ | WRITE | EXEC;
int thirdBit = value & (1 << 2);

判断规则很简单:如果读者需要停下来回忆优先级表,括号就应该出现。

七、实战判断口诀

遇到 &&||&|^~、移位运算时,可以按这几个问题判断:

  1. 是不是条件判断?
    • 是:优先使用 &&||
  2. 右边是否可能有空指针、数组越界、方法副作用?
    • 是:更要使用短路逻辑。
  3. 操作数是不是整数,而且你关心的是二进制的某一位?
    • 是:使用位运算。
  4. 是不是要表示权限、状态、掩码、哈希扰动、编码解码?
    • 是:位运算可能合适。
  5. 只是想把两个 boolean 条件连起来?
    • 不要随手写单个 &|

最容易记错的一句话是:

记忆口诀(text):一句话区分短路逻辑和位运算

短路逻辑看“要不要继续算”,位运算看“每一位怎么算”。

&&|| 解决的是控制流问题;&|^~<<>>>>> 解决的是二进制表示问题。把这个边界立住,空指针保护、条件副作用、权限掩码和移位计算就不会混成一团。

到此这篇关于Java运算符易混点之&amp;&amp;、||、&amp;、|与 到底差在哪的文章就介绍到这了,更多相关Java运算符&amp;&amp;、||、&amp;、|与位运算内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文