为什么Java没有条件与和条件或的复合赋值运算符? (&&=, ||=)

94

因此,在布尔运算符上,Java有&|^&&||

让我们简要总结一下它们的作用:

对于" & ",如果两个操作数的值都为" true ",则结果值为" true ";否则,结果为" false "。
对于" | ",如果两个操作数的值都为" false ",则结果值为" false ";否则,结果为" true "。
对于" ^ ",如果操作数的值不同,则结果值为" true ";否则,结果为" false "。
" && "运算符类似于" & ",但仅在其左操作数的值为" true "时才评估其右操作数的值。
" || "运算符类似于" | ",但仅在其左操作数的值为" false "时才评估其右操作数的值。
现在,在这5个运算符中,有3个具有复合赋值版本,分别是" |= "、"&= "和" ^= "。那么我的问题很明显:为什么Java不提供" &&= "和" ||= "呢?我发现我比需要这些更需要" &= "和" |= "。
我认为"因为太长"不是一个好答案,因为Java有" >>>= "。这个省略必须有更好的原因。

来自15.26 赋值运算符

共有12个赋值运算符;[...] = *= /= %= += -= <<= >>= >>>= &= ^= |=


有人评论说如果实现&&=||=,那么它将是唯一不先评估右侧的运算符。我认为这种复合赋值运算符评估右侧的观念是错误的。
来自15.26.2 Compound Assignment Operators
复合赋值表达式E1 op = E2等同于E1 =(T)((E1)op(E2)),其中TE1的类型,除了E1只被评估一次。
作为证明,以下片段抛出一个NullPointerException,而不是ArrayIndexOutOfBoundsException
    int[] a = null;
    int[] b = {};
    a[0] += b[-1];

2
我选择第二个,没人在意 :P 另外,所有关于“为什么语言y没有x特性?”的问题应该问语言的设计者,而不是我们 :P - Federico klez Culloca
1
"&=" 是什么意思?有人可以告诉我吗? - Tarik
1
可能是为什么不存在"&&="运算符?的重复问题。 - Josh Lee
1
@jleedev:那个问题比较老,但这个问题有更多的投票和链接。我认为如果要合并的话,应该将旧的合并到这个问题上(是的,这是可以做到的)。 - polygenelubricants
如果它明确地询问技术原因,那么这不会是一个坏问题。不幸的是,它吸引了很多糟糕的答案,唯一不像抱怨的答案是该运算符在C++中也失败了,这应该是一个评论而不是答案 :( - Danubian Sailor
显示剩余2条评论
12个回答

33

原因

Java中,运算符&&=||=是不可用的,因为对于大多数开发人员来说,这些运算符具有以下特点:

  • 容易出错
  • 无用

&&=的示例

如果Java允许使用&&=运算符,则以下代码:

bool isOk = true; //becomes false when at least a function returns false
isOK &&= f1();
isOK &&= f2(); //we may expect f2() is called whatever the f1() returned value

等同于以下代码:

bool isOk = true;
if (isOK) isOk = f1();
if (isOK) isOk = f2(); //f2() is called only when f1() returns true

这段代码容易出现错误,因为许多开发人员会认为 f2() 总是会被调用,无论 f1() 返回什么值。它就像 bool isOk = f1() && f2();,只有当 f1() 返回 true 时才会调用 f2()

如果开发人员希望只有在 f1() 返回 true 时才调用 f2(),那么上面的第二个代码就更安全一些。

否则,使用 &= 就足够了,因为开发人员希望f2() 总是被调用:

同样的例子,但是针对 &=

bool isOk = true;
isOK &= f1();
isOK &= f2(); //f2() always called whatever the f1() returned value

此外,JVM应该将以上代码运行为以下代码:

bool isOk = true;
if (!f1())  isOk = false;
if (!f2())  isOk = false;  //f2() always called

比较&&&的结果

当应用于布尔值时,运算符&&&的结果是否相同?

让我们使用以下Java代码进行检查:

public class qalcdo {

    public static void main (String[] args) {
        test (true,  true);
        test (true,  false);
        test (false, false);
        test (false, true);
    }

    private static void test (boolean a, boolean b) {
        System.out.println (counter++ +  ") a=" + a + " and b=" + b);
        System.out.println ("a && b = " + (a && b));
        System.out.println ("a & b = "  + (a & b));
        System.out.println ("======================");
    }

    private static int counter = 1;
}

输出:

1) a=true and b=true
a && b = true
a & b = true
======================
2) a=true and b=false
a && b = false
a & b = false
======================
3) a=false and b=false
a && b = false
a & b = false
======================
4) a=false and b=true
a && b = false
a & b = false
======================

因此,对于布尔值,是的,我们可以用&来替换&&;-)
因此,最好使用&=而不是&&=

对于||=也是一样的

&&=相同的原因:
运算符|=||=更不容易出错。

如果开发人员希望在f1()返回true时不调用f2(),那么我建议使用以下替代方案:

// here a comment is required to explain that 
// f2() is not called when f1() returns false, and so on...
bool isOk = f1() || f2() || f3() || f4();

或者:

// here the following comments are not required 
// (the code is enough understandable)
bool isOk = false;
if (!isOK) isOk = f1();
if (!isOK) isOk = f2(); //f2() is not called when f1() returns false
if (!isOK) isOk = f3(); //f3() is not called when f1() or f2() return false
if (!isOK) isOk = f4(); //f4() is not called when ...

1
嗨@StriplingWarrior。我已经向我的同事Yannick核实了,他是我们最好的Java专家。我已经使用用于检查该点的Java代码源更新了我的答案。正如你所说的&&&得出相同的结果。非常感谢您的反馈。你喜欢我的回答吗?干杯。 - oHo
2
如果我想要非常快地执行这个操作怎么办?如果存在的话,&&= 操作符比 &= 操作符更快,所以你应该使用 if (a) a = b 来提高速度。 - adventurerOK
17
当你说“这不是我们想要的”的时候,我不同意。如果我写了isOK &&= f2();,我希望它能像&&一样短路。 - Tor Klingberg
4
我不同意你的说法,认为操作员可能会出错或没有用。使用复合赋值运算符是为了省略通常的 A = A op B ,因此每个人都非常清楚自己在做什么,并且可以自行处理其影响。如果你提出的理由确实是其缺失的原因,我认为这是不必要的指导。但是,我还是要感谢你添加了这一行代码 bool isOk = f1() || f2() || f3() || f4(); ,因为这正是我自己没能看到的。 - Franz B.
感谢@FranzB.的反馈:你是对的。我会尽快更新答案(目前没有足够的时间深入探讨这个话题)。干杯;-) - oHo
显示剩余4条评论

17

可能是因为类似下面这样的原因:

x = false;
x &&= someComplexExpression();

看起来好像是将值赋给x并且执行someComplexExpression(),但实际上这个表达式的求值取决于x的值,而语法中并没有明显体现。

Java的语法基于C,因此没有人觉得有必要添加这些运算符。无论如何,使用if语句可能更好一些。


43
我认为这不是一个好的答案,因为有人可能会认为 x() && y() 看起来 应该评估表达式的两侧。显然人们接受 && 是短路操作,所以 &&= 也应该遵循这个规则。 - polygenelubricants
2
@jleedev,同意。 我认为在这些情况下,重要的是要记住这不等同于x = x && someComplexExpression(),而相当于x = someComplexExpression() && x。 右侧应首先评估/执行以与其他任何赋值运算符保持一致。 鉴于此,&&= 与 &= 没有不同的行为。 - PSpeed
@polygene 这很公平。然而,事物的外观是基于你所熟悉的内容,我想他们可能不想添加任何新的东西。我喜欢 C/C++/Java/C# 的一件事情是表达式的基本语法几乎相同。 - Josh Lee
2
@PSpeed,您错了。JLS非常清楚复合赋值应该做什么。请参见我上面的补充。 - polygenelubricants
1
我认为说设计师仅仅是遵循 C 语言的先例而没有添加 >>>>>>= 是具有误导性的,因为它们是全新的。 - polygenelubricants
2
@PSpeed:在这种情况下,a -= b;a &&= b;的作用完全不同。 - David Thornley

12

Java这样做是因为C语言也是这样的。

那么问题来了,为什么在C语言中会是这样呢?因为当 & 和 && 成为不同的运算符(有时先于C从B的衍生)时,&= 运算符的变体被简单地忽视了。

但是我回答的第二部分并没有任何支持它的来源。


2
从有趣的角度来看 - 最近在C论坛上提出了这个问题;并且这个答案被链接了(虽然没有标记为重复)。这使得循环论证得以完成! - UKMonkey

7
由于Java语法基于C(或至少是C家族),在C中,所有这些赋值运算符都会编译为单个寄存器上的算术或位运算汇编指令。 赋值运算符版本避免了临时变量,并且可能在早期的非优化编译器上生成了更有效的代码。 逻辑运算符(在C中称为)等效项(&&=||=)没有如此明显的单个汇编指令对应关系;它们通常会扩展为一系列测试和分支指令。
有趣的是,像ruby这样的语言确实具有||=和&&=。
编辑:Java和C之间的术语不同。

我相信你的意思是条件运算符等价物没有这样明显的对应关系。在JLS术语中,布尔逻辑运算符是&|^&&||是条件运算符。 - polygenelubricants
在C语言中,“&&”和“||”被称为“逻辑运算符”,参见ISO 9899:1999的s6.5.13-14。当应用于单个位(Java中的布尔值)时,按位运算符只是“逻辑”操作符;但是,在C语言中没有单个位类型,因此逻辑运算符适用于所有标量类型。 - p00ya
在C99之前,C甚至没有bool类型。这意味着在语言历史上有20年的时间是没有bool类型的。因此,在那个时候并没有必要使用&&=。位运算已经足够了。 - v.oddou
+1 这是唯一有意义的答案。缺少这些运算符没有比 Java 的 C 遗产更深层次的原因了。C 只不过是一个“高级汇编语言”,从这个角度来看,没有必要使用这些运算符,因为它们无法编译成更有效的机器代码。 - Elmar Zander

4

Java 最初的一个目标之一是“简单、面向对象、熟悉” (aims)。在这种情况下,&= 是熟悉的 (C、C++ 有它,并且在这个上下文中熟悉意味着对那两种语言了解的人是熟悉的)。

&&= 不会很熟悉,也不会很简单,因为语言设计者并没有试图想出每个可以添加到语言中的运算符,所以尽量减少额外的运算符更简单。


3
对于布尔变量,&& 和 || 使用短路评估,而 & 和 | 不使用。因此,你可以期望 &&= 和 ||= 也使用短路评估。这有一个很好的用例。特别是当你迭代一个循环时,你希望代码快速、高效、简洁。
相比编写:
foreach(item in coll)
{
   bVal = bVal || fn(item); // not so elegant
}

我想要写东西

foreach(item in coll)
{
  bVal ||= fn(item);    // elegant
}

请注意,一旦bVal为真,fn()将不会在剩余的迭代中被调用。


2
对于那个循环,你可能只需要这样做:if (fn(item)) { bVal = true; break; } - Radiodef

2
'&'和'&&'不同,'&&'是一种快捷操作,当第一个操作数为false时不会执行,而'&'则无论如何都会执行(适用于数字和布尔值)。
我确实认为它存在会更有意义,但如果没有也没那么糟糕。我猜这是因为C语言中没有它。
真的想不出为什么。

2
Brian Goetz(Oracle的Java语言架构师)写道
“https://dev59.com/H3E95IYBdhLWcg3wadKq/ [这个问题] 显示了人们想要这些运算符的兴趣,但没有明确的理由说明为什么它们尚不存在。 因此问题是:JDK团队是否曾经讨论过添加这些运算符,如果有的话,对添加它们的原因是什么?”
我不知道具体讨论了这个问题,但是如果有人提出这个问题,答案可能是:这不是一个不合理的请求,但没有达到预期的效果。
“达到预期的效果”需要根据其成本和收益来衡量,并与其他候选功能相比较。
我认为您隐含地假设(通过短语“存在兴趣”),成本接近于零,而受益大于零,因此似乎是一种显而易见的胜利。 但这掩盖了对成本的错误理解。此类功能影响语言规范、实现、JCK以及每个IDE和Java教科书。 没有微不足道的语言功能。 另外,我们可以做无限多的功能,但每几年我们只能做几个(用户也有限制吸收新功能的能力)。 因此,我们必须非常小心地选择我们挑选的功能,因为每个功能(即使看似微不足道)都会消耗一些预算,并不可避免地从其他功能中脱颖而出。这不是“为什么不做这个功能”,而是“我们将不做哪些其他功能(或推迟)以便我们能够实现这个功能,这是一个好的交易吗?” 我无法真正想象它在其他任何我们正在处理的问题上都有更好的交易。
因此,它达到了“不是一个可怕的想法”的标准(这已经很好了,很多功能请求甚至连那个标准都没有达到),但似乎永远不可能达到“比我们的进化预算中的其他任何事情更好的用途”的标准。

0

&

验证两个操作数,它是一个按位运算符。Java定义了几个按位运算符,可以应用于整数类型long、int、short、char和byte。

&&

如果第一个操作数的值为false,则停止评估,因为结果将为false,这是一个逻辑运算符。它可以应用于布尔值。

&&运算符类似于&运算符,但可以使您的代码更加高效。因为与&运算符比较的两个表达式必须都为true才能使整个表达式为true,所以如果第一个表达式返回false,则没有理由评估第二个表达式。&运算符始终评估两个表达式。&&运算符仅在第一个表达式为true时才评估第二个表达式。

拥有&&=赋值运算符实际上不会为语言添加新功能。位运算符的算术更具表现力,您可以进行整数位运算,其中包括布尔运算。逻辑运算符只能进行布尔运算。


0

很有趣,我遇到了这个问题。

运算符||=和&&=不存在,因为它们的语义容易被误解;如果你认为需要它们,请使用if语句代替。

Ely(上面的帖子)理解得很对:

||

如果第一个操作数的值为真,那么逻辑运算符会停止计算,因为结果已经是真了。

所以想象一下如果 b == true; 会发生什么;

b ||= somethingreturningaboolean(); // !!??

如果 b == true,这将不会调用 somethingreturningaboolean()。

这种行为在长形式中更加明显:

b = b || somethingreturningaboolean();

这就是为什么 ||= 和 &&= 运算符不存在的原因。应该给出以下解释:

||= 和 &&= 运算符不存在,因为它们的语义很容易被误解;如果你认为需要它们,请使用 if 语句代替。


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