java - ¿Por qué “a ^=b ^=a ^=b;” diferente de “a ^=b; b ^=a; a ^=b; ”?
expression-evaluation compound-assignment (6)
El tema es el orden de evaluación:
Ver la sección 15.26.2 de JLS.
Primero, se evalúa el operando de la izquierda para producir una variable. Si esta evaluación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por la misma razón; El operando de la derecha no se evalúa y no se produce ninguna asignación.
De lo contrario, el valor del operando de la izquierda se guarda y luego se evalúa el operando de la derecha. Si esta evaluación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por el mismo motivo y no se produce ninguna asignación.
De lo contrario, el valor guardado de la variable de la izquierda y el valor del operando de la derecha se utilizan para realizar la operación binaria indicada por el operador de asignación compuesta. Si esta operación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por el mismo motivo y no se produce ninguna asignación.
De lo contrario, el resultado de la operación binaria se convierte al tipo de la variable de la izquierda, sujeto a la conversión del conjunto de valores (§5.1.13) al conjunto de valores estándar apropiado (no a un conjunto de valores de exponente extendido), y el resultado De la conversión se almacena en la variable.
Entonces tu expresión hace:
a^=b^=a^=b;
- evaluar
a
- evaluar
b^=a^=b
- xo los dos (por lo tanto, la
a
en el paso uno no tiene^=b
aplicado todavía) - almacenar el resultado en
a
En otras palabras, su expresión es equivalente al siguiente código Java:
int a1 = a;
int b2 = b;
int a3 = a;
a = a3 ^ b;
b = b2 ^ a;
a = a1 ^ b;
Puedes verlo en la versión desmontada de tu método:
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
Probé un código para intercambiar dos enteros en Java sin usar una tercera variable, usando XOR.
Aquí están las dos funciones de intercambio que probé:
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);
}
}
La salida producida por este código fue esta:
After swap: 0,2984
After swap: 87593,2984
Tengo curiosidad por saber, por qué esta declaración:
a^=b^=a^=b;
diferente de este?
a^=b;
b^=a;
a^=b;
Esto es muy similar a una entrada en el libro de Java Puzzlers de Bloch and Gafter, ver Capítulo 2, rompecabezas 7 ("Intercambiar carne"). No puedo mejorarlo.
La explicación en la solución es:
Este lenguaje se usó en el lenguaje de programación C y desde allí se abrió camino hacia C ++, pero no se garantiza que funcione en ninguno de estos lenguajes. Está garantizado que no funciona en Java. La especificación del lenguaje Java dice que los operandos de los operadores se evalúan de izquierda a derecha [JLS 15.7]. Para evaluar la expresión
x ^= expr
, se muestrea el valor dex
antes de evaluar expr, y se asigna el OR exclusivo de estos dos valores a la variablex
[JLS 15.26.2]. En el programa CleverSwap, la variablex
se muestrea dos veces, una por cada aparición en la expresión, pero ambas muestreos se producen antes de cualquier asignación. El siguiente fragmento de código describe el comportamiento del lenguaje de intercambio roto con más detalle y explica el resultado que observamos:
El código al que se hace referencia en la cita anterior es:
// 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
No tengo suficientes puntos de reputación para comentar sobre la respuesta de Nathan.
La respuesta de @Nathan Hughes sobre el libro de Java Puzzlers es acertada, y su respuesta apunta a algunas ideas que le permiten hacer esto en 1 línea. Aunque no tan elegante como la pregunta del OP.
Como Nathan señala:
En el programa CleverSwap, la variable x se muestrea dos veces, una por cada aparición en la expresión, pero ambas muestreos se producen antes de cualquier asignación
Además de usar https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html como guía, puede hacerlo en 1 línea con:
a = ( b = (a = a ^ b) ^ b) ^ a;
La clave es la asignación del valor de una variable como parte del primer XOR, lo que garantiza que se mantenga en el lado izquierdo del segundo XOR (vea el enlace jls arriba, para un buen ejemplo de asignación en el operando de la izquierda) De manera similar, establezca la variable b con los resultados del segundo XOR, nuevamente a la izquierda del XOR final.
Porque a ^= b ^= a ^= b;
se analiza como
a ^= (b ^= (a ^= b));
Que se puede reducir a:
a ^= (b ^= (a ^ b));
Entonces b
tendrá el valor b ^ (a ^ b)
y finalmente a
será a ^ (b ^ (a ^ b)
.
Segunda variante es igual a
a=a^(b^(a^b));
Verifique la precedencia del operador (ref: http://introcs.cs.princeton.edu/java/11precedence/ resultante de la búsqueda de la precedencia del operador Java). Una referencia a la documentación de Oracle aparece aquí ( http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html ), aunque es un poco densa).
En particular, ^ = se procesa de derecha a izquierda (no de izquierda a derecha)