非短路逻辑运算符存在的原因

41
当用于布尔操作数时,&|成为JLS第15.22.2节中的逻辑运算符。然而,与&&||不同的是,它们不会短路;它们总是评估双方。我有一个愚蠢的问题:既然我们有更有效的短路逻辑运算符(&&||),为什么还保留效率较低的非短路逻辑运算符(&|)?我的意思是,相对于短路逻辑运算符,使用非短路逻辑运算符总是评估两侧的真实用途是什么?换句话说,使用非短路逻辑运算符始终评估双方的使用方法是什么?

7
不是关于“效率”,它们具有不同的“语义”。 - Johan Sjöberg
2
这就是C所做的事情。实际上,在某些情况下,它们可能会更快,因为它们可以减少分支。在评估rhs时也可能会产生副作用(不好)。 - Tom Hawtin - tackline
1
@TomHawtin-tackline:C语言不支持短路运算符&|。与Java一样,它支持短路运算符&&||,但不支持&| - T.J. Crowder
位运算符在Java中对布尔值的影响 - wmz
短路运算符并不总是更高效的,详见代码效率 - Johnny
显示剩余3条评论
6个回答

33

更新的答案:

抱歉,尽管你的问题中提到了"逻辑(logical)"这个词,但我错过了它。(我已经修改了一下,加强了它的重点。)

考虑这样一��情况,即使左侧表达式的值为truefalse,您仍希望任何副作用始终发生。例如,对比如下:

if (foo() & bar()) {
    // Only call this if both operations returned true
}

使用

if (foo() && bar()) {
    // Only call this if both operations returned true
}

假设我们希望无论foo返回truefalse,都可以产生效果。在上面的第一个例子中,我知道bar总会被调用并起作用。而在后者中,bar可能会被调用,也可能不会被调用。如果没有非短路版本,我们就必须使用临时变量:

boolean fooResult, barResult;
fooResult = foo();
barResult = bar();
if (fooResult && barResult) {
    // ...
}
你可能会认为(我也许会这么认为)你应该无论如何都这样做,因为很容易误读 if (foo() & bar())。但是现在有了一个实用的理由来使用非短路版本。

3
在Java中,如果应用于布尔类型,& 和 | 不会使用位运算。 - Suzan Cioc
抱歉,如果可以的话,我会取消踩的。我只是因为你错了才试了一下,你同意吗?:> - Suzan Cioc
@SuzanCioc:谢谢,我在问题中漏看了“逻辑”这个词!已更新答案并编辑了问题,以帮助其他人避免同样的错误。 - T.J. Crowder
实际上,不进行短路处理是有效率原因的:分支可能很昂贵,如果两个条件都很便宜,使用 &| 实际上可能更快。除非 a) 您真的非常关注性能,并且 b) 您可以生成基准数据来支持您,否则我不建议这样做。 - Louis Wasserman
依赖于“非短路”逻辑(尤其是不是在所有地方,而是在某些地方)对我来说似乎是代码设计上的缺陷。这会让你记住表达式的所有元素都会产生影响的事实。 - Nikita Barishok

16

有时候布尔表达式的组成部分涉及到你想在所有情况下执行的操作。考虑以下检查密码有效性的示例:

while ( !password.isValid() & (attempts++ < MAX_ATTEMPTS) ) {

    // re-prompt

}
如果由于短路而未对第二个条件进行评估,则不会增加attempts。因此,这样可以增强程序员的灵活性。

10
如果改变顺序,这也可以用 && 写成 :) - Suzan Cioc

2
我的案例(C++):
void setFields(Parameters bundle)
{
  if (setIfDifferent(&field1, bundle.value1) | 
      setIfDifferent(&field2, bundle.value2) |
      setIfDifferent(&field3, bundle.value3)) {
    storeState();
  }
}

setIfDifferent()方法用于设置对象的字段值。如果新值与原值不同,则设置新值并返回true;如果新值与原值相同,则返回false。因此,我们希望尝试设置所有字段值,如果有任何一个字段值已更改,则存储新对象状态。


1

在逻辑表达式中可能会有一些副作用,例如您可以同时进行赋值和检查。如果只有一个部分被评估,则可能会出现错误。

现在无法记住好的例子,但记得我有时需要“非短路”运算符。

嗯...下面是错误的例子,如果没有“非短路”OR,它将无法工作:

if( (object1=getInstance1()).getNumber() == 1 || (object2=getInstance2()).getNumber() == 2 ) {

    // do something which requires bot object1 and object2 assigned

}

指出“逻辑表达式中的一些副作用”加1。 - ironwood

0

从技术上讲,&和|不是逻辑运算符,它们是按位运算符,当与布尔值相关联时,它们变成逻辑运算符。

有时候,您可能希望在逻辑表达式中包含赋值表达式。

比如:

if(a = (checkForSomeCondition()) | b = checkForAnotherCondition())
{
 //now do something here with a and b that has been cached
}

如果我使用了||,我将无法执行上述检查,并且必须将赋值拆分为单独的语句。在应用程序开发期间,我从未遇到过这样的情况,但在编写算法时遇到了几次。

当然,您可以对逻辑表达式使用一元运算符或通过引用将变量传递到谓词中,但这些似乎比上述情况更少见。


0
在我的情况下,我有两种方法来比较两个不同但相关的对象(Object2是Object1的属性),以查看是否有任何更改。如果更新了其中任何一个对象,则需要进行更新,但必须评估两个对象,以便在两者都更改时修改这些对象。因此,需要进行单管道“OR”比较。
例如:
if (compare(object1, currentObject1) | comparison(object2, currentObject2)) {
    updateObject1(object1);
}

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