Ejecución del operador de asignación de Java

76

En Java, entiendo que la asignación evalúa el valor del operando correcto, por lo que declaraciones como x == (y = x)evaluar atrue .

Este código, sin embargo, genera false.

public static void main(String[]args){
    String x = "hello";
    String y = "goodbye";
    System.out.println(x.equals(x = y));
}

¿Por qué es esto? Según tengo entendido, primero evalúa (x = y), que asigna xel valor de yy luego devuelve el valor de y. Luego x.equals(y)se evalúa, que debería ser truedesde xy ydebería compartir las mismas referencias ahora, pero en cambio, obtengo false.

Captura de pantalla que muestra la fuente y que el resultado es "falso"

¿Que está sucediendo aquí?

Sam
fuente
13
Creo que quería ver resultado parax.equals( y = x )
nits.kk
1
¿Podría el compilador en línea xy y?
Lino
3
Estás suponiendo que la asignación x = yse ejecuta en el lado derecho antes de la xse evalúa en el lado de la mano izquierda?
khelwood
@khelwood sí, esa fue mi suposición. No debe
Sam
1
@ nits.kk No lo creo. OP ya dijo que entienden que se x == (y = x)evalúa como verdadero. El comportamiento de lo que sugieres entonces sería obvio ...
Pedro A

Respuestas:

76

En primer lugar: esa es una pregunta interesante, pero nunca debería aparecer en "código real", ya que asignar a la variable que llamas en la misma línea es confuso incluso si sabes cómo funciona.

Lo que sucede aquí son estos 3 pasos:

  1. averiguar en qué objeto llamar al método (es decir, evaluar el primer x , esto dará como resultado una referencia a la cadena "hola")
  2. averiguar los parámetros (es decir x = y, evaluar , que cambiará xpara apuntar a la Cadena "adiós" y también devolverá una referencia a esa Cadena)
  3. llame al método equalsen el resultado de # 1 usando el resultado de # 2 como parámetro (que serán referencias a las cadenas "hola" y "adiós" respectivamente).

Al observar el código de bytes producido para ese método, queda claro (suponiendo que domine el código de bytes de Java):

     0: ldc           #2                  // String hello
     2: astore_1
     3: ldc           #3                  // String goodbye
     5: astore_2
     6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: aload_1
    10: aload_2
    11: dup
    12: astore_1
    13: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
    16: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
    19: return

La línea # 9 es el paso 1 anterior (es decir, evalúa x y recuerda el valor).

La línea # 10-12 es el paso 2. La carga y, la duplica (una vez para asignar, una vez para el valor de retorno de la expresión de asignación) y la asigna ax .

La línea # 13 invoca equalsel resultado calculado en la línea # 9 y el resultado de las líneas # 10-12.

Joachim Sauer
fuente
36
TL; DR: x.equals(x = y)=> "hello".equals(x = y)=> "hello".equals(x = "goodbye")=> "hello".equals("goodbye")=> false.
Bernhard Barker
8
Un punto importante a tener en cuenta es que .tiene mayor precedencia que= .
Gaurang Tandon
4
Se trata más de un orden de evaluación que de una precedencia. Los paréntesis hacen que la precedencia sea irrelevante de todos modos.
amalloy
38

¡Buena pregunta! Y el JLS tiene la respuesta ...

§15.12.4.1 (Ejemplo 15.12.4.1-2). Orden de evaluación durante la invocación del método:

Como parte de la invocación de un método de instancia, hay una expresión que denota el objeto que se va a invocar. Esta expresión parece estar completamente evaluada antes de evaluar cualquier parte de cualquier expresión de argumento para la invocación del método.

Entonces, en:

String x = "hello";
String y = "goodbye";
System.out.println(x.equals(x = y));

la ocurrencia de xbefore .equalsse evalúa primero, antes de la expresión de argumentox = y .

Por lo tanto, una referencia a la cadena   hello  se recuerda como la referencia de destino antes de que la variable local xse cambie para hacer referencia a la cadena goodbye. Como resultado, el equalsmétodo se invoca para el objeto de destino hellocon argumento goodbye, por lo que el resultado de la invocación es false.

Oleksandr Pyrohov
fuente
28

Es importante recordar que Stringen java es un objeto y, por lo tanto, una referencia. Cuando usted llama

x.equals(...)

Está comprobando si el valor en la ubicación a la que se hace referencia actualmente xes igual a lo que está pasando. En el interior, está cambiando el valor que xhace referencia , pero todavía está llamando equalscon la referencia original (la referencia a "hola"). Entonces, ahora mismo su código se está comparando para ver si "hola" es igual a "adiós", lo cual claramente no es así. Después de este punto, si lo usa xnuevamente, resultará en una referencia al mismo valor que y.

Keveloper
fuente
si te entiendo, esto: ¿ "hello".equals((x = y))debería volver true?
Halayem Anis
3
No, porque (x = y) devolverá el valor de y, que es "adiós". Entonces, si hiciera "adiós" .equals (x = y), eso devolvería verdadero
Keveloper
5

x=yentre paréntesis significa que la expresión (x=y)es ahora goodbye, mientras que la x exterior x.equalstiene el valorhello

CD de Chetan Jadhav
fuente
2
Esto realmente no explica por qué sucede esto ni proporciona detalles adicionales que serían útiles para otros.
Grey
Ahora que lo leo en voz alta, tiendo a estar de acuerdo contigo ... las otras respuestas son bastante detalladas, aunque así que no las editas ...
Chetan Jadhav CD
4

Reimus dio la respuesta correcta, pero me gustaría dar más detalles.

En Java (y la mayoría de los lenguajes) la convención es variable va a la izquierda, asignación a la derecha.

Vamos a analizarlo:

String x = "hello";
//x <- "hello"

String y = "goodbye";
//y <- "goodbye";

Para fines de depuración y legibilidad del código, siempre es una buena práctica dividir las líneas para que solo hagan una cosa.

System.out.println(x.equals(x = y)); //Compound statement

Aquí, x.equals(...) se llama a la referencia original ax, o "hola", se actualiza para la segunda referencia.

Escribiría esto como (y esto le dará la respuesta esperada):

x = y;
// x <- y = "goodbye"

boolean xEqualsX = x.equals(x);
// xEqualsX <- true

System.out.println(xEqualsX);
// "true"

Ahora bien, esto parece obvio que debería comportarse de esta manera, pero también es muy fácil ver exactamente lo que está sucediendo en cada línea, que es algo por lo que debes esforzarte.

Asesino de la aleatoriedad
fuente
2
Re: "siempre": existe el exceso de claridad: ¿recomendaría que System.out.println("Bytes: "+1024*k);se escribiera como tres declaraciones?
Davis Herring
@DavisHerring esto es en el contexto de la depuración. Si está intentando depurar esa declaración, entonces sí, recomendaría absolutamente dividir esta declaración en sus componentes. Un enunciado para la multiplicación y otro para imprimir. Permite la máxima flexibilidad. Como regla general, desea que cada línea haga solo una cosa. Hace que el código sea más legible y más fácil de depurar.
Randomness Slayer
2

He intentado su pregunta en eclipse, ambas expresiones son correctas. 1) x == (y = x) evaluar como verdadero, es cierto porque el valor de x se asigna a y, que es 'hola', luego xey comparan serán iguales, por lo que el resultado será verdadero

2) x.equal (x = y) es falso porque el valor de y se asigna a x que es adiós, entonces xyx comparan su valor será diferente, por lo que el resultado será falso

Alishan
fuente
1

Veo la pregunta en términos simples como "hello".equals("goodbye"). Entonces devuelve falso.

Vamsi
fuente
1

En Java String es una clase.

String x = "hello";
String y = "goodbye"; 

es una cadena de dos diferentes que se refieren a dos valores diferentes que no son iguales y si compara

 System.out.println(x.equals(x = y)); 
//this compare value (hello and goodbye) return true

    System.out.println(x == (y = x)); 
// this compare reference of an object (x and y) return false  
Aadesk
fuente
1
¿Realmente ha ejecutado este código de muestra? Como dice la pregunta, System.out.println(x.equals(x = y));devuelve falso, al contrario de lo que afirma su respuesta.
Charlie Harding
-4

Está viendo si x.equals (asignar xay, devuelve verdadero siempre) así que básicamente x.equals (verdadero)

mmx
fuente
8
esto es falso, no es así como se evalúa la tarea
Sam