51

En el código veo muchas veces x++ en bucles, pero alguna vez me encuentro ++x.

¿Hay alguna diferencia entre estas dos expresiones?

lois6b
  • 7,419
  • 5
  • 29
  • 50
Jordi Castilla
  • 7,189
  • 10
  • 35
  • 60
  • 5
    Una pregunta para quienes piensan que ++x incrementa el valor de la variable antes de que se evalúe la expresión. ¿Cuanto vale b tras : int a=3; int b= ++a + ++a;? Si fuese así, al incrementarse a dos veces antes de la expresión el resultado sería 10. Pero no, no es 10, es 9. a se incrementa una vez en medio de la expresión (después de evaluar b) y otra vez en medio de la expresión (después de que a hubiese sido incrementado antes). – Anonymous Coward Dec 04 '15 at 21:28
  • @JoseAntonioDuraOlmos b será 9 porque (3+1) + (4+1) = 9 al acabar de ejecutar, el valor de a es 5 y el de b es 9. – Carlos López Jun 26 '19 at 11:05

7 Answers7

41

POST INCREMENTO:

x++, El valor de la expresión es el valor de la variable antes que el nuevo valor sea almacenado.

Ejemplo:

int x = 1;
System.out.println(x++); // imprime 1.
System.out.println(x);   // imprime 2.

PRE INCREMENTO:

++x, El valor de la expresión es el valor de la variable después que el nuevo valor es almacenado.

Ejemplo:

int x = 1;
System.out.println(++x); // imprime 2. 
System.out.println(x);   // imprime 2.

Similar para x-- y --x

Ejemplos:

int x = 1;
System.out.println(x--); // imprime 1. 
System.out.println(x);   // imprime 0.

int x = 1;
System.out.println(--x); // imprime 0. 
System.out.println(x);   // imprime 0.

Más información:

Jorgesys
  • 103,630
  • 13
  • 52
  • 124
40

En el codigo veo muchas veces x++ en loops, pero alguna vez me encuentro ++x.

Si te refieres a construcciones de este tipo:

public static void main(String[] args) {
    for (int i = 0; i < 10; ++i) {
        System.out.println(i);
    }
    for (int i = 0; i < 10; i++) {
        System.out.println(i);
    }
}

... entonces no hay diferencia. Si vemos el bytecode:

public static void main(java.lang.String[]);
  Code:
     0: iconst_0
     1: istore_1
     2: iload_1
     3: bipush        10
     5: if_icmpge     21
     8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    11: iload_1
    12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    15: iinc          1, 1
    18: goto          2
    21: iconst_0
    22: istore_1
    23: iload_1
    24: bipush        10
    26: if_icmpge     42
    29: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    32: iload_1
    33: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    36: iinc          1, 1
    39: goto          23
    42: return

En ambos casos, justo después de invocar el println ( invokevirtual #3 ), se incrementa la variable 1 (en el código es i para ambos casos) en 1. Véase el iinc 1, 1 antes del goto.


No obstante, si te refieres a algo como esto:

public static void main(String[] args) {
    for (int i = 0; i < 10;) {
        System.out.println(i++);
    }
    for (int i = 0; i < 10;) {
        System.out.println(++i);
    }
}

... entonces hay diferencia. Revisando el bytecode:

public static void main(java.lang.String[]);
  Code:
     0: iconst_0
     1: istore_1
     2: iload_1
     3: bipush        10
     5: if_icmpge     21
     8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    11: iload_1
    12: iinc          1, 1
    15: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    18: goto          2
    21: iconst_0
    22: istore_1
    23: iload_1
    24: bipush        10
    26: if_icmpge     42
    29: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    32: iinc          1, 1
    35: iload_1
    36: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    39: goto          23
    42: return

En el primer caso, primero se carga el valor de la variable 1 (i) para ser usado en el println. Véase el iload_1. Después se incrementa esta variable en 1. Véase el iinc 1, 1.

En el segundo caso, primero se incrementa el valor de la variable 1 (i) en 1, Véase el iinc 1, 1. Luego se carga el valor de la variable 1 (i) para ser usado en el println. Véase el iload_1.

Carlos Muñoz
  • 12,864
  • 2
  • 42
  • 62
Paul Vargas
  • 181
  • 1
  • 20
  • 39
18

La diferencia no está en que el incremento se ejecute antes o después de evaluar la expresión sino en el valor de retorno de las dos expresiones.

Según la especificación oficial de Java en la sección: 15.14.2. Postfix Increment Operator ++ y 15.15.1. Prefix Increment Operator ++

15.14.2. Postfix Increment Operator ++

...The value of the postfix increment expression is the value of the variable before the new value is stored.

Traducción

15.14.2. Operador de Incremento Postfijo ++

El valor de la expresión de incremento postfijo es el valor de la variable antes que el nuevo valor sea almacenado.

Igualmente

15.15.1. Prefix Increment Operator ++

The value of the prefix increment expression is the value of the variable after the new value is stored.

Traducción

15.15.1. Operador de Incremento Prefijo ++

El valor de la expresión de incremento prefijo es el valor de la variable después que el nuevo valor es almacenado.

Ambas incrementan la variable x en 1. La diferencia es que la expresión ++x devuelve el valor de la variable incrementada en cambio la expresión x++ devuelve el valor original de variable antes de ser incrementada.

Es decir:

int x = 5;
System.out.println(++x);  // Imprime 6, x vale 6: El resultado de la expresión ++x es 6 y se imprime 6
System.out.println(x); // Imprime 6, x vale 6: x ya se incrementó por lo que devuelve 6

Mientras que:

int x = 5;
System.out.println(x++);  // Imprime 5, x vale 6. La variable x ya es 6 pero el resultado de la expresión es 5 por lo que se imprime 5
System.out.println(x); // Imprime 6, x vale 6: x ya se incrementó por lo que devuelve 6
Carlos Muñoz
  • 12,864
  • 2
  • 42
  • 62
  • Es correcto lo que dices @CarlosMuñoz, ya lo revisé con linqpad, creo que es un poco confuso cuando dices "sino en el valor de retorno de las dos expresiones" pues se está retornando la misma variable pero en tiempo distintos,en el siguiente párrafo lo aclaras, me tomaré un tiempo para responder la pregunta en SO en inglés, creo que también es válido no?. – Juan Ruiz de Castilla Dec 03 '15 at 22:36
  • 1
    Este pregunta es acerca de java, LINQPad no tiene peso aquí pero deben haber otros decompiladores paa ver el bytecode – Carlos Muñoz Dec 03 '15 at 23:31
  • 1
    *"La diferencia no está en que el incremento se ejecute antes o después de evaluar la expresión sino en el valor de retorno de las dos expresiones"* ambas explicaciones significan **lo mismo**. La segunda, de hecho, es consecuencia de la primera. – Darkhogg Dec 04 '15 at 07:52
  • 1
    +1. En efecto, así es. La diferencia está en el valor al que se evalúa la expresión ++x o x++, como indican los apartados 15.14.2 y 15.15.2 de la Java Language Specification. – Anonymous Coward Dec 04 '15 at 21:01
  • 1
    @Darkhogg, el incremento no se ejecuta ni antes ni después de evaluar la expresión sino *durante* la evaluación de la expresión es decir es parte del código generado por el compilador. No tiene lógica pensar que se ejecuta primero `System.out.println()` y luego se evalúe la expresión que es su argumento cuando en realidad se evalúa primero `x++` y luego su resultado es pasado a `System.out.println()`, ambos el prefijo y postfijo incrementan la variable como resultado de evaluar la expresión – Carlos Muñoz Dec 04 '15 at 23:51
  • @JuanRuizdeCastilla, tanto el incremento postfijo y el prefijo no devuelven variables sino valores, como dice la documentación "The result of the postfix increment expression is not a variable, but a value. " http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.15.1 – Carlos Muñoz Dec 04 '15 at 23:55
  • también piensa que no significan lo mismo, y le di vueltas un tiempo. Por otro lado, olvidé mencionar que probé con C#, pero para ambos casos entiendo que debe ser igual. – Juan Ruiz de Castilla Dec 05 '15 at 00:12
4

Sé que la pregunta está taggeada en Java, y desconozco la implementación en este lenguaje (aunque supongo que será similar), pero en C#, aunque lo normal e intuitivo es decir que "uno devuelve el valor antes de incrementar, y el otro después", y eso más o menos da el significado básico... la realidad es que la implementación no es así, y tiene una secuencia de eventos muy concreta.

Esto no es un problema si lo usamos sólo: x++; no trae ningún problema y la explicación simple es válida... pero si nos metemos en multihilos, y operaciones largas con diferentes precedencias de operadores, la cosa cambia.

Como digo, desconozco cómo lo hace Java, pero en C#, la secuencia de cada comando está perfectamente definida (e imagino que en Java será similar):

Para el tipo de prefijo (++x), la secuencia es:

  1. Se evalua x para producir la variable
  2. El valor de la variable se copia a un espacio temporal
  3. La variable temporal se incrementa y produce un nuevo valor (que no sobreescribe al temporal)
  4. El nuevo valor se guarda en la variable
  5. Se devuelve el nuevo valor (no la variable)

Para el tipo sufijo (x++), la secuencia es:

  1. Se evalua x para producir la variable
  2. El valor de la variable se copia a un espacio temporal
  3. La variable temporal se incrementa y produce un nuevo valor (que no sobreescribe al temporal)
  4. El nuevo valor se guarda en la variable
  5. Se devuelve el valor de la copia temporal

Por tanto, la secuencia de eventos ocurre en ambos casos en el mismo orden, y no cambia ni la evaluación ni el incremento de la variable, lo único que cambia es el valor que se devuelve. Sólo hay diferencia en el paso 5.

En C# usando extensiones de métodos, esto es muy fácil de probar... en Java no lo tengo tan claro (sólo tengo nociones básicas de Java).

Perdón por haber contestado usando C#, estando la pregunta taggeada como Java, pero como ya digo, sospecho que la implementación debe ser similar, si no igual.

Jcl
  • 1,273
  • 8
  • 9
3

Ambas incrementan el valor de la variable en 1. Si las utilizas en una linea donde sea la unica expresion no hay ninguna diferencia, pero si las ejecutas junto con otras hay una variacion importante.

  • ++x lo hace antes de que se evalue la expresión. Tambien se llama PREINCREMENTO

    Ejemplo:

    int x = 5;
    System.out.println(++x); // escribira 6 porque ejecutará x + 1 ANTES de printar
    System.out.println(x);   // volverá a escribir 6!!!!
    
  • x++ lo hace posteriormente a que la expresión sea evaluada. También se llama POSTINCREMENTO

    Ejemplo:

    int x = 5;
    System.out.println(x++); // escribirá 5 porque ejecutará x + 1 DESPUÉS de printar
    System.out.println(x);   // escribirá 6
    

Este tipo de expresiones tambien es muy habitual al hacer asignaciones de un array cuando realizas un bucle sin contador (por ejemplo for-each) o rellenando varias propiedades de un mismo objeto ahorrando asi una linea de codigo y ganando legibilidad.

arrayALlenar[i++] = elemento;  

en vez de

arrayALlenar[i] = elemento;  
i++;
Jordi Castilla
  • 7,189
  • 10
  • 35
  • 60
  • 4
    *`System.out.println(x++); // escribirá 5 porque ejecutará x + 1 DESPUÉS de printar .`* Si bien imprimirá 5, la explicación es incorrecta. Primero se evalúa el argumento `x++` y luego se ejecuta el método `println`. Lo que realmente sucede es que la **variable** se incrementa pero el **resultado de la expresión** será el valor que tenía originalmente por lo que devuelve el 5 – Carlos Muñoz Dec 03 '15 at 17:12
  • 1
    -1. "++x" no "lo hace antes de que se evalue la expresión." x se evalúa antes de ser incrementado. Véase el apartado 15.15.2. Prefix Decrement Operator -- de la Java Language Specification. – Anonymous Coward Dec 04 '15 at 20:59
  • Corrección, quiero decir el apartado 15.15.1. Prefix Increment Operator ++ – Anonymous Coward Dec 04 '15 at 21:22
3

++x incrementa la variable antes de ser llamada. X++ la llama y después la incrementa.

Jorgesys
  • 103,630
  • 13
  • 52
  • 124
Emiliano
  • 49
  • 1
  • 1
2

Ambos hacen lo mismo salvo que el de postincremento se evalúa al valor de la variable ANTES de que se almacene el valor incrementado y el de preincremento se evalúa al valor de la variable DESPUÉS de que se almacene el valor incrementado.

El pre y post incremento se definen en la Especificación del Lenguaje Java (JLS) en dos apartados de los que reproduzco traducidas las partes que son significativas para esta pregunta (énfasis mío) :

  • 15.14.2:Operador de Incremento Postfijo ++
    (..omitido..)
    En tiempo de ejecución, si la evaluación de la expresión se completa abruptamente, entonces la expresión de incremento postfijo se completa abruptamente for la misma razón y no hay incremento. En caso contrario, el valor 1 se añade al valor de la variable y la suma es almacenada en la variable. (...omitido...) El valor de la expresión de incremento postfijo es el valor de la variable antes de que se almacene el nuevo valor.
    (..omitido..)

  • 15.15.1. Operador de Incremento Prefijo ++
    (..omitido..)
    En tiempo de ejecución, si la evaluación de la expresión se completa abruptamente, entonces la expresión de decremento prefijo se completa abruptamente for la misma razón y no hay incremento. En caso contrario, el valor 1 se añade al valor de la variable y la suma es almacenada en la variable. (...omitido...) El valor de la expresión de incremento postfijo es el valor de la variable después de que el nuevo valor sea almacenado.
    (..omitido..)

Obsérvese que es todo igual, salvo el valor con que se evalúa la expresión, que es el que tenía la variable antes de que se almacenase el nuevo valor incrementado para el incremento postfijo y es el valor que tiene la variable después de que sea incrementado para el incremento prefijo.

Todo esto no tiene ninguna importancia en código como el siguiente :

for ( int n=1; n<10; ++n)
for ( int n=1; n<10; n++)

En ese código, para un mismo valor de n, las expresiones ++n y n++ se evalúan a valores distintos, pero esto es irrelevante puesto que este valor no es utilizado y lo único que tiene efecto significativo es el hecho de que la variable se incremente en 1, que sucede en ambos casos. Ambos bucles se ejecutarán el mismo número de iteraciones.

Pero sí es importante en código como el siguiente :

int a; int b;
a = 3;
b = a++;
System.out.println( "a="+a + " b=" + b);
a = 3;
b = ++a; 
System.out.println( "a="+a + " b=" + b);

Donde la salida es :

a=4 b=3
a=4 b=4

En la expresión b = a++ se evalúa la variable a (que vale 3), se añade 1 a este valor y el resultado de la suma (4) se almacena en la variable a. El resultado de la expresión a++ es el valor que tiene la variable a antes de que se le almacene el nuevo valor (que era 3), con lo que el resultado de la expresión a++ es 3. Y esto, 3, es lo que se almacena en b.

En la expresión b = ++a se evalúa la variable a (que vale 3), se añade 1 a este valor y el resultado de la suma (4) se almacena en la variable a. El resultado de la expresión ++a es el valor que tiene la variable a después de que se le almacene el nuevo valor (que es 4), con lo que el resultado de la expresión ++a es 4. Y esto, 4, es lo que se almacena en b.

Siguiendo estríctamente esta forma de evaluar el incremento pre y postfijo podemos evaluar corréctamente cualquier expresión en que estén involucrados. Por ejemplo :

    int a; int b;
    a = 3;
    b = ++a + a++;
    System.out.println( "a="+a + " b=" + b);
    a = 3;
    b = a++ + a++; 
    System.out.println( "a="+a + " b=" + b);

En la primera expresión b acaba valiendo 8 porque el primer ++a se evalúa a 4 (el valor tras almacenar el incremento) y para cuando se ejecuta el segundo a++ la variable a ya contiene 4, se incrementa a 5 pero el valor de la expresión a++ es el de la variable antes de incrementarse, con lo que se evalúa a 4. Y 4+4=8.
En la segunda expresión b acaba valiendo 7 porque el primer a++ se evalua a 3 (el valor antes de almacenar el incremento) y para cuando se ejecuta el segundo a++ la variable a ya contiene 4, se incrementa a 5 pero el valor de la expresión a++ es el de la variable antes de incrementarse con lo que se evalúa a 4. Y 3+4=7.

PERO ES MEJOR NO HACER ESO

Aunque entendamos perféctamente cómo funcionan los operadores de pre y post incremento el utilizarlos en expresiones que no sean triviales solo va a servir para confundir. A ti, a tus cooperadores, a tu coordinador, al que revisa el código, al que tiene que ocuparse del código 5 años después cuando el que lo escribió ya hace tiempo que se fue de la empresa, al gato...

Anonymous Coward
  • 6,185
  • 4
  • 31
  • 58