20

我尝试了一些代码来交换 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;
4

6 回答 6

40

问题是评估的顺序:

请参阅JLS 部分 15.26.2

首先,评估左侧操作数以产生变量。如果这个求值突然完成,那么赋值表达式也会因为同样的原因而突然完成;不计算右侧操作数,也不发生赋值。

否则,将保存左侧操作数的值,然后计算右侧操作数。如果这个求值突然完成,那么赋值表达式也会因为同样的原因而突然完成并且没有赋值发生。

否则,左侧变量的保存值和右侧操作数的值用于执行复合赋值运算符指示的二元运算。如果此操作突然完成,则赋值表达式出于同样的原因突然完成并且不发生赋值。

否则,二元运算的结果将转换为左侧变量的类型,经过值集转换(第 5.1.13 节)到适当的标准值集(不是扩展指数值集),并将结果转换的存储到变量中。

所以你的表达是:

a^=b^=a^=b;

  1. 评估a
  2. 评估b^=a^=b
  3. xor 两者(所以a第一步还没有^=b应用到它)
  4. 将结果存储在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  
于 2014-02-26T14:46:48.647 回答
7

因为a ^= b ^= a ^= b;解析如下:

a ^= (b ^= (a ^= b));

可以简化为:

a ^= (b ^= (a ^ b));

所以b才会有价值b ^ (a ^ b),最后a才会有a ^ (b ^ (a ^ b)

于 2014-02-26T14:27:05.197 回答
5

这与 Bloch 和 Gafter 的Java Puzzlers书中的条目非常相似,参见第 2 章,谜题 7(“交换肉”)。我无法改进它。

解决方案中的解释是:

这个习惯用法在 C 编程语言中使用,并从那里进入 C++,但不能保证在这两种语言中都可以使用。保证不能在 Java 中工作。Java 语言规范说运算符的操作数是从左到右计算的[JLS 15.7]。为了计算表达式x ^= expr,在计算xexpr 之前对 的值进行采样,并将这两个值的异或分配给变量x[JLS 15.26.2]。在 CleverSwap 程序中,变量x被采样两次——每次出现在表达式中一次——但两次采样都发生在任何赋值之前。以下代码片段更详细地描述了损坏的交换习语的行为,并解释了我们观察到的输出:

上面引用中引用的代码是:

// 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
于 2014-02-26T14:54:54.033 回答
2

检查运算符优先级(参考:http: //introcs.cs.princeton.edu/java/11precedence/由搜索 java 运算符优先级产生)。此处显示了对 Oracle 文档的引用 ( http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html ),尽管它有点密集)。

特别是 ^= 是从右到左(不是从左到右)处理的

于 2014-02-26T14:29:35.233 回答
1

我没有足够的声誉点来评论内森的回答。

@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 的左侧。

于 2015-08-19T04:55:42.743 回答
0

第二个变体等于

a=a^(b^(a^b));

于 2014-02-26T14:21:19.263 回答