短路运算符和非短路运算符的区别

8
我理解下面的区别(至少对于Java):

静态绑定是指在编译时确定方法调用,而动态绑定是指在运行时确定方法调用。

if( true || false ) // short-circuiting boolean operator
if( true | false )  // non-short-circuiting boolean operator

但我的问题是,当你处理布尔表达式时,使用非短路运算符是否有任何理由?是否存在一些性能优势或用途,而不会被认为是不好的做法?


1
你不会意外地使用 | 作为位运算符;两个操作数必须是布尔或整数。在条件表达式中,你只能使用布尔类型。 - cHao
@cHao 是的,没错。我在看到你的评论之前就把它删除了,因为它与实际问题并不是很相关。 - kin3tik
可能是非短路逻辑运算符存在的原因的重复。 - fredoverflow
根据我的经验,98%的非短路运算符都是打字错误,而剩下的2%则是糟糕的设计或危险的代码。这太容易被忽略了。想象一下5年后维护你代码的开发人员。 - Agoston Horvath
9个回答

12

如果您在某种程度上依赖于函数的副作用,那么您可能希望使用非短路运算符。例如:

boolean isBig(String text) {
  System.out.println(text);
  return text.length() > 10;
}

...
if( isBig(string1) || isBig(string2) ){
   ...
}
如果你不关心println是否被执行,那么你应该像上面那样使用短路操作。然而,如果你希望两个字符串总是被打印出来(因此依赖副作用),那么你需要使用非短路操作符。
实际上,在编程中几乎总是要使用短路操作符。依赖表达式中的副作用通常是不好的编程实践。
有一个例外是在非常底层或对性能敏感的代码中。短路运算符可能会稍微慢一些,因为它们会在程序执行中导致分支。此外,使用位运算符允许您进行32或64个并行布尔运算作为单个整数操作,这非常快速。

啊,那是一个我没有考虑过的好点。但是依赖这样的效果通常被认为是一种不好的做法,不是吗? - kin3tik
是的,我在看到你的评论之前就已经将那个点添加到答案中了!这确实是不好的做法。 - Eamonn O'Brien-Strain
通过添加的编辑,这似乎是我问题的最佳答案(涵盖了右侧副作用和偶尔的性能优势)。 - kin3tik

10

如果你的代码性能足够敏感并且操作足够便宜,那么使用非短路运算符可能会更快。这是因为使用 || 需要执行分支,并且分支预测错误可能非常昂贵。而使用 | 则执行计算和检查变量可以更快,避免了分支预测错误。

注意:这是一种微优化,除非被多次调用,否则很少看到差异。


理论上,JIT 可以将其中一个优化为另一个,其中 RHS 没有副作用,因此在完美的世界中,性能不应该是一个考虑因素(您始终可以使用 || 进行无副作用的比较)。例如,Guava 的 LongMath 类确实使用了这种优化来进行溢出检查:(a ^ b) < 0 | (a ^ result) >= 0。我还没有检查 Hotspot 是否足够聪明,可以 实际 编译两种情况下的相同代码。就像三元运算符可能意味着无分支代码与 if/else 相对应,但是一个好的编译器会在两种情况下生成相同的代码。 - BeeOnRope
@BeeOnRope 在许多情况下,JIT 足够聪明,可以使用 条件移动 汇编指令。例如,这意味着 max/min a > b ? a : b 表达式不需要分支。 - Peter Lawrey
确实 - 我的评论更多地涉及优化如何使性能建议过时。特别是,使用没有优化的gcc,if/else总是生成分支,三元运算符生成CMOV(如果可能,例如选择原语)。鉴于此,当分支不可预测时,您可能会开始建议使用?:,因为编译器会以不同的方式处理它们。但是,一旦您打开优化,差异就消失了:两者都编译为无分支代码。我怀疑类似的等价关系也适用于||| - BeeOnRope

6
short-circuit是指如果右侧不必要,则不会评估右侧。例如,如果&&左侧为false,则无需评估右侧,而||如果左侧为true,则无需评估右侧。 non-short总是评估两个方面。
显然,short-circuit运算符有好处。
非短路的好处可以在这里找到:Are there good uses for non-short-circuiting logical (boolean) operators in Java/Scala? 也请考虑以下示例。
  while (status){ // status is boolean value
          if(!a | result){// a and result are boolean value
              result=getResult(); // result can change time to time
          } 
      }

现在我们需要检查双方。


我理解它们的区别,我的问题更关注在仅处理布尔值时使用 | 的好处。 - kin3tik
@kin3tik,我在我的答案中添加了更多信息。 - Ruchira Gayan Ranaweera

6
我的问题更关注只在处理布尔值时使用|的好处。
考虑以下情况:
while ( !credentialvalid() | (loginAttempts++ < MAX) ) {

    // tell something to user.

}

在这种情况下,| 是必需的,因为我还需要增加尝试次数 :)

这是一个(非常)晚的评论,但我建议不要以这种方式使用 |。它不直观,如果必须始终执行loginAttempts增量,则最好将其移出条件。 - Craig Taylor

5

在条件语句中,唯一不应使用非短路运算符的地方是当您希望执行第二个语句时,这通常不是情况。

,使用非短路运算符没有性能优势,但使用短路运算符绝对有好处。


3
第三个声明
if( 10 | 11 )       // bitwise operator

将会产生编译错误。if语句中只能放置布尔值。

如果您使用Eclipse等IDE,它将自动显示第二个表达式即“||”之后的死代码,用于短路布尔运算符。


1
你说得对。我会从问题中删除那行,因为它并不是很相关。 - kin3tik

3
简单的布尔表达式中,使用符号“|”有时比“||”更快。有时候,您希望表达式被统一计算(例如在索引值中有一个“++”时)。
您不能意外地“混合”布尔“|”和位“|”,因为您不能混合布尔和整数操作数。但是,当您本意使用“||”时,您可能会意外地使用“|”。感谢上帝这不是C语言,否则您可能会意外地使用“=”而非“==”。

{ boolean b = false; if (b = true) { /*do stuff*/ } } 是被允许的,并且会执行代码块中的操作。这有点儿影响了你最后一句话的意思。 :) - cHao
@cHao - 是的,但在Java中,你不能说if (intVar1 = intVar2)并通过编译器。然而,C会欣然接受它。(而且你的例子是为什么应该避免像boolValue == true (or false)这样的表达式而使用boolValue!boolValue的原因之一。) - Hot Licks

2

如果您在使用过程中,

  if(true || /*someStatementHere*/)

如果第一个条件为真,整个if块将为真,因此不需要检查其他条件。

简单地说,在短路运算符的情况下,如果第一个条件给出结果,则不会评估右侧操作数。


1
我理解它们的不同之处,我的问题更侧重于在仅处理布尔值时使用|的好处是什么。 - kin3tik
你能得到的唯一好处是,短路运算符不会评估另一个条件。 - Prasad Kharkar
2
你正在回答的问题有点与被问的问题相反。我们已经知道 || 短路运算符了。问题是,是否存在一个合理的理由而更喜欢使用 | 而不是 || 呢? - cHao

1
我不知道为什么大家都说按位运算只能在布尔操作数上执行。我告诉你,按位运算符可以处理操作数的类型,并根据操作数返回相同类型的值。只需将按位运算符视为数学“+”运算符,它可以添加2个数字。按位运算也是这样。
如果(10 | 11)不会导致编译错误。如果结果为0,则为false,否则为true。
按位运算符不会短路,因为按位运算始终需要两个操作数才能执行。请注意,按位运算使用操作数而不是布尔条件。
语法:
   for bitwise :  (operand_1) bitwise_operator (operand_2)
                 for example: ( 2 ) | ( 3 )

   for LOGICAL :  (Boolean_condition_1) LOGICAL_operator (Boolean_condition_2)
                 for example: ( true ) || ( false )

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