为啥我总是要使用 ||而不是 |和 && 而不是 &?

Posted

技术标签:

【中文标题】为啥我总是要使用 ||而不是 |和 && 而不是 &?【英文标题】:Why should I always use || instead of | and && instead of &?为什么我总是要使用 ||而不是 |和 && 而不是 &? 【发布时间】:2013-12-07 18:59:15 【问题描述】:

自从我开始编写 Java 以来,我注意到每个人都在使用 &&|| 而不是 &|。这是什么原因?我一直在使用&&||,因为我不知道你可以在布尔值上使用&|

class Astatic
  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

使用||&& 是否会以任何方式改进我的代码?


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

【问题讨论】:

这样做的一个原因是它引入了侧通道... 看看 Erlang。它不进行短路评估,因为它引入了不确定性/不可预测性。短路评估不利于测试。 投反对票的原因是什么?这怎么不是一个有效的问题? 【参考方案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"); 有什么问题?我个人认为最后一个最容易阅读,而且仍然很短。我永远不会想出你做的例子。 您如何在第二个成语中添加相当于整个else 子句的内容?关于第一个,也许它对您个人来说更好,但大多数熟悉 &&|| 的人仍然害怕 Elvis 运算符作为不可读性的顶峰。 哦,现在我注意到您的 if (a != null & b != null) if (a.equals("b") | & z.equals("y")) 甚至都不正确:如果 either a"b" 或 i> z"y"。仅当 both 均非 null 且无法重写以匹配此版本时,您的版本才会打印。 哎呀。好吧: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("correct"); 我并不是说||/&& 没有用,我是在问为什么我应该总是使用它。当我问这个问题时,我认为它们是多余的,因为我不知道较短的版本不会短路。【参考方案2】:

正如已经指出的,&&|| 进行短路评估。 &| 使用布尔操作数,执行相同的操作,但无论第一个操作数的值如何,都会计算两个操作数。

在某些情况下,使用它很重要,例如当左操作数是对右手操作数进行无异常评估的先决条件时。

在大多数情况下,这无关紧要。在这些情况下,我使用&&|| 以提高可读性,因为这是更常见的选择。将&| 与布尔操作数一起使用会使大多数Java 程序员不得不停下来思考,并问自己是否有特定的原因。我怀疑它可能因为继承自 C 和 C++ 而变得如此普遍。

【讨论】:

我想说的是,尽管在很多情况下这并不重要,但没有动机不使用条件运算符作为默认运算符,而且显然 不使用普通运算符作为默认值的动机。【参考方案3】:

认为短路评估是强制性的,因为性能很弱。您可以提出一个几乎同样弱的论点:短路评估会鼓励额外的不确定性和旁道。随机优化或随机丢失非确定性/边信道,我更喜欢后者。

剩下的唯一论点是:如果您的代码库依赖于短路行为,例如 Habib (if (obj != null && obj.ID == 1)) 描述的技巧,那么开始混合 & 可能不是一个好主意,因为程序员可能会将其与&& 混淆。即,如果您在任何地方都使用&/|,但只在少数依赖短路评估的地方使用&&,则可能有人会出错并只输入&,这可能很难发现.在这种情况下,如果您依靠 && 来获得性能,而不是像 null 那样检查,那么当有人不小心单独放置 & 时可能更难发现,因为不会有堆栈跟踪。

【讨论】:

Short-circuit evaluatian 不是强制,但它作为默认值绝对正确:它要么不会造成伤害,要么提供真正的性能/正确性优势。不过,我认为您低估了性能论点。许多布尔操作数涉及非平凡的计算/等待外部资源/同步等。我经常发现自己仔细排序子句以延迟重量级调用,直到证明有必要。【参考方案4】:

||&& 使用 short circuit evaluation

来自same article

短路评估、最小评估或麦卡锡评估 表示某些编程中某些布尔运算符的语义 仅执行或评估第二个参数的语言 如果第一个参数不足以确定 表达

假设你有一个对象,如果你想检查它的某个属性是否有一些值:

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())


现在在上面的代码 sn-p 中,假设您有一个带有方法 SomeTimeConsumingMethod 的对象,该方法需要花费大量时间进行处理并返回 truefalse。如果您使用单个|,它将评估这两个条件,因为第一个conditionAtrue,它也会处理obj.SomeTimeConsumingMethod。整个 if 语句的最终结果将是 true,因为使用了 | (OR)

如果你的条件是使用 double || (OR 逻辑运算符)

if(conditionA || obj.SomeTimeConsumingMethod())

那么第二个条件obj.SomeTimeConsumingMethod() 将不会被计算。这是短路,只是使您免于执行一些耗时的方法。不管从obj.SomeTimeConsumingMethod() 返回什么,最终结果仍然是true

【讨论】:

+1 不过如果能加几行或者解释一下就好了! @Habib:- 这更像是 Habib 的回答! ;) 但不能多次投票:( @HaroldR.Eason 这是 LISP 中的规范,不仅仅是 C 的后代。它既不可怕也不违反直觉——除非你的直觉有误。 所以如果我说得对,你是说你应该在任何地方使用&&,因为有时人们使用它来执行你描述的null 技巧?除了明确想要短路之外,您对在任何地方使用& 有何看法?我的网站确实存在需要严格防止侧信道攻击的案例(@HaroldR.Eason 的评论指出)。 @Dog 您希望在 >99% 的用例中使用短路语义,因此条件运算符是自然默认值。普通布尔运算符的使用通常表明缺乏经验,并且很少出于特定需求的动机。【参考方案5】:

虽然使用按位运算符可能会得到“正确”的结果,但请理解它们并不代表相同的事物。例如,这有效

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()== 进行比较的原因相同——您不希望读者猜测您的意图。它可能在某些情况下有效(例如将固定的硬编码字符串与== 进行比较),但它的格式很差,因为它是您要求实例相等而不是值相等,这通常是隐含的。并且使用按位运算符意味着您想要进行位操作,而不是一阶逻辑评估。

【讨论】:

实际上,有时| 可能适合布尔逻辑,if 一个人想要调用两个方法并执行一些代码,或者如果其中一个返回 true,则继续循环,但无论如何都必须调用这两种方法。 任何此类用法都应在代码站点上添加注释,说明需要同时运行这两种方法 @supercat:如果我应该在需要评估两个操作数时发表评论,那么我是否也应该评论仅当左操作数为 true 时才应评估右操作数(如 Habib 的示例) ?在我看来,两者都同样直观。 @Dog:我猜在大约 90% 的使用布尔或运算符的上下文中,在side 返回 true,大约 9% 的情况下是有害的,而 1% 的情况下是必要的。为 9% 的情况评论 || 的用法不如为 1% 的情况评论 | 的用法重要,因为有人猜测该用法代表 90% 的情况(这无关紧要) 可能不恰当地将 1% 大小写 | 更改为 ||,但没有理由将 9% 大小写 || 更改为 | @supercat 说得好——除了你严重高估了 1% :) 我不记得在我的整个编码生涯中我不得不使用普通运算符。一路上可能有过一两次。 @MarkoTopolnik:我并不想在数字上精确。我的观点是,| 正确且|| 错误的情况比|| 有效且优于| 的情况要少得多(无论是90:1 还是9,000:1 的比率确实没有没关系)。我不是特别喜欢do bool more = func1(); if (func2()) more=true; while(more); 这样的代码,因为如果代码将more 设置为false,然后将true 设置为,这将暗示代码决定停止并改变主意。此外,如果没有评论,即使more 为真,func2 是否需要运行也不清楚,并且...【参考方案6】:

&& 和 ||是捷径运算符。例如:

A && B 

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

A || B

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

您应该始终使用快捷操作符进行多条件评估。 |或 & 用于按位运算。

【讨论】:

在某些情况下,需要无条件运行多个方法并根据结果做出决定。非短路运算符对此很有用,如果有人在代码站点明确要求无条件调用方法【参考方案7】:

不要这么快做出假设。这是因为这不会编译:

  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;
  

【讨论】:

有趣的一点,但这不是不使用|的好理由。【参考方案8】:

在 Java 中(如果我没记错的话,还有 C/C++)中的单个“|”和“&”是位运算符,不是逻辑运算符,它们用于完全不同的事情。

你使用“&&”和“||”对于布尔语句

您使用“&”和“|”对于位操作,给定以下是二进制数

【讨论】:

【参考方案9】:

|| 是逻辑或,其中| 是按位运算或。与&&& 相同。如果您在 if 语句中,请使用 &&||,如果您正在执行位操作,请使用 |&

【讨论】:

'|'也是布尔参数的逻辑或,但它不做短路评估,所以即使第一个操作数为真,第二个操作数也会被评估。 '&' 和第一个返回 false 的操作数也是如此。 布尔值在逻辑上是单个位,因此它实际上是按位运算。为了成功完成操作,它必须知道左值和右值的实际值。因此,它没有短路的事实是布尔运算的副作用。 在 Java 中,boolean 是一个原子布尔值,与按位运算无关。 @MarkoTopolnik:AtomicBoolean 是原子的。你在说什么? @dog 我说的是boolean 值的原子性,正如我在上面的评论中已经非常清楚的那样。

以上是关于为啥我总是要使用 ||而不是 |和 && 而不是 &?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 And 而不是 AndAlso?

何时或为啥要使用右外连接而不是左连接?

Cocoa - 为啥使用 NSInteger 和 CGFloat 而不是使用 int 和 float,或者总是使用 NSNumber?

python&linux pip 总是尝试使用 python2.7 而不是 3.4

为啥要使用 Deque 而不是内置 Stack<>? [复制]

为啥要使用 Deque 而不是内置 Stack<>? [复制]