为什么我应该总是使用||而不是|,以及&&而不是&?

5
自从我开始编写Java程序以来,我注意到每个人都在使用&&||而不是&|。这是为什么呢?我一直在使用&&||,因为我不知道你可以在布尔值上使用&|
class A{static{
  boolean t = true;
  boolean f = false;
  System.out.println("&ff " + (f&&f) + " " + (f&f));
  System.out.println("&ft " + (f&&t) + " " + (f&t));
  System.out.println("&tf " + (t&&f) + " " + (t&f));
  System.out.println("&tt " + (t&&t) + " " + (t&t));
  System.out.println("|ff " + (f||f) + " " + (f|f));
  System.out.println("|ft " + (f||t) + " " + (f|t));
  System.out.println("|tf " + (t||f) + " " + (t|f));
  System.out.println("|tt " + (t||t) + " " + (t|t));
}}

就我所知,它们是相同的:
$ javac A.java && java A
&ff false false
&ft false false
&tf false false
&tt true true
|ff false false
|ft true true
|tf true true
|tt true true
Exception in thread "main" java.lang.NoSuchMethodError: main

使用 ||&& 能否改进我的代码?


作为一个简单的测试,我用短形式替换了数百个出现的情况,所有的单元测试仍然通过。


1
不做这件事的原因之一是它会引入侧信道… - Harold R. Eason
1
看看Erlang。它不执行短路评估,因为它引入了非确定性/不可预测性。短路评估对测试来说是不好的。 - Harold R. Eason
1
下投票的原因是什么?这不是一个有效的问题吗? - Dog
9个回答

16

||&& 使用 短路求值

来自 同一篇文章

短路求值、最小化求值或麦卡锡求值表示某些编程语言中一些布尔运算符的语义,其中仅当第一个参数不足以确定表达式的值时才执行或评估第二个参数

考虑你有一个对象并且想要检查它的一个属性是否具有某个值,如果你这样做:

if(obj != null & obj.ID == 1)

如果您的对象obj为空,那么您将会得到一个空引用异常。由于您使用了单个的&,第一个条件将会被评估为false,但它仍将继续执行下一个条件并引发空引用异常。
如果您使用了&&,那么第二个条件将永远不会被评估,因此不会出现异常。
if(obj != null && obj.ID == 1)

使用您的代码,您不会看到任何区别,但是在使用按位运算符|&与多个条件时,将不会有任何短路。

更多解释。


考虑以下代码:
Boolean conditionA = 2 > 1; //some condition which returns true
if(conditionA | obj.SomeTimeConsumingMethod()){

}

现在在上面的代码片段中,假设您有一个带有方法 SomeTimeConsumingMethod 的对象,该方法需要很长时间进行处理,并返回 true false 。 如果使用单个 | ,它将评估两个条件,因为第一个 conditionA true ,它也会处理 obj.SomeTimeConsumingMethod 。由于使用了 | (OR),因此整个 if 语句的最终结果将为 true

如果您的条件使用双重 || (OR逻辑运算符)

if(conditionA || obj.SomeTimeConsumingMethod())

然后第二个条件 obj.SomeTimeConsumingMethod() 将不会被评估。这就是短路,它刚刚帮你避免了执行一些耗时的方法。尽管从 obj.SomeTimeConsumingMethod() 返回什么结果,最终结果仍然是 true


2
+1 但如果您能添加几行或解释一下,那就太好了! - Rahul Tripathi
1
@HaroldR.Eason 这是 LISP 中的常规操作,不仅限于 C 的后代。这既不糟糕也不反直觉——除非您有错误的直觉。 - Marko Topolnik
1
所以如果我理解正确,你的意思是应该到处使用 &&,因为有时人们会用它来执行你描述的“null”技巧?那么你认为除了在明确想要短路的情况下,到处使用 & 怎么样?我的网站中确实有一些情况需要严格控制以防止侧信道攻击(由 @HaroldR.Eason 的评论指出)。 - Dog
1
但是使用按位 | 或 & 与多个条件可能会导致不希望的行为。这是错误的。语义已经在此处 [http://docs.oracle.com/javase/specs/jls/se5.0/html/expressions.html#15.22.2] 明确说明了。 - Harold R. Eason
2
@Dog 在大多数情况下,您希望使用短路语义,因此条件运算符是自然的默认选择。使用普通布尔运算符通常表明缺乏经验,并且只有在特定要求充分时才能很好地激发。 - Marko Topolnik
显示剩余29条评论

3

||是逻辑或运算符,其中|是位或运算符。同样的情况也适用于&&&。如果您在if语句中,请使用&&||,如果您正在进行位操作,请使用|&


“|”符号也可以用作布尔参数的逻辑或运算,但它不进行短路计算,因此即使第一个操作数为真,第二个操作数也会被计算。对于“&”和第一个操作数返回false的情况也是如此。 - isnot2bad
在Java中,boolean是一个原子Boolean值,与位运算无关。 - Marko Topolnik
1
@dog,我在谈论boolean值的原子性,正如我在上面的评论中已经非常清楚地表达了。 - Marko Topolnik
说实话,我还没有尝试过 - 我的IDE没有抱怨。无论它是否有效,它可能不会记录操作的意图,因此未通过“最小惊讶原则”测试。 - PaulProgrammer
@MarkoTopolnik:哈罗德发布的链接似乎意味着不同。"布尔逻辑运算符&,^和|" - Dog
显示剩余12条评论

3
正如已经指出的那样,&&||进行短路求值。而&|在布尔操作数下执行相同的操作,但无论第一个操作数的值如何都会评估两个操作数。
有些情况下使用哪种方法很重要,比如左操作数是右操作数无异常评估的先决条件时。
在大多数情况下,这并不重要。我在这些情况下使用&&||来提高可读性,因为这是更常见的选择。使用布尔操作数的&|可能会使大多数Java程序员停下来思考,并问自己是否有某些特定的原因。我怀疑这可能是从C和C++继承下来的普遍做法。

我认为,虽然在许多情况下这并不重要,但没有理由不将条件运算符作为默认运算符使用,而明显有理由不使用普通运算符作为默认运算符。 - Marko Topolnik

1
虽然您可能使用位运算符得到“正确”的结果,但请注意它们并不代表相同的事情。例如,这个可以工作。
public static void main(String[] args){
    int x = 5;
    int y = 9;
    System.out.println(x&y); // prints "1"
} 

然而,这不会。
public static void main(String[] args){
    int x = 5;
    int y = 9;
    System.out.println(x&&y); // Compile error: java: operator && cannot 
                              //                be applied to int,int
}

你不使用位运算符进行布尔逻辑的原因在于需要清楚代码要做什么(以及知道它实际上是否有效)。位运算符用于操作位值,而布尔运算符用于评估一阶逻辑语句。
虽然不同,但使用equals()与==进行比较的原因是相同的——你不希望读者猜测你想做什么。它可能在某些情况下起作用(例如使用==比较常量、硬编码字符串),但这是不好的形式,因为它所表达的是你正在请求实例相等性,而不是值相等性,通常是暗示的。而使用位运算符则意味着你想进行位操作,而不是一阶逻辑评估。

1
@supercat:如果两个操作数都需要被评估,那么我应该进行评论,那么当左操作数为“true”时才评估右操作数时,我也应该进行评论吗(就像Habib的示例中一样)?对我来说,这两种情况看起来同样直观。 - Dog
@Dog:我猜在大约90%的情况下,使用布尔或运算符时,右侧的计算会是无用但无害的时间浪费,而左侧返回true的情况下,大约9%的情况下会是有害的,在1%的情况下则是必要的。对于9%的情况,注释使用||并不像注释使用|的1%的情况那样重要,因为猜测使用表示90%的情况(无关紧要)的人可能会不适当地将1%的情况|更改为||,但没有理由将9%的情况||更改为| - supercat
@supercat说得好---只是你高估了1% :) 我在我的整个编程生涯中都不记得有一次必须使用纯运算符。也许一路上有一两个场合。 - Marko Topolnik
@MarkoTopolnik:我并不是在追求数字上的精确度。我的观点是,使用|正确而||错误的情况比||可行且优于|的情况要少得多(无论是90:1还是9,000:1的比例都无关紧要)。我不太喜欢像这样的代码do {bool more = func1(); if (func2()) more=true;} while(more);,因为如果代码将more设置为false,然后又设置为true,那就意味着代码决定停止了,然后又改变了主意。此外,在没有注释的情况下,也不清楚即使more为true,func2是否需要运行,以及... - supercat
@supercat 假设我认为留下评论很有意义,但个人倾向于避免它们,因为 a) 在阅读代码时会妨碍视线;b) 不能保证其中的陈述是正确的或最新的;c) 很难确定何时开始进行注释。 - Marko Topolnik
显示剩余8条评论

1

使用 || 和 && 有什么改进代码的作用吗?

让我举两个例子来展示如何使用 条件布尔运算符 可能会改善您的代码。比较一下:

if (a != null && a.equals("b") || z != null && z.equals("y"))
  System.out.println("correct");

使用

boolean b1 = a != null, b2 = z != null;
if (b1) b1 = b1 & a.equals("b");
if (b2) b2 = b2 & z.equals("y");
if (b1 | b2) System.out.println("correct");

我能想到的最好方法是仅依赖逻辑布尔运算符。

其次,假设你有两个需要执行的测试:simpleTest()heavyTest(),其中后者涉及进行HTTP请求并解析响应。你有

if (simpleTest() & heavyTest()) System.out.println("success");
else throw new IllegalStateException();

我想优化它。你更愿意做什么,这个:

if (simpleTest()) {
  if (heavyTest()) System.out.println("success");
  else throw new IllegalStateException("operation could not be completed");
} else throw new IllegalStateException("operation could not be completed"); 

这个:
boolean b = simpleTest();
if (b) b = b & heavyTest());
if (b) System.out.println("success");
else throw new IllegalStateException("operation could not be completed");

或者这个:
if (simpleTest() && heavyTest()) System.out.println("success");
else throw new IllegalStateException();

为什么我应该总是使用 || 而不是 |,以及 && 而不是 &?

你不应该总是使用它们,但如果你将它们作为你的默认,在99.9%的情况下,要么不会有问题,要么可以让你的代码更好。将逻辑运算符保留给那0.1%的情况,那里你确实需要它们的语义。


在本页面的其他地方已经提到,仅依赖于右操作数的条件评估会导致反直觉的代码。我拒绝接受这种关于直觉的观点,因为根据定义,直觉是一种学习技能。它是一种进化赋予你的工具,用于识别你经常遇到的情况中的模式,并迅速(而且潜意识地)得出正确的结论。学习语言最重要的方面之一就是获得正确的直觉。一个语言特性之所以被认为是不好的是因为对于第一次看到它的人来说它是反直觉的这个观点是不可被严肃对待的。


以下的代码有什么问题:if (a == null ? false : a.equals("b") | z == null ? false : z.equals("b")) System.out.println("correct");或者if (a != null & b != null) if (a.equals("b") | & z.equals("y")) System.out.println("correct");?我个人认为最后一个例子最易读,而且也很短。我自己想不出你给的那个例子。 - Dog
你如何在第二个习惯用语中添加相当于整体“else”子句?至于第一个习惯用语,也许它对你个人来说更好,但是大多数熟悉“&&”和“||”的人仍然认为Elvis运算符是不可读性的顶峰。 - Marko Topolnik
哦,现在我注意到你的 if (a != null & b != null) if (a.equals("b") | & z.equals("y")) 甚至不正确:我的版本将打印如果 a"b" 或者 z"y" 中的任意一个。你的版本只会在两个都非空的情况下打印,并且无法重写以匹配此条件。 - Marko Topolnik
哎呀,好吧:if ("b".equals(a) || "y".equals("z"))。不够通用吗?boolean correct; if (a != null) if (a.equals("b")) correct = true; if (z != null) if (z.equals("y")) correct = true; if (correct) System.out.println("正确"); - Dog
@Dog 你不应该_总是_使用它们。然而,大多数情况下,我们只关心回答以下两个问题之一:1)所有条件都是真的吗?2)任何一个条件都是真的吗?在这两种情况下,短路是必要的,因为对于1),一旦你找到一个“假”的条件,你就回答了你的问题;对于2),一旦你找到一个“真”的条件,你就回答了你的问题。在回答问题后继续评估条件是毫无意义的。 - DannyMo
显示剩余2条评论

1
主张短路评估是必要的,因为性能不充分。你可以提出一个几乎同样薄弱的观点: 短路评估鼓励额外的非确定性和侧信道。随机优化或随机失去非确定性/侧信道,我更喜欢后者。唯一剩下的论点是: 如果你的代码库依赖于短路行为,例如 Habib 描述的技巧 (if (obj != null && obj.ID == 1)),那么开始混合使用 & 可能不是个好主意,因为程序员可能会将其与 && 弄混。也就是说,如果你在所有地方都使用 &/|,但只在一些地方使用 &&,以依赖短路评估,某人可能会犯一个错误并只放置 &,这可能很难发现。在这种情况下,如果你依赖于 && 来提高性能而不是像 null 检查之类的东西,当有人意外地放置 & 时,可能更难发现,因为没有堆栈跟踪。

短路求值并非“强制性”的,但作为默认选项绝对是正确的:它要么不会有任何影响,要么提供真正的性能/正确性优势。我认为你低估了性能方面的论点。许多布尔运算涉及到非平凡的计算/等待外部资源/同步等操作。我经常发现自己仔细地排列子句,延迟重量级调用直到被证明是必要的。 - Marko Topolnik

0

不要太快做出假设。这是因为它无法编译:

  static boolean f(boolean a) {
    boolean x = (true|a) ? a : x;
    return x;
  }

Bro.java:6: variable x might not have been initialized
    boolean x = (true|a) ? a : x;
                           ^

这将会:

  static boolean f(boolean a) {
    boolean x = (true||a) ? a : x;
    return x;
  }

有趣的观点,但这并不是不使用 | 的很好理由。 - Dog

0
在Java中(如果我记得正确的话,还有C / C ++),单个“|”和“&”是位运算符,而不是逻辑运算符,它们用于完全不同的事情。
你使用“&&”和“||”来表示布尔语句
你使用“&”和“|”进行位运算,给定下面是二进制数。

0

&& 和 || 是快捷操作符。例如:

A && B 

如果条件A为假,则不会评估条件B

A || B

如果条件A为真,则不会评估条件B。
在进行多个条件评估时,应始终使用快捷操作符。| 或 & 用于位运算。

1
有时候需要无条件地运行多个方法并根据结果做出决策。非短路运算符在这种情况下非常有用,如果在代码中明确注释需要无条件调用这些方法的话 - supercat

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