Java中的快捷方式“或分配”(| =)运算符


89

我在Java中有很长的比较需要做,我想知道其中是否有一个或多个是正确的。比较字符串很长且难以阅读,因此为了便于阅读,我对其进行了分解,并自动使用快捷方式运算符|=而不是negativeValue = negativeValue || boolean

boolean negativeValue = false;
negativeValue |= (defaultStock < 0);
negativeValue |= (defaultWholesale < 0);
negativeValue |= (defaultRetail < 0);
negativeValue |= (defaultDelivery < 0);

negativeValue如果任何默认<something>值为负,我希望是真实的。这有效吗?会满足我的期望吗?我在Sun的网站或stackoverflow上看不到它,但是Eclipse似乎没有问题,并且代码可以编译并运行。


同样,如果我想执行多个逻辑交集,是否可以使用&=代替&&


13
你为什么不尝试呢?
罗马2010年

这是通用的布尔逻辑,而不仅仅是Java。因此您可以在其他地方查找它。而且,为什么不尝试一下呢?
Dykam 2010年

16
@Dykam:不,这里有特定的行为。Java 可以选择使| =短路,从而如果LHS已经成立,则它不会评估RHS,但事实并非如此。
乔恩·斯基特

2
@Jon Skeet:短路适用于不存在的||=运算符,但这|=是按位运算符或运算符的组合形式。
David Thornley 2010年

1
@Jon Skeet:可以,但是|=短路会与其他复合赋值运算符不一致,因为a |= b;这与并不相同a = a | b;,通常会引起a两次评估(如果重要)的注意。在我看来,没有做出重大的语言行为决定||=,所以我想念您的意思。
David Thornley 2010年

Answers:


204

|=是这样的化合物赋值运算符(JLS 15.26.2为布尔逻辑运算符)|JLS 15.22.2); 不要与条件或混淆||JLS 15.24)。也有&=^=对应于所述化合物分配版本布尔逻辑&^分别。

换句话说,对于boolean b1, b2,这两个是等效的:

 b1 |= b2;
 b1 = b1 | b2;

(逻辑运算符之间的差异&|)相比,他们的条件同行(&&||)之处在于前者不“短路”; 后者可以。那是:

  • &| 始终评估两个操作数
  • &&有条件地||评估正确的操作数; 仅当右操作数的值可能影响二进制运算的结果时,才对右操作数进行求值。这意味着在以下情况下不会评估正确的操作数:
    • 的左操作数&&计算为false
      • (因为无论正确的操作数求值是什么,整个表达式都是false
    • 的左操作数||计算为true
      • (因为无论正确的操作数求值是什么,整个表达式都是true

所以回到你原来的问题,是的,结构是有效的,虽然|=是不完全的等价快捷=||,这你想要做什么的计算。由于|=您使用的运算符的右侧是一个简单的整数比较运算,因此|不会短路的事实微不足道。

在某些情况下,需要短路甚至是短路时,但您的情况并非其中之一。

不幸的是,与某些其他语言不同,Java没有&&=and ||=。问题Java为什么不为条件和和条件或运算符提供复合赋值版本?(&& =,|| =)


+1,非常彻底。如果编译器可以确定RHS没有副作用,则可以将其转换为短路运算符似乎是合理的。有什么线索吗?
卡尔2010年

我读到,当RHS琐碎而不需要SC时,“智能” SC运算符实际上要慢一些。如果为true,那么更有趣的是想知道某些编译器是否可以在某些情况下将SC转换为NSC。
polygenelubricants 2010年

@polygenelubricants短路的运算符在引擎盖下涉及某种分支,因此,如果没有与运算符一起使用的真值的分支预测友好模式和/或所使用的体系结构没有好的/任何分支预测(并假设编译器和/或虚拟机本身未进行任何相关的优化),那么是的,与非短路相比,SC运算符会带来一些缓慢。找出编译器执行任何操作的最佳方法是使用SC与NSC编译程序,然后比较字节码以查看其是否不同。
JAB

另外,不要与按位OR运算符混淆,后者也是 |
user253751

16

它不是||方式的“捷径”(或短路)运算符。和&&是(因为如果他们已经知道基于LHS的结果,他们将不会评估RHS),但是它将在工作方面满足您的要求。

作为区别的一个示例,如果text为null ,则此代码会很好:

boolean nullOrEmpty = text == null || text.equals("")

而这不会:

boolean nullOrEmpty = false;
nullOrEmpty |= text == null;
nullOrEmpty |= text.equals(""); // Throws exception if text is null

(显然,您可以"".equals(text)针对该特定情况进行处理-我只是在尝试说明原理。)


3

您可能只有一个陈述。用多行表示,它的读法几乎与您的示例代码完全一样,仅次于命令而已:

boolean negativeValue
    = defaultStock < 0 
    | defaultWholesale < 0
    | defaultRetail < 0
    | defaultDelivery < 0;

对于最简单的表达式,使用|可能会比更快,||因为即使它避免进行比较,也意味着隐式使用分支,并且代价可能高出很多倍。


我确实只是从一个开始,但正如最初的问题所述,我感到“比较字符串很长且难以阅读,因此为了可读性我将其分解了”。除此之外,在这种情况下,我对学习| =的行为比使这段特定代码更感兴趣。
戴维·梅森

1

尽管对于您的问题来说可能是矫kill过正,但Guava库的Predicates 语法不错,并且可以对or / and Predicates 进行短路评估。

本质上,比较被转换为对象,打包为一个集合,然后进行迭代。对于或谓词,第一个真命中会从迭代中返回,反之亦然。


1

如果是关于可读性,我就有了将测试数据与测试逻辑分离的概念。代码示例:

// declare data
DataType [] dataToTest = new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
}

// define logic
boolean checkIfAnyNegative(DataType [] data) {
    boolean negativeValue = false;
    int i = 0;
    while (!negativeValue && i < data.length) {
        negativeValue = data[i++] < 0;
    }
    return negativeValue;
}

该代码看起来更加冗长和不言自明。您甚至可以在方法调用中创建一个数组,如下所示:

checkIfAnyNegative(new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
});

它比“比较字符串”更具可读性,并且还具有短路的性能优势(以数组分配和方法调用为代价)。

编辑: 可以通过使用varargs参数简单地实现更高的可读性:

方法签名为:

boolean checkIfAnyNegative(DataType ... data)

呼叫看起来像这样:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );

1
数组分配和方法调用对于短路来说是相当大的代价,除非您在比较中进行了一些昂贵的操作(尽管问题中的示例很便宜)。话虽这么说,大多数时候,代码的可维护性将超过性能方面的考虑。如果我要在一堆不同的地方进行不同的比较,或者比较四个以上的值,那么我可能会使用类似的东西,但是对于一个案例,这对于我的口味来说有些冗长。
David Mason

@DavidMason我同意。但是请记住,大多数现代计算器会在不到几毫秒的时间内吞下这种开销。就个人而言,在性能问题看来是合理的之前,我不会在意开销。同样,代码冗长是一个优势,尤其是当JAutodoc不提供或生成javadoc时。
KrzysztofJabłoński2013年

1

这是一篇旧文章,但是为了给初学者提供不同的见解,我想举一个例子。

我认为类似复合运算符的最常见用例是+=。我敢肯定我们都写了这样的东西:

int a = 10;   // a = 10
a += 5;   // a = 15

这有什么意义呢?关键是要避免样板并消除重复的代码。

因此,下一行的功能完全相同,避免b1在同一行中两次键入变量。

b1 |= b2;

1
很难发现操作和操作员名称中的错误,尤其是当名称很长时。 longNameOfAccumulatorAVariable += 5; 主场迎战 longNameOfAccumulatorAVariable = longNameOfAccumulatorVVariable + 5;
安德烈(Andrei)

0
List<Integer> params = Arrays.asList (defaultStock, defaultWholesale, 
                                       defaultRetail, defaultDelivery);
int minParam = Collections.min (params);
negativeValue = minParam < 0;

我想我更喜欢negativeValue = defaultStock < 0 || defaultWholesale < 0等。除了这里所有装箱和包装的效率低下外,我发现理解您的代码的真正含义几乎没有那么容易。
乔恩·斯基特

如果他有4个以上的参数,并且所有参数的标准都相同,那么我喜欢我的解决方案,但是出于可读性考虑,我将其分成几行(即创建List,查找minvalue,将minvalue与0比较) 。
罗马2010年

对于大量比较而言,这确实很有用。在这种情况下,我认为在清晰度的降低是不值得打字等储蓄
大卫梅森

0

|| 逻辑布尔值OR
| 按位或

| =按位包含或或赋值运算符

| =不能短时循环的原因是因为它执行按位或运算而不是逻辑或运算。也就是说:

C | = 2等于C = C | 2

Java运算符教程


没有按位或布尔值!运算符|是整数按位“或”运算符,也是逻辑“或”运算符-参见 Java语言规范15.22.2。
user85421 2013年

您是正确的,因为对于单个位(布尔值),按位和逻辑或是等效的。尽管实际上,结果是相同的。
oneklc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.