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

Posted

技术标签:

【中文标题】为啥 Java 没有条件与和条件或运算符的复合赋值版本? (&&=, ||=)【英文标题】:Why doesn't Java have compound assignment versions of the conditional-and and conditional-or operators? (&&=, ||=)为什么 Java 没有条件与和条件或运算符的复合赋值版本? (&&=, ||=) 【发布时间】:2010-12-03 02:01:18 【问题描述】:

所以对于布尔值的二元运算符,Java 有 &|^&&||

让我们在这里简要总结一下他们做了什么:

JLS 15.22.2 Boolean Logical Operators &, ^, and | JLS 15.23 Conditional-And Operator && JLS 15.24 Conditional-Or Operator ||

对于&,如果两个操作数的值都是true,则结果值为true;否则,结果为false

对于|,如果两个操作数的值都是false,则结果值为false;否则,结果为true

对于^,如果操作数值不同,则结果值为true;否则,结果为false

&& 运算符类似于 &,但仅当其左侧操作数的值为 true 时才计算其右侧操作数。

|| 运算符类似于 |,但仅当其左侧操作数的值为 false 时才计算其右侧操作数。

现在,在所有 5 个中,其中 3 个具有复合赋值版本,即 |=&=^=。所以我的问题很明显:为什么 Java 不提供 &&=||= 呢?我发现我需要的比我需要的更多 &=|=

而且我不认为“因为它太长”是一个好的答案,因为 Java 有 >>>=。这种遗漏一定有更好的理由。


来自15.26 Assignment Operators:

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


有人评论说,如果实现了&&=||=,那么它将是唯一不首先评估右侧的运算符。我认为复合赋值运算符首先计算右手边的想法是错误的。

来自15.26.2 Compound Assignment Operators:

E1 op= E2 形式的复合赋值表达式等价于E1 = (T)((E1) op (E2)),其中TE1 的类型,但E1 只计算一次。

作为证据,以下 sn-p 抛出 NullPointerException,而不是 ArrayIndexOutOfBoundsException

    int[] a = null;
    int[] b = ;
    a[0] += b[-1];

【问题讨论】:

我选择第二个,没人关心 :P 也是,所有这些关于“为什么功能 x 不在语言 y 中?”的问题应该问语言的设计者,而不是问我们:P &= 是什么意思?有谁可以告诉我吗? @Aaron: a = a & b。写在问题里 Why does a "&&=" Operator not exist?的可能重复 @jleedev:这个问题比较老,但是有更多的投票和传入的链接。我会说如果有任何合并,请将旧的合并到这个(是的,可以这样做)。 【参考方案1】:

原因

&&=||= 运算符在 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();,其中f2() 仅在f1() 返回true 时被调用。

如果开发人员希望仅在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
======================

因此 YES 我们可以将 && 替换为 & 以获得布尔值 ;-)

所以最好使用&= 而不是&&=

||= 相同

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

如果开发人员希望在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 ...

【讨论】:

嗨@StriplingWarrior。我已经咨询了我的同事 Yannick,他是我们最好的 Java 专家。我已经使用用于检查这一点的 Java 代码源更新了我的答案。正如你所说的&&&给出相同的结果。非常感谢您的反馈。你喜欢我的回答吗?干杯。 如果我想很快做到这一点怎么办? &&= 会比 &= 快,如果它存在的话,所以你应该使用 if (a) a = b 来提高速度 嗨@adventurerOK。抱歉,我不确定你的意思……我认为a&=b;if(a) a=b; 在使用存储在 CPU 寄存器中的值时更快。但是,如果b 在外部存储器中(未缓存),那么if(a) a=b; 会更快。是你的意思吗?请提供更多示例代码;-) 我很好奇您的意见。再见。干杯 我不同意你说“这不是我们想要的”。如果我写isOK &&= f2();,我希望它像&& 那样短路。 我不同意您关于运算符容易出错或无用的说法。使用复合赋值是您为通常的A = A op B 走捷径而做的事情,因此每个人都完全知道他们在做什么,并且可以自己关心其含义。如果你的理由确实是它缺席的原因,我会认为这是不必要的光顾。但是,我要感谢您添加 bool isOk = f1() || f2() || f3() || f4(); 行,因为那是我看不见自己的原因。【参考方案2】:

可能是因为类似

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

看起来它应该分配给x 并评估someComplexExpression(),但评估取决于x 的值这一事实在语法中并不明显。

还因为 Java 的语法是基于 C 的,没有人认为添加这些运算符的迫切需要。无论如何,使用 if 语句可能会更好。

【讨论】:

我认为这不是一个好的答案,因为有人可能会争辩说 x() && y() 看起来 应该评估表达式的两边。显然,人们接受&& 是短路的,因此&&= 也应该如此。 @jleedev,同意。我相信在这些情况下,重要的是要记住这不是 x = x && someComplexExpression() 的等价物,而是 x = someComplexExpression() && x 的等价物。右侧将/应该首先评估以与其他所有赋值运算符保持一致。鉴于此,&&= 的行为与 &= 没有什么不同。 @PSpeed,你错了。 JLS 非常清楚复合分配应该做什么。见我上面的补充。 我认为说他们不存在是因为设计师只是遵循 C 设置的优先级是一种误导,因为他们确实添加了 >>>>>>=,它们是全新的。 @PSpeed:在这种情况下,a -= b; 的工作方式与a &&= b; 完全不同。【参考方案3】:

在 Java 中是这样,因为在 C 中也是这样。

现在问题为什么在 C 中如此,因为当 & 和 && 成为不同的运算符时(有时在 C 从 B 下降之前),&= 各种运算符被简单地忽略了。

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

【讨论】:

有趣的一面——这个问题最近在 C 论坛上被问到;并且这个答案是链接的(虽然没有标记为重复)这使得循环论证完成!【参考方案4】:

很大程度上是因为 Java 语法基于 C(或至少 C 系列),并且在 C 中,所有这些赋值运算符都被编译为单个寄存器上的算术或按位汇编指令。赋值运算符版本避免了临时性,并且可能在早期的非优化编译器上生成了更高效的代码。逻辑运算符(在 C 中被称为)等价物(&&=||=)与单个汇编指令没有如此明显的对应关系;它们通常会扩展为测试和分支指令序列。

有趣的是,像 ruby​​ 这样的语言确实有 ||= 和 &&=。

编辑:Java 和 C 之间的术语不同

【讨论】:

我相信你的意思是 conditional 运算符等价物没有如此明显的对应关系。在 JLS 术语中,布尔逻辑运算符为 &|^&&|| 是条件运算符。 在 C 术语中,&& 和 ||是“逻辑运算符”,ISO 9899:1999 中的 s6.5.13-14。位运算符仅在应用于单个位(Java 中的布尔值)时才是“逻辑的”; C 中没有单位类型,那里的逻辑运算符适用于所有标量类型。 在 C99 之前,C 甚至没有 bool 类型。那是语言历史上没有布尔值的 20 年。没有理由拥有&&=。按位运算就足够了。 +1 唯一真正有意义的答案。没有比 Java 的 C 遗产更深层次的原因导致缺少这些运算符。 C 曾经(并且在很大程度上仍然是)只是一个“高级汇编程序”,从这个角度来看,不需要这些运算符,因为它们不会编译成更高效的机器代码。【参考方案5】:

Java 最初的aims 之一是“简单、面向对象和熟悉”。应用于这种情况时,&= 是熟悉的(C、C++ 有它,并且在这种情况下,熟悉意味着熟悉这两者的人熟悉)。

&&= 不会很熟悉,也不会简单,因为语言设计者并不想考虑他们可以添加到语言中的每个运算符,因此额外的运算符越少越简单。

【讨论】:

【参考方案6】:

对于布尔变量,&& 和 ||将使用短路评估而 & 和 |不要,所以你会期望 &&= 和 ||= 也使用短路评估。有一个很好的用例。尤其是当您在循环中进行迭代时,您希望快速、高效且简洁。

而不是写

foreach(item in coll)

   bVal = bVal || fn(item); // not so elegant

我想写

foreach(item in coll)

  bVal ||= fn(item);    // elegant

并且知道一旦 bVal 为真,fn() 将不会在剩余的迭代中被调用。

【讨论】:

对于那个循环,你可能只想做if (fn(item)) bVal = true; break; 【参考方案7】:

'&' 和 '&&' 与 '&&' 不同是一种快捷操作,如果第一个操作数为假,则不会执行此操作,而 '&' 无论如何都会执行此操作(适用于数字和布尔值)。

我确实同意存在更有意义,但如果它不存在也没有那么糟糕。我猜它不存在是因为 C 没有它。

实在想不出为什么。

【讨论】:

【参考方案8】:

Brian Goetz(Oracle 的 Java 语言架构师)wrote:

https://***.com/q/2324549/ [this question] 表明有兴趣拥有这些运算符并且 没有明确的论据为什么它们还不存在。 因此,问题是:JDK 团队过去是否讨论过添加这些运算符,如果有的话 反对添加它们的原因在哪里?

我不知道关于这个特定问题的任何具体讨论,但是 如果有人提议,答案很可能是:这不是一个 不合理的要求,但它没有重量。

“负重前行”需要从成本和收益来判断,而 根据其相对于其他候选特征的成本效益比。

我认为您是在隐含地假设(通过“有兴趣”这句话) 成本接近于零,收益大于零,所以它 似乎是一个明显的胜利。但这掩盖了对 成本;像这样的特性会影响语言规范、实现、 JCK,以及所有 IDE 和 Java 教科书。没有琐碎的语言 特征。好处虽然不为零,但非常小。

其次,我们可以做无限多的功能,但我们 每隔几年只能做几次(而且用户有 吸收新功能的能力有限。)所以我们必须非常小心 至于我们选择哪个,作为每个特征(即使是一个看似微不足道的特征) 消耗了一些预算,并且总是从其他人那里拿走它。 不是“为什么不做这个功能”,而是“我们不会做哪些其他功能” (或延迟)所以我们可以做这个,这是一个很好的交易吗?”我 真的无法想象这是一个很好的交易来对抗我们的其他任何东西 工作中。

因此,它清除了“不是一个糟糕的想法”的标准(这已经很漂亮了 很好,很多功能请求甚至都不清楚),但似乎 不太可能清除“更好地利用我们的进化预算”的障碍 比什么都重要。”

【讨论】:

【参考方案9】:

在 Ruby 中是允许的。

如果我猜的话,我会说它不经常使用,所以没有实现。另一种解释可能是解析器只查看 =

之前的字符

【讨论】:

Java 支持 >= 和 >>>=,因此严格来说并不正确。 真的。我没有想到那些。我想唯一的解释是它的使用频率。【参考方案10】:

我想不出比“它看起来非常丑陋!”更好的理由了!

【讨论】:

但是 C/Java 从来就不是为了漂亮。 我几乎不会认为 &&= 很丑【参考方案11】:

&

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

&&

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

&& 运算符类似于 & 运算符,但可以使您的代码更高效。因为 & 运算符比较的两个表达式必须为真才能使整个表达式为真,所以如果第一个表达式返回假,则没有理由评估第二个表达式。 & 运算符始终计算这两个表达式。只有当第一个表达式为真时,&& 运算符才会计算第二个表达式。

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

【讨论】:

以上是关于为啥 Java 没有条件与和条件或运算符的复合赋值版本? (&&=, ||=)的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 中的竞争条件与复合赋值

Java NO.2

JAVA的运算符和条件结构

运算符和条件结构

有人可以解释为啥条件运算符和赋值运算符一起使用时表现奇怪吗?

为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)