为啥我总是要使用 ||而不是 |和 && 而不是 &?
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
的对象,该方法需要花费大量时间进行处理并返回 true
或 false
。如果您使用单个|
,它将评估这两个条件,因为第一个conditionA
是true
,它也会处理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
值的原子性,正如我在上面的评论中已经非常清楚的那样。以上是关于为啥我总是要使用 ||而不是 |和 && 而不是 &?的主要内容,如果未能解决你的问题,请参考以下文章
Cocoa - 为啥使用 NSInteger 和 CGFloat 而不是使用 int 和 float,或者总是使用 NSNumber?
python&linux pip 总是尝试使用 python2.7 而不是 3.4