为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?
Posted
技术标签:
【中文标题】为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?【英文标题】:Why is "a^=b^=a^=b;" different from "a^=b; b^=a; a^=b;"?为什么是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”? 【发布时间】:2014-03-29 10:40:39 【问题描述】:我尝试了一些代码来交换 Java 中的两个整数,而不使用第三个变量,使用 XOR。
这是我尝试过的两个交换函数:
package lang.numeric;
public class SwapVarsDemo
public static void main(String[] args)
int a = 2984;
int b = 87593;
swapDemo1(a,b);
swapDemo2(a,b);
private static void swapDemo1(int a, int b)
a^=b^=a^=b;
System.out.println("After swap: "+a+","+b);
private static void swapDemo2(int a, int b)
a^=b;
b^=a;
a^=b;
System.out.println("After swap: "+a+","+b);
这段代码产生的输出是这样的:
After swap: 0,2984
After swap: 87593,2984
我很想知道,为什么会有这样的说法:
a^=b^=a^=b;
和这个不一样?
a^=b;
b^=a;
a^=b;
【问题讨论】:
我认为你被 java 的值传递搞砸了。 @EvertonAgner 我看不出传递值与问题有什么关系。究竟在哪里认为传递值会导致问题? 很公平,我忽略了这一点。 当您以这种方式“交换”值时,java 不是实际上创建了隐式中间对象吗?我认为使用临时变量应该可以提高性能。通常应该尝试使用按位运算来提高性能,仅在没有自动 GC 的语言中。 【参考方案1】:问题是评估的顺序:
见JLS section 15.26.2
首先,计算左侧操作数以生成变量。 如果 此评估突然完成,然后赋值表达式 出于同样的原因突然完成;右手操作数不是 评估并且没有分配发生。
否则,左边操作数的值被保存,然后 评估右手操作数。如果此评估完成 突然,然后赋值表达式突然完成 同样的原因,没有分配发生。
否则,左侧变量的保存值和 右手操作数用于执行二元运算 由复合赋值运算符指示。如果这个操作 突然完成,然后赋值表达式突然完成 出于同样的原因,没有分配发生。
否则,二进制运算的结果被转换为类型 左侧变量的值,经过值集转换(第 5.1.13 节) 到适当的标准值集(不是扩展指数值 set),并将转换结果存入变量中。
所以你的表达方式是:
a^=b^=a^=b;
-
评估
a
评估b^=a^=b
xor 两者(所以第一步中的a
还没有应用^=b
)
将结果存储在a
也就是说,你的表达式等价于下面的java代码:
int a1 = a;
int b2 = b;
int a3 = a;
a = a3 ^ b;
b = b2 ^ a;
a = a1 ^ b;
你可以从你的方法的反汇编版本中看到:
private static void swapDemo1(int, int);
Code:
0: iload_0
1: iload_1
2: iload_0
3: iload_1
4: ixor
5: dup
6: istore_0
7: ixor
8: dup
9: istore_1
10: ixor
11: istore_0
【讨论】:
+1 - 我倾向于跳上使用反汇编字节码作为 Java 行为“解释”的答案。但是你做对了……你在 JLS 中找到了真正的解释,并使用字节码来确认解释。 很好的答案,但我很好奇 Java 是否允许您在这种情况下使用括号强制执行所需的行为。a^=(b^=(a^=b));
有效吗?
@Patrick_M 它没有任何区别(您描述的已经是运算符优先级的工作方式),因为左侧仍然在右侧之前评估。【参考方案2】:
因为a ^= b ^= a ^= b;
被解析为:
a ^= (b ^= (a ^= b));
可以简化为:
a ^= (b ^= (a ^ b));
所以b
的值将是b ^ (a ^ b)
,最后a
将是a ^ (b ^ (a ^ b)
。
【讨论】:
解释为什么你做的减少是正确的——参考语言规范和运算符优先级信息。提问者很可能会遇到处理运算符优先级的其他问题(在这种语言和其他语言中)。 简化表示在最里面的括号a ^= b
等于a ^ b
。这是正确的,因为分配给变量a
的值不会在其余的评估中使用。相反,该值稍后会被最外层括号评估的结果覆盖。不需要语言规范...【参考方案3】:
这与 Bloch 和 Gafter 的 Java Puzzlers 书中的条目非常相似,请参阅第 2 章,谜题 7(“交换肉”)。我无法改进它。
解决方案中的解释是:
这个成语被用在 C 编程语言中,并由此产生 它进入 C++,但不能保证在其中任何一个中工作 语言。保证不能在 Java 中工作。 Java 语言 规范说 运算符的操作数从左到右计算 [JLS 15.7]。要评估表达式
x ^= expr
,x
的值在计算 expr 之前被采样,并且 这两个值的异或分配给变量x
[JLS 15.26.2]。在 CleverSwap 程序中,变量x
被采样两次——表达式中的每次出现一次——但两次采样 发生在任何分配之前。以下代码 sn -p 描述了 更详细地解释了损坏的交换习语的行为并解释了 我们观察到的输出:
上面引用的代码是:
// The actual behavior of x ^= y ^= x ^= y in Java
int tmp1 = x; // First appearance of x in the expression
int tmp2 = y; // First appearance of y
int tmp3 = x ^ y; // Compute x ^ y
x = tmp3; // Last assignment: Store x ^ y in x
y = tmp2 ^ tmp3; // 2nd assignment: Store original x value in y
x = tmp1 ^ y; // First assignment: Store 0 in x
【讨论】:
【参考方案4】:检查运算符优先级(参考:http://introcs.cs.princeton.edu/java/11precedence/ 来自搜索 java 运算符优先级)。此处显示了对 Oracle 文档的引用 (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html),虽然它有点密集)。
特别是^=是从右到左(不是从左到右)处理的
【讨论】:
你应该为这些基本的东西指向正确的 Java 语言文档。此外,您应该对问题进行解释,而不仅仅是指向外部文档。【参考方案5】:我没有足够的声望点来评论 Nathan 的回答。
@Nathan Hughes 对 Java Puzzlers 这本书的回应恰到好处,他的回答指出了一些可以让您在 1 行中做到这一点的见解。虽然不如 OP 的问题那么优雅。
正如内森指出的那样:
在 CleverSwap 程序中,变量 x 被采样两次——每次出现在表达式中一次——但两次采样都发生在任何赋值之前
除了使用https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html 作为指导,您还可以通过以下方式在 1 行中执行此操作:
a = ( b = (a = a ^ b) ^ b) ^ a;
关键是变量值的赋值作为第一个 XOR 的一部分,确保您将其保留在第二个 XOR 的左侧(请参阅上面的 jls 链接,左侧操作数中赋值的一个很好的例子)。同样,将 b 变量设置为第二个 XOR 的结果,再次保持在最终 XOR 的左侧。
【讨论】:
【参考方案6】:第二个变体等于
a=a^(b^(a^b));
【讨论】:
以上是关于为啥是“a^=b^=a^=b;”不同于“a^=b;b^=a;a^=b;”?的主要内容,如果未能解决你的问题,请参考以下文章