为什么“a ^ = b ^ = a ^ = b;”与“a ^ = b; b ^ = a; a ^ = b;”不同?

时间:2014-02-26 14:19:40

标签: java expression-evaluation compound-assignment

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

6 个答案:

答案 0 :(得分:39)

问题在于评估顺序:

请参阅JLS section 15.26.2

  

首先,评估左侧操作数以生成变量。 If   此评估突然完成,然后是赋值表达式   因同样的原因突然完成;右手操作数不是   评估并且没有分配。

     

否则,保存左侧操作数的值,然后保存   评估右手操作数。如果此评估完成   然后突然完成赋值表达式   同样的原因,没有任何转让。

     

否则,保存左侧变量的值和值   右侧操作数用于执行二进制操作   由复合赋值运算符表示。如果这个操作   突然完成,然后赋值表达式突然完成   出于同样的原因,没有任何转让。

     

否则,二进制操作的结果将转换为该类型   左侧变量的值,经过值集转换(§5.1.13)   到适当的标准值集(不是扩展指数值)   set),转换结果存储在变量中。

所以你的表达式确实如此:

a^=b^=a^=b;

  1. 评估a
  2. 评估b^=a^=b
  3. xor这两个(因此第一步中的a尚未应用^=b
  4. 将结果存储在a
  5. 换句话说,您的表达式等同于以下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 :(得分:7)

因为a ^= b ^= a ^= b;被解析为:

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

可以减少为:

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

因此b的值为b ^ (a ^ b),最后a的值为a ^ (b ^ (a ^ b)

答案 2 :(得分:5)

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

解决方案中的解释是:

  

这个成语用于C编程语言并从那里开始   它进入C ++的方式,但不保证可以在其中任何一个中使用   语言。保证不能在Java中工作。 Java语言   规范说运算符的操作数从左到右进行评估 [JLS 15.7]。要评估表达式x ^= expr,   在计算expr之前对x的值进行采样,并且   这两个值的异或被分配给变量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

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

第二个变体等于

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

答案 5 :(得分:0)

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

@Nathan Hughes回应谈论Java Puzzlers的书,并且他的回答指向了一些见解,这些见解允许你在一行中做到这一点。虽然没有OP的问题那么优雅。

内森指出:

  

在CleverSwap程序中,变量x对表达式中的每个外观进行两次采样 - 但两次采样都在任何赋值之前发生

除了使用https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html作为指导之外,您还可以在以下行中执行此操作:

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

关键是将变量值赋值为第一个XOR的一部分,确保将其保留在第二个XOR的左侧(参见上面的jls链接,左手操作数中的一个很好的赋值示例) )。同样,将b变量设置为第二个XOR的结果,再次保持在最终XOR的左侧。