&、| 和 ^ 运算符是位运算符还是逻辑运算符?

4
首先我学到了&|^是按位运算符,现在有人把它们称为逻辑运算符&&||,我完全困惑了 - 同一个运算符有两个名称?已经有逻辑运算符&&||,那么为什么还要使用&|^呢?

参见:https://dev59.com/gmgu5IYBdhLWcg3wUljt - assylias
4个回答

20
Java运算符&|^是按位运算符或逻辑运算符,取决于操作数的类型。如果操作数是整数,则运算符是按位运算符。如果它们是布尔值,则运算符是逻辑运算符。
这不仅是我的说法。JLS也是这样描述这些运算符的;请参见JLS 15.22
(这就像+表示加法或字符串连接一样,取决于操作数的类型。或者就像“玫瑰”既可以是花朵,也可以是淋浴喷头。或者“猫”既可以是毛茸茸的动物,也可以是UNIX命令。单词在不同的上下文中有不同的含义。这对编程语言中使用的符号也是如此。)

已经有逻辑运算符&&||,为什么要使用&|^

对于前两个运算符,是因为它们在操作数何时/是否被计算方面具有不同的语义。这两种不同的语义在不同的情况下是必需的;例如:

    boolean res = str != null && str.isEmpty();

对抗

    boolean res = foo() & bar();  // ... if I >>need<< to call both methods.
< p > ^ 运算符没有短路等效项,因为这根本就没有意义。


3
请看这个例子,位运算符作为逻辑运算符使用,虽然没有短路:http://www.anicehumble.com/2012/05/operator-precedence-101.html。不要忘记阅读有关“Neonatal C”的解释:http://cm.bell-labs.com/who/dmr/chist.html。 - Michael Buen
@Crackers他已经回答了。boolean => 逻辑,integer => 位运算。 - user207421

3

拥有语言参考文献是一回事,正确解释它又是另一回事。

我们需要正确解释事物。

即使Java文档说明&既可以是按位运算符也可以是逻辑运算符,我们也可以提出论点,认为自上古以来自C以来,&确实没有失去其逻辑运算符的魔力。也就是说,&首先是一个内在的逻辑运算符(尽管这是一个非短路的运算符)

&按字面+逻辑解析为逻辑操作。

为了证明这一点,自从C开始到现在(Java、C#、PHP等),这两行代码的行为都是相同的。

if (a == 1 && b)

if (a == 1 & b)

也就是说,编译器会将它们解释为这样:
if ( (a == 1) && (b) )

if ( (a == 1) & (b) )

即使变量ab都是整数,这个...

if (a == 1 & b)

...仍然会被解释为:

if ( (a == 1) & (b) )

因此,在不支持整数/布尔二元性的语言(例如Java和C#)上,这将导致编译错误:
if (a == 1 & b)

事实上,在上面的编译错误中,我们甚至可以认为 & 没有失去其逻辑(非短路)操作魔力,并且我们可以得出结论:Java延续了C的传统,使 & 仍然是一个逻辑操作。因此,我们可以说反过来,即可以通过应用括号将 & 重新用作位操作:
if ( a == (1 & b) )

所以在另一个平行宇宙中,有人可能会问如何将表达式&转换为位掩码操作。
引用如下:
如何使以下代码编译通过?我在JLS中读到&是一种按位运算符。a和b都是整数,但我不明白为什么以下位运算是Java中的编译错误:
if (a == 1 & b)
或者这样的问题:
为什么以下代码无法编译通过?我在JLS中读到&是一种按位运算符,在其操作数都是整数时使用。a和b都是整数,但我不明白为什么以下位运算是Java中的编译错误:
if (a == 1 & b)
事实上,如果已经有类似上述问题的现有stackoverflow问题询问如何在Java中执行掩码惯用语,我将不感到惊讶。
要使语言对该逻辑操作进行按位解释,我们必须在所有语言(C、Java、C#、PHP等)上执行以下操作:
if ( a == (1 & b) )

所以回答这个问题,不是因为JLS定义了这样的方式,而是因为Java(以及其他受C启发的语言)的&运算符实际上仍然是一个逻辑运算符,它保留了C的语法和语义。自从C语言诞生以来,就一直是这样的。
事情不是偶然发生的,JLS 15.22 不是偶然发生的,它周围有深厚的历史。
在另一个平行宇宙中,如果没有将&&引入到语言中,我们仍将使用&进行逻辑操作,今天可能会有人问一个问题:
“这是真的吗?我们可以使用逻辑运算符&进行位运算吗?”
& 不关心其操作数是整数还是布尔值。它仍然是一个逻辑运算符,一个非短路运算符。事实上,在Java(甚至是C中),强制使其成为位运算符的唯一方法是在其周围加上括号。即:
if ( a == (1 & b) )

想一想,如果C语言(以及任何复制其语法和语义的语言)没有引入&&,现在可能会有人问:

如何使用&进行位运算?

总之,首先Java的&本质上是一个逻辑运算符(非短路运算符),它不关心其操作数,即使两个操作数都是整数(例如掩码习惯用法),它也会像往常一样执行其业务(应用逻辑运算)。您只能通过应用括号来强制将其变为位运算。Java延续了C的传统

如果Java的&的操作数(整数1和整数变量b)都是整数,则应该编译此代码:

 int b = 7;
 int a = 1;

 if (a == 1 & b) ...

1
我的回答与其他答案相同。我知道Java在C / C ++中有前例。但是Java和|运算符之所以是它们的样子,是因为它们“有效”。实际上,由于它们没有借用C / C ++的语义,它们比C / C ++更好...因为它们更简单,更易于理解,而且更不易出错。这也意味着用历史来描述Java运算符实际上并不能1)回答问题,或者2)帮助理解。(使用混淆的术语也没有帮助)。 - Stephen C
这里有一个反例可以证明你所说的Java中的&|是“本质上”逻辑运算符的断言是错误的。a = b & 42;没有对ab进行类型定义,使得&成为逻辑运算符。很抱歉,但你的论点并不站得住脚,将整个段落加粗也无法改变这一点。 - Stephen C
顺便问一下,我的术语混乱吗?我很乐意学习的 :-) 使用正确的术语可以实现有效的沟通。 - Michael Buen
1
  1. 这个问题不是关于 if 操作符的。它涉及到 Java 中所有上下文中的操作符。 1a) 而且 JLS 在术语上没有对这些上下文进行任意区分。
  2. 混淆的术语评论指的是您与 JLS 的说法相矛盾。这使得您在 Java 上下文中的术语错误和令人困惑。JLS 所说的是具有决定性的。
- Stephen C
如果您回到原始问题,您会发现问题在于OP被不同人使用的看似矛盾的术语所困惑。唯一解决这种困惑的方法是指出JLS实际上对此的说法,并使用其(一致的)术语。 - Stephen C
显示剩余7条评论

1

&|在很久以前被用于两个目的,逻辑运算符和位运算符。如果你查看新生儿C语言(Java语言的模板),&|被用作逻辑运算符。

但是,由于在同一语句中消除位运算和逻辑运算的歧义非常令人困惑,这促使Dennis Ritchie创建了一个单独的运算符(&&||)用于逻辑运算符。

在这里检查“新生儿C”部分:http://cm.bell-labs.com/who/dmr/chist.html

您仍然可以将位运算符用作逻辑运算符,其保留的运算符优先级就是证据。阅读有关位运算符在新生儿C上作为逻辑运算符的过去历史

关于证据,我写了一篇博客文章比较逻辑运算符和位运算符。如果你在实际程序中对比它们,就会自然而然地发现所谓的位运算符仍然是逻辑运算符,这一点是不言自明的:http://www.anicehumble.com/2012/05/operator-precedence-101.html 我还回答了一个与你的问题相关的问题:What is the point of the logical operators in C? 因此,没错,位运算符也是逻辑运算符,尽管是非短路逻辑运算符的版本。

关于

已经有逻辑运算符&&、||,为什么还要使用&、|、^?

XOR 可以很容易地回答,它就像单选按钮,只允许一个,下面的代码返回 false。对于下面人为编造的代码示例,抱歉,已经证明同时喝啤酒和牛奶并不会有什么问题;-)

String areYouDiabetic = "Yes";
String areYouEatingCarbohydrate = "Yes";


boolean isAllowed = areYouDiabetic == "Yes" ^ areYouEatingCarbohydrate == "Yes";

System.out.println("Allowed: " + isAllowed);

对于XOR位运算符,没有短路等效的操作,因为需要评估表达式的两侧。

关于为什么需要使用&|位运算符作为逻辑运算符,坦率地说,你很难找到需要使用位运算符(也称为非短路逻辑运算符)作为逻辑运算符的情况。如果您想要实现一些副作用并使代码紧凑(主观),则可以使用位运算符(即非短路逻辑运算符)来进行非短路逻辑操作,例如:

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

    // re-prompt

}

以上内容可以重写为以下形式(去掉括号),并且与前面的代码完全具有相同的解释。
while ( !password.isValid() & attempts++ < MAX_ATTEMPTS ) {

    // re-prompt

}

去掉括号后,它仍然产生与带括号的相同解释,可以使逻辑运算符遗迹“&”更加明显。冒着听起来多余的风险,但我必须强调,非括号表达式不会被解释为这样:
while ( ( !password.isValid() & attempts++ ) < MAX_ATTEMPTS ) {

    // re-prompt

}

总之,使用 & 运算符(更普遍地称为位运算符,但实际上既可以是位运算符也可以是逻辑运算符(非短路))来实现副作用的非短路逻辑操作是聪明的(主观的),但并不被鼓励,这只是为了换取可读性而获得的一行节省效果。
示例来源:存在非短路逻辑运算符的原因

这个答案大部分是不正确的,而且离题了。&&和||运算符是由John McCarthy发明的,并不仅仅是为了减少混淆:它们与&和|有着不同的作用。 - user207421
@A.H.和EJP,同样的原则适用于Java继承了C的运算符及其优先级,Java没有偏离这一原则。同样的原则适用。我只是提供了一个背景,说明为什么Java的位运算符也是逻辑运算符,这可以追溯到C。 - Michael Buen
@StephenC 我已经知道了,我提供的是Java中的 & 可以作为逻辑运算符的背景。 - Michael Buen
@MichaelBuen - 我恐怕“背景”大多是你的想象。Java的&逻辑运算符可以用作逻辑运算符,因为它被定义为这样。同样适用于&位运算符。显然,Java从C中借鉴了语法,但它没有借鉴语义,这一点可以通过在Java中不能混合使用布尔和整数操作数来说明。 - Stephen C
@Crackers - 1)我没有给这个帖子或者你的回答点踩。2)你没有权利告诉我该怎么投票。 - Stephen C
显示剩余11条评论

0
Java 中的 byte 类型是有符号的,这可能会对位运算符造成问题。当负字节被扩展为 int 或 long 时,符号位会被复制到所有更高的位以保持解释值。例如:
byte b1=(byte)0xFB; // that is -5
byte b2=2;
int i = b1 | b2<<8;
System.out.println((int)b1); // This prints -5
System.out.println(i);       // This prints -5

原因:(int)b1在内部是0xFFFB,而b2<<8是0x0200,所以i将会是0xFFFB。

解决方案:

int i = (b1 & 0xFF) | (b2<<8 & 0xFF00);
System.out.println(i); // This prints 763 which is 0x2FB

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