Tengo el siguiente código:
public class Tests {
public static void main(String[] args) throws Exception {
int x = 0;
while(x<3) {
x = x++;
System.out.println(x);
}
}
}
Sabemos que debería haber escrito solo x++
o x=x+1
, pero x = x++
primero debería atribuirse x
a sí mismo y luego incrementarlo. ¿Por qué x
continúa con 0
como valor?
--actualizar
Aquí está el código de bytes:
public class Tests extends java.lang.Object{
public Tests();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_3
4: if_icmpge 22
7: iload_1
8: iinc 1, 1
11: istore_1
12: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_1
16: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
19: goto 2
22: return
}
Leeré sobre las instrucciones para tratar de entender ...
x++
es post-incremento;x=
es la asignación del resultado ; el resultado dex++
es el originalx
(y hay un efecto secundario de incremento, pero eso no cambia el resultado), así que esto puede interpretarse comovar tmp = x; x++; x = tmp;
Respuestas:
Nota : Originalmente publiqué el código C # en esta respuesta con fines ilustrativos, ya que C # le permite pasar
int
parámetros por referencia con laref
palabra clave. Decidí actualizarlo con un código Java legal real usando la primeraMutableInt
clase que encontré en Google para aproximarme a lo queref
hace en C #. Realmente no puedo decir si eso ayuda o perjudica la respuesta. Diré que personalmente no he hecho tanto desarrollo de Java; por lo que sé, podría haber formas mucho más idiomáticas de ilustrar este punto.Quizás si escribimos un método para hacer el equivalente de lo
x++
que hace, esto lo aclarará más.¿Derecha? Incremente el valor pasado y devuelva el valor original: esa es la definición del operador postincremento.
Ahora, veamos cómo se desarrolla este comportamiento en su código de ejemplo:
postIncrement(x)
que hace Incrementosx
, sí. Y luego devuelve lo quex
estaba antes del incremento . Este valor de retorno se asigna ax
.Entonces, el orden de los valores asignados
x
es 0, luego 1, luego 0.Esto podría ser más claro aún si reescribimos lo anterior:
Su fijación en el hecho de que cuando reemplaza
x
en el lado izquierdo de la asignación anterior cony
"puede ver que primero incrementa x, y luego lo atribuye a y" me confunde. No es a lox
que se le está asignandoy
; es el valor anteriormente asignadox
. Realmente, la inyeccióny
hace que las cosas no sean diferentes del escenario anterior; simplemente tenemos:Entonces está claro:
x = x++
efectivamente no cambia el valor de x. Siempre hace que x tenga los valores x 0 , luego x 0 + 1 y luego x 0 nuevamente.Actualización : Incidentalmente, para que no dude que
x
alguna vez se asigna a 1 "entre" la operación de incremento y la asignación en el ejemplo anterior, he preparado una demostración rápida para ilustrar que este valor intermedio realmente "existe", aunque lo hará nunca ser "visto" en el hilo de ejecución.La demostración llama
x = x++;
en un bucle mientras un hilo separado imprime continuamente el valor dex
en la consola.A continuación se muestra un extracto de la salida del programa anterior. Observe la ocurrencia irregular de 1s y 0s.
fuente
Integer
clase, que es parte de la biblioteca estándar, e incluso tiene la ventaja de estar en caja automática desde y haciaint
casi de forma transparente.x
en su último ejemplo debe declararsevolatile
, de lo contrario, es un comportamiento indefinido y ver1
s es específico de la implementación.++
podría hacer antes o después de la asignación. Prácticamente hablando, puede haber un compilador que haga lo mismo que Java, pero no querrás apostar por él.x = x++
funciona de la siguiente manera:x++
. La evaluación de esta expresión produce un valor de expresión (que es el valor dex
antes del incremento) e incrementosx
.x
, sobrescribiendo el valor incrementadoEntonces, la secuencia de eventos se ve de la siguiente manera (es un bytecode descompilado real, como el producido por
javap -c
, con mis comentarios):A modo de comparación
x = ++x
:fuente
iinc
incrementa una variable, no incrementa un valor de pila, ni deja un valor en la pila (a diferencia de casi cualquier otra operación aritmética). Es posible que desee agregar el código generado por++x
para la comparación.Esto sucede porque el valor de
x
no se incrementa en absoluto.es equivalente a
Explicación:
Veamos el código de byte para esta operación. Considere una clase de muestra:
Ahora ejecutando el desensamblador de clase en esto obtenemos:
Ahora, la máquina virtual Java está basada en la pila, lo que significa que para cada operación, los datos se enviarán a la pila y desde la pila, los datos saldrán para realizar la operación. También hay otra estructura de datos, típicamente una matriz para almacenar las variables locales. Las variables locales reciben identificadores que son solo los índices de la matriz.
Veamos la mnemotecnia en el
main()
método:iconst_0
: El valor constante0
se transfiere a la pila.istore_1
: El elemento superior de la pila aparece y se almacena en la variable local con el índice1
que es
x
.iload_1
: El valor en la ubicación1
cuyo valorx
es0
, se inserta en la pila.iinc 1, 1
: El valor en la ubicación de la memoria1
se incrementa en1
. Entoncesx
ahora se convierte1
.istore_1
: El valor en la parte superior de la pila se almacena en la ubicación de la memoria1
. Eso se0
asigna ax
sobrescribir su valor incrementado.Por lo tanto, el valor de
x
no cambia dando como resultado el bucle infinito.fuente
++
), pero la variable se sobrescribe más tarde.int temp = x; x = x + 1; x = temp;
es mejor no usar una tautología en su ejemplo.Sin embargo, "
=
" tiene una prioridad de operador menor que "++
".Entonces
x=x++;
debe evaluar de la siguiente manerax
preparado para la asignación (evaluado)x
incrementadox
asignado ax
.fuente
++
tiene mayor prioridad que=
en C y C ++, pero la declaración no está definida en esos lenguajes.Ninguna de las respuestas fue acertada, así que aquí va:
Cuando estás escribiendo
int x = x++
, no estás asignandox
ser él mismo en el nuevo valor, estás asignandox
ser el valor de retorno de lax++
expresión. Que resulta ser el valor original dex
, como se insinúa en la respuesta de Colin Cochrane .Por diversión, pruebe el siguiente código:
El resultado será
El valor de retorno de la expresión es el valor inicial de
x
, que es cero. Pero más tarde, cuando leemos el valor dex
, recibimos el valor actualizado, ese es uno.fuente
Ya ha sido explicado bien por otros. Solo incluyo los enlaces a las secciones de especificación de Java relevantes.
x = x ++ es una expresión. Java seguirá el orden de evaluación . Primero evaluará la expresión x ++, que incrementará x y establecerá el valor del resultado al valor anterior de x . Luego asignará el resultado de la expresión a la variable x. Al final, x vuelve a su valor anterior.
fuente
Esta declaración:
evalúa así:
x
sobre la pila;x
;x
de la pila.Entonces el valor no ha cambiado. Compare eso con:
que se evalúa como:
x
;x
sobre la pila;x
de la pila.Lo que quieres es:
fuente
x++
fragmento de código.x++;
su soluciónx=x; x++;
y está haciendo lo que dice que está haciendo el código original.La respuesta es bastante directa. Tiene que ver con el orden en que se evalúan las cosas.
x++
devuelve el valor yx
luego incrementax
.En consecuencia, el valor de la expresión
x++
es0
. Entonces estás asignandox=0
cada vez en el ciclo. Ciertamentex++
incrementa este valor, pero eso sucede antes de la asignación.fuente
De http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
Para ilustrar, intente lo siguiente:
Que imprimirá 1 y 0.
fuente
Efectivamente está obteniendo el siguiente comportamiento.
La idea es que el operador posterior al incremento (x ++) incremente esa variable en cuestión DESPUÉS de devolver su valor para usar en la ecuación en la que se usa.
Editar: agregando un poco debido al comentario. Considérelo como lo siguiente.
fuente
Realmente no necesita el código de la máquina para comprender lo que está sucediendo.
Según las definiciones:
El operador de asignación evalúa la expresión del lado derecho y la almacena en una variable temporal.
1.1. El valor actual de x se copia en esta variable temporal
1.2. x se incrementa ahora.
La variable temporal se copia en el lado izquierdo de la expresión, que es x por casualidad. Por eso, el antiguo valor de x se copia nuevamente en sí mismo.
Es bastante simple
fuente
Esto se debe a que nunca se incrementa en este caso.
x++
usará su valor primero antes de incrementar como en este caso será como:Pero si haces
++x;
esto aumentará.fuente
El valor permanece en 0 porque el valor de
x++
es 0. En este caso, no importa si el valor dex
se incrementa o no, la asignaciónx=0
se ejecuta. Esto sobrescribirá el valor temporal incrementado dex
(que fue 1 por "muy poco tiempo").fuente
x++
, no para toda la tareax=x++;
Esto funciona como espera que el otro lo haga. Es la diferencia entre prefijo y postfix.
fuente
Piense en x ++ como una llamada de función que "devuelve" lo que X era antes del incremento (es por eso que se llama un incremento posterior).
Por lo tanto, el orden de operación es:
1: almacena en caché el valor de x antes de incrementar
2: incrementa x
3: devuelve el valor almacenado en caché (x antes de que se incremente)
4: el valor de retorno se asigna a x
fuente
Cuando ++ está en rhs, el resultado se devuelve antes de que se incremente el número. Cambie a ++ x y hubiera estado bien. Java habría optimizado esto para realizar una sola operación (la asignación de x a x) en lugar del incremento.
fuente
Bueno, hasta donde puedo ver, el error ocurre, debido a que la asignación anula el valor incrementado, con el valor anterior al incremento, es decir, deshace el incremento.
Específicamente, la expresión "x ++" tiene el valor de 'x' antes del incremento en oposición a "++ x" que tiene el valor de 'x' después del incremento.
Si está interesado en investigar el código de bytes, echaremos un vistazo a las tres líneas en cuestión:
7: iload_1 # Pondrá el valor de la 2da variable local en la pila
8: iinc 1,1 # incrementará la 2da variable local con 1, ¡tenga en cuenta que deja la pila intacta!
9: istore_1 # Aparecerá la parte superior de la pila y guardará el valor de este elemento en la segunda variable local
(Puede leer los efectos de cada instrucción JVM aquí )
Esta es la razón por la cual el código anterior se repetirá indefinidamente, mientras que la versión con ++ x no lo hará. El bytecode para ++ x debería ser bastante diferente, por lo que recuerdo del compilador 1.3 Java que escribí hace poco más de un año, el bytecode debería ser algo así:
Entonces, simplemente intercambiando las dos primeras líneas, cambia la semántica para que el valor que queda en la parte superior de la pila, después del incremento (es decir, el "valor" de la expresión) sea el valor después del incremento.
fuente
Entonces:
Mientras
Entonces:
Por supuesto, el resultado final es el mismo que solo
x++;
o++x;
en una línea por sí mismo.fuente
debido a la declaración anterior x nunca llega a 3;
fuente
Me pregunto si hay algo en la especificación de Java que defina con precisión el comportamiento de esto. (La implicación obvia de esa afirmación es que soy demasiado vago para verificarlo).
Nota del código de bytes de Tom, las líneas clave son 7, 8 y 11. La línea 7 carga x en la pila de cálculo. La línea 8 incrementa x. La línea 11 almacena el valor de la pila de nuevo a x. En los casos normales en los que no se asignan valores a sí mismos, no creo que haya alguna razón por la que no pueda cargar, almacenar e incrementar. Obtendría el mismo resultado.
Como, supongamos que tiene un caso más normal en el que escribió algo como: z = (x ++) + (y ++);
Si dijo (pseudocódigo para omitir tecnicismos)
o
debería ser irrelevante Cualquiera de las dos implementaciones debería ser válida, creo.
Sería extremadamente cauteloso al escribir código que depende de este comportamiento. Me parece muy dependiente de la implementación, entre las grietas en las especificaciones. La única vez que marcaría la diferencia es si hiciste algo loco, como en el ejemplo aquí, o si tienes dos hilos ejecutándose y dependías del orden de evaluación dentro de la expresión.
fuente
Creo que porque en Java ++ tiene una precedencia más alta que = (asignación) ... ¿Lo hace? Mire http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...
De la misma manera, si escribe x = x + 1 ... + tiene una precedencia mayor que = (asignación)
fuente
++
tiene mayor prioridad que=
en C y C ++ también, pero la declaración no está definida.La
x++
expresión se evalúa comox
. La++
parte afecta el valor después de la evaluación , no después de la declaración . por lo quex = x++
se traduce efectivamente enfuente
Antes de incrementar el valor en uno, el valor se asigna a la variable.
fuente
Está sucediendo porque se incrementó después. Significa que la variable se incrementa después de evaluar la expresión.
x es ahora 10, pero y es 9, el valor de x antes de que se incrementara.
Ver más en Definición de incremento posterior .
fuente
x
/y
ejemplo es diferente del código real, y la diferencia es relevante. Su enlace ni siquiera menciona Java. Para dos de los idiomas que no menciona, la declaración en la pregunta no está definido.Comprueba el siguiente código,
la salida será
post increment
significa incrementar el valor y devolver el valor antes del incremento . Es por ello que el valortemp
es0
. Entonces, ¿quétemp = i
pasa si esto está en un bucle (excepto la primera línea de código)? justo como en la pregunta !!!!fuente
El operador de incremento se aplica a la misma variable a la que está asignando. Eso es pedir problemas. Estoy seguro de que puede ver el valor de su variable x mientras ejecuta este programa ... eso debería dejar en claro por qué el ciclo nunca termina.
fuente