为什么我们通常使用 || 而不是 |?它们有什么区别?

240

我很好奇为什么我们通常在两个布尔值之间使用逻辑或||而不是位或|,尽管它们都能正常工作。

我的意思是,看看下面这个例子:

if(true  | true)  // pass
if(true  | false) // pass
if(false | true)  // pass
if(false | false) // no pass
if(true  || true)  // pass
if(true  || false) // pass
if(false || true)  // pass
if(false || false) // no pass

我们可以使用|代替||吗?同样的,&&&也一样。


18
大多数人忘记了 | 除了作为位运算符外,还可用作非短路布尔运算符。 - John Meagher
67
它们并不相同。请查看有关它们的教程,特别是有关短路求值与急切求值的部分。||&&是短路求值,而|&则是急切求值。 - Hovercraft Full Of Eels
4
纯好奇,你真的有使用非短路版本的情况吗?我几乎总是看到 &&||,但从未见过 &|。如果你要做一些依赖副作用的事情,我不明白为什么会使用类似 (a & b | c) 这样的表达式,因为很容易想到“我可以通过使用短路版本来优化这个表达式”。 - Mike Bailey
1
这是位运算布尔值与“逻辑”(即与数学“逻辑”有关)之间的区别。逻辑运算符“短路”的事实有些偶然,但非常重要。 - Hot Licks
2
当然,它们具有不同的优先级。 - Hot Licks
显示剩余2条评论
28个回答

370

如果使用 ||&& 这种形式,而不是 |& 这种形式的运算符,Java 将不会单独评估右操作数。

这取决于你是否想要短路求值 -- 大多数情况下你都想要短路求值

一个说明短路求值好处的好方法是考虑以下示例。

Boolean b = true;
if(b || foo.timeConsumingCall())
{
   //we entered without calling timeConsumingCall()
}

正如Jeremy和Peter所提到的,短路求值的另一个好处是空引用检查:

if(string != null && string.isEmpty())
{
    //we check for string being null before calling isEmpty()
}

更多信息


120
典型例子是foo != null && foo.hasBar() - Jeremy
1
如果您在回答中加入了@Jeremy评论中提到的使用|处理可能的空引用异常,那么这将是一个很好的答案。 - Peter Kelly
也要记住,&& 和 || 在机器代码层面上意味着分支指令(分支可能导致预测错误),因此如果你对性能超级挑剔,只有在实际需要时(foo != null && foo.hasBar())或更快时(b || foo.timeConsumingCall())才使用它们。不过99%的开发者不需要担心这种微观优化水平。 - Jonathan Dickinson
4
我很惊讶没有人提到当你想使用 | 时。我最常用的情况是在检查中修改变量,比如 (j>3 | ++i>3),或 (++i > 3 | modifiesGlobalAmongOtherThings() = true)。不过这种情况并不太常见。 - AndSoYouCode
9
另一个经典的例子是 string == null || string.isEmpty() ;) (注:原文中最后有一个笑脸符号,本翻译也保留了它) - Peter Lawrey
示例可能更改为 if(string != null && !string.isEmpty()),否则仅适用于空字符串 - 不确定这是否符合您的意图。 - Paul Bellora

92

|在布尔表达式中不会进行短路运算,||会在第一个操作数为true时停止计算,但是|不会。

此外,|可用于对字节/短整型/整型/长整型值执行按位或运算,||不能这样做。


提供一个完整的答案,我会接受它。到目前为止,你是第一个注意到这个方面的人。 - John Meagher
缺少 | 的按位运算特性。 - John Meagher

64

所以,为了举例进一步阐述其他答案,短路在以下防御性检查中至关重要:

if (foo == null || foo.isClosed()) {
    return;
}

if (bar != null && bar.isBlue()) {
    foo.doSomething();
}

使用|&代替可能导致在这里抛出一个NullPointerException


如果您应用了NullObject模式,它将不会(或者更确切地说,会否定答案)。此外,我认为检查foo是否为蓝色是foo内部的事情。如果它是蓝色的,则doSomething应该什么也不做。 - nicodemus13
@nicodemus13 - 说得好,虽然空对象模式有时候是可取的,但是函数体可能不一定是另一个对 foo 的调用。Peter Lawrey 的“典型例子”是最好的。 - Paul Bellora
@Khan:是的,我有点过于挑剔了,而且 Null 对象并不总是适用的。我已经下意识地养成了重构代码的习惯。你的回答并没有什么特别的问题。 - nicodemus13

42

逻辑运算符 ||&& 只会在必要时检查右侧的操作数,而 |& 则会每次都检查两侧的操作数。

例如:

int i = 12;
if (i == 10 & i < 9) // It will check if i == 10 and if i < 9
...
重写它:
int i = 12;
if (i == 10 && i < 9) // It will check if i == 10 and stop checking afterward because i != 10
...

另一个示例:

int i = 12;
if (i == 12 | i > 10) // It will check if i == 12 and it will check if i > 10
...

重写它:

int i = 12;
if (i == 12 || i > 10) // It will check if i == 12, it does, so it stops checking and executes what is in the if statement
...

20

还要注意一个常见陷阱:非惰性运算符的优先级高于惰性运算符,因此:

boolean a, b, c;
a || b && c; //resolves to a || (b && c)
a | b && c; //resolves to (a | b) && c

当混合它们时要小心。


15
除了短路运算外,另一件需要记住的事情是,在对可能不为0或1的值进行按位逻辑操作时,其含义与条件逻辑非常不同。虽然|||通常相同,但使用 &&&会得到非常不同的结果(例如:2&4为0/false,而2&&4为1/true)。
如果从函数获取的东西实际上是错误代码,并且您正在测试非0,则这可能非常重要。
这在Java中不是很重要,因为必须显式类型转换为布尔值或与0进行比较之类的操作,但在具有类似语法的其他语言(如C/C ++等)中,这可能会相当令人困惑。
另外,请注意,&和|仅适用于整数类型的值,而不能应用于可等效于布尔测试的所有内容。再次强调,在非Java语言中,有许多可以通过隐式!= 0比较(指针、浮点数、具有operator bool()的对象等)用作布尔值的东西,而按位运算符几乎在这些上下文中都是没有意义的。

3
我很高兴“至少有一个人”提到了位运算符存在的全部目的。 - ulidtko

10

只有当你的布尔表达式非常简单,并且通过不进行短路(即不要执行后面的语句)来节省时间的成本大于跳转的成本时,才会使用 |& 而不是 ||&&

然而,这是一种微小的优化,在除了最底层的代码之外很少有影响。


1
编译器在某些情况下是否会自动执行此操作,这是一个有趣的问题。 - starblue
很棒你提到了这个。我曾经通过将一些瓶颈逻辑重写为无分支(并在那里替换||为|)而获得了巨大的提升。无论如何,我们仍应该记住黄金法则:“先进行剖析,再进行优化”。分支预测也是我们CPU中存在的原因之一! - Kos
2
是的,我也见过|比||分支开销显着更快的情况,尤其是在没有或受限制的分支预测CPU上。这很少见,但并非闻所未闻。我的一个同事曾经因为他(正确地)使用了|而与一位承包商在一些代码中发生了回滚战争,而承包商一直认为那是“错误的”。 - fluffy
4
@Fluffy,故事的道德是,如果你做了一些棘手的事情,就需要注释说明为什么这样做,否则你的努力可能会被浪费掉。;) - Peter Lawrey
1
是的,最终他加了一条评论(根据我的建议,让承包商停止“修复”它),现在一切都好了。 - fluffy
显示剩余2条评论

8

|| 是逻辑或运算符,而 | 是按位或运算符。

boolean a = true;
boolean b = false;

if (a || b) {
}

int a = 0x0001;
a = a | 0x0002;

1
缺少 | 也是一种非短路布尔运算符。 - John Meagher
2
@John Meagher: 那是隐式的,因为它是按位操作。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
@L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳e̲̳̳ 你是如何在你的个人资料中将你的名字变成不同的样式的? - UdayKiran Pulipati

8

a | b:在任何情况下评估b

a || b:只有当a评估为假时,评估b


7
除了 | 是一个按位运算符之外,|| 还是一个短路运算符 - 当一个元素为假时,它将不会检查其他元素。
 if(something || someotherthing)
 if(something | someotherthing)

如果某个条件为真,|| 运算符不会对其它条件进行求值,而 | 运算符则会。如果 if 语句中的变量实际上是函数调用,使用 || 可能可以节省大量性能。


你为什么要在if语句中使用|呢?||是布尔值,而|不是,只有当你已经在处理两个布尔值时,|才会成为布尔值。 - FlySwat
这是获取所有内容的第一个答案。 - John Meagher
这个答案是不正确的。如果某个东西是FALSE,两个运算符都会继续执行下一个操作数。唯一的区别在于第一个操作数为true时才会出现差异。 - Michael Myers
很遗憾,它有一个绝对荒谬的例子。 - FlySwat
为了澄清之前(正确的)评论,如果您的右侧参数具有副作用并且必须在评估if时每次运行,则可以使用&或|。这不是一个好的编程实践,如果您发现自己以这种方式使用它,几乎可以肯定地增加了项目的长度,复杂性和错误率。 - Bill K
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接