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

Abh*_*Oza 20 java expression-evaluation compound-assignment

我尝试使用XOR在不使用第三个变量的情况下在Java中交换两个整数的代码.

以下是我尝试的两个交换函数:

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

此代码生成的输出如下:

After swap: 0,2984
After swap: 87593,2984
Run Code Online (Sandbox Code Playgroud)

我很想知道,为什么这句话:

        a^=b^=a^=b;
Run Code Online (Sandbox Code Playgroud)

与此不同?

        a^=b;
        b^=a;
        a^=b;
Run Code Online (Sandbox Code Playgroud)

Erw*_*idt 39

问题是评估顺序:

JLS第15.26.2节

首先,评估左侧操作数以产生变量.如果此评估突然完成,则赋值表达式出于同样的原因突然完成; 不评估右侧操作数,也不进行赋值.

否则,保存左侧操作数的值,然后评估右侧操作数.如果此评估突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何分配.

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

否则,二进制操作的结果将转换为左侧变量的类型,经过值集转换(第5.113节)到相应的标准值集(不是扩展指数值集),结果转换的内容存储在变量中.

所以你的表达式如下:

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;
Run Code Online (Sandbox Code Playgroud)

您可以从方法的反汇编版本中看到:

  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  
Run Code Online (Sandbox Code Playgroud)

  • +1 - 我倾向于跳过使用反汇编字节码作为Java行为的"解释"的答案.但是你已经以正确的方式完成了......你已经在JLS中找到了真正的解释,并使用字节码来确认解释. (3认同)
  • @Patrick_M 它没有区别(您所描述的已经是运算符优先级的工作方式),因为左侧仍然在右侧之前进行评估。 (2认同)

Mar*_*oun 7

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

a ^= (b ^= (a ^= b));
Run Code Online (Sandbox Code Playgroud)

哪个可以简化为:

a ^= (b ^= (a ^ b));
Run Code Online (Sandbox Code Playgroud)

所以b将有价值b ^ (a ^ b),最终a将是a ^ (b ^ (a ^ b).


Nat*_*hes 5

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

解决方案中的解释是:

这个习惯用法在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
Run Code Online (Sandbox Code Playgroud)