Question Pourquoi cela passe-t-il dans une boucle infinie?


J'ai le code suivant:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}

Nous savons qu'il aurait dû écrire juste x++ ou x=x+1, mais sur x = x++ il faut d'abord attribuer x à lui-même, et l'incrémenter plus tard. Pourquoi x continue avec 0 comme valeur?

--mettre à jour

Voici le bytecode:

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

}

Je vais lire sur le instructions pour essayer de comprendre ...


479
2017-09-30 14:07


origine


Réponses:


Remarque: À l'origine j'ai posté le code C # dans cette réponse à des fins d'illustration, puisque C # vous permet de passer int paramètres par référence à la ref mot-clé. J'ai décidé de le mettre à jour avec le vrai code Java légal en utilisant le premier MutableInt classe que j'ai trouvé sur Google pour trier approximative ref fait en C #. Je ne peux pas vraiment dire si cela aide ou blesse la réponse. Je dirai que je n'ai personnellement pas fait autant de développement Java; Donc, pour tout ce que je sais, il pourrait y avoir beaucoup plus de façons idiomatiques d'illustrer ce point.


Peut-être si nous écrivons une méthode pour faire l'équivalent de ce que x++ cela rendra-t-il cela plus clair.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

Droite? Incrémente la valeur passée et retourne la valeur d'origine: c'est la définition de l'opérateur postincrement.

Maintenant, voyons comment ce comportement se joue dans votre exemple de code:

MutableInt x = new MutableInt();
x = postIncrement(x);

postIncrement(x) Fait quoi? Incréments x, Oui. Et alors retourne quoi x  était avant l'incrément. Cette valeur de retour est ensuite affectée à x.

Donc l'ordre des valeurs assignées à x est 0, puis 1, puis 0.

Cela pourrait être plus clair si nous réécrivons ce qui précède:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.

Votre fixation sur le fait que lorsque vous remplacez x sur le côté gauche de l'affectation ci-dessus avec y, "vous pouvez voir qu'il commence par incrémenter x et l'attribue plus tard à y" me semble confus. Ce n'est pas x qui est affecté à y; c'est la valeur précédemment attribuée à x. Vraiment injecter y rend les choses différentes du scénario ci-dessus; nous avons simplement:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.

Donc c'est clair: x = x++ efficacement ne change pas la valeur de x. Il oblige toujours x à avoir les valeurs x0, alors x0 + 1, puis x0 encore.


Mettre à jour: D'ailleurs, de peur que vous ne doutiez de x jamais assigné à 1 "entre" l'opération d'incrémentation et l'affectation dans l'exemple ci-dessus, j'ai rassemblé une démo rapide pour illustrer que cette valeur intermédiaire "existe effectivement", bien qu'elle ne sera jamais "vue" sur l'exécution fil.

Les appels de démonstration x = x++; dans une boucle alors qu'un thread séparé imprime en continu la valeur de x à la console.

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}

Voici un extrait de la sortie du programme ci-dessus. Notez l'occurrence irrégulière des 1 et des 0.

Fil de départ
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

347
2017-09-30 15:09



x = x++ fonctionne de la manière suivante:

  • D'abord, il évalue l'expression x++. L'évaluation de cette expression produit une valeur d'expression (qui est la valeur de xavant l'incrémentation) et incrémente x.
  • Plus tard, il affecte la valeur d'expression à x, écrasant la valeur incrémentée.

Ainsi, la séquence d’événements se présente comme suit (il s’agit d’un véritable bytecode décompilé, produit par javap -c, avec mes commentaires):

   8: iload_1 // Mémoriser la valeur actuelle de x dans la pile
   9: iinc 1, 1 // Incrément x (ne change pas la pile)
   12: istore_1 // Ecrit la valeur de la pile à x

En comparaison, x = ++x:

   8: iinc 1, 1 // Incrément x
   11: iload_1 // Pousse la valeur de x sur la pile
   12: istore_1 // Pop la valeur de la pile à x

168
2017-09-30 14:13



Cela arrive parce que la valeur de x ne s'incrémente pas du tout.

x = x++;

est équivalent à

int temp = x;
x++;
x = temp;

Explication:

Regardons le code d'octet pour cette opération. Considérons un exemple de classe:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}

Maintenant, en exécutant le désassembleur de classes, nous obtenons:

$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    return

public static void main(java.lang.String[]);
  Code:
   0:    iconst_0
   1:    istore_1
   2:    iload_1
   3:    iinc    1, 1
   6:    istore_1
   7:    return
}

Maintenant le Java VM est basé sur la pile, ce qui signifie que pour chaque opération, les données seront poussées sur la pile et à partir de la pile, les données apparaîtront pour effectuer l'opération. Il existe également une autre structure de données, généralement un tableau pour stocker les variables locales. Les variables locales sont des identifiants qui ne sont que les index du tableau.

Regardons le mnémotechnique dans main() méthode:

  • iconst_0: La valeur constante 0 est poussé sur la pile.
  • istore_1: L'élément supérieur du pile est sorti et stocké dans le variable locale avec index 1 
     lequel est x.
  • iload_1 : La valeur à la emplacement 1 c'est la valeur de x lequel est 0, est poussé dans la pile.
  • iinc 1, 1 : La valeur à la emplacement mémoire 1 est incrémenté de 1. Alors x devient maintenant 1.
  • istore_1 : La valeur au sommet de la pile est stockée dans l'emplacement de la mémoire1. C'est 0 est assigné à x  écraser sa valeur incrémentée.

D'où la valeur de x ne change pas résultant dans la boucle infinie.


104
2017-09-30 14:15



  1. La notation préfixée incrémentera la variable AVANT que l'expression soit évaluée.
  2. La notation postfixe sera incrémentée APRÈS l'évaluation de l'expression.

Toutefois "="a préséance opérateur inférieure à"++".

Alors x=x++; devrait évaluer comme suit

  1. x préparé pour l'affectation (évalué)
  2. x incrémenté
  3. Valeur précédente de x assigné à x.

52
2017-09-30 14:27



Aucune des réponses n'est assez précise, alors voici:

Quand vous écrivez int x = x++, vous n'attribuez pas x être lui-même à la nouvelle valeur, vous attribuez x être la valeur de retour de la x++ expression. Ce qui arrive à être la valeur originale de x, comme l'a laissé entendre La réponse de Colin Cochrane .

Pour vous amuser, testez le code suivant:

public class Autoincrement {
        public static void main(String[] args) {
                int x = 0;
                System.out.println(x++);
                System.out.println(x);
        }
}

Le résultat sera

0
1

La valeur de retour de l'expression est la valeur initiale de x, qui est zéro. Mais plus tard, en lisant la valeur de x, nous recevons la valeur mise à jour, c’est une.


34
2017-09-30 14:37



Cela a déjà été bien expliqué par d'autres. Je viens d'inclure les liens vers les sections de spécification Java pertinentes.

x = x ++ est une expression. Java suivra le ordre d'évaluation. Il évaluera d'abord l'expression x ++, qui va incrémenter x et définir la valeur du résultat à la valeur précédente de x. Alors ce sera affecter le résultat de l'expression à la variable x. À la fin, x est de retour à sa valeur précédente.


29
2017-09-30 15:33



Cette déclaration:

x = x++;

évalue comme ceci:

  1. Pousser x sur la pile;
  2. Incrément x;
  3. Pop x de la pile.

Donc, la valeur est inchangée. Comparez cela à:

x = ++x;

qui évalue comme:

  1. Incrément x;
  2. Pousser x sur la pile;
  3. Pop x de la pile.

Ce que vous voulez c'est:

while (x < 3) {
  x++;
  System.out.println(x);
}

18
2017-09-30 14:10



La réponse est assez simple. Cela concerne l'ordre dans lequel les choses sont évaluées. x++ renvoie la valeur x puis incrémente x.

Par conséquent, la valeur de l'expression x++ est 0. Donc, vous attribuez x=0 chaque fois dans la boucle. Certainement x++ incrémente cette valeur, mais cela se produit avant l'affectation.


10
2017-10-01 22:18



De http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

Les opérateurs d'incrémentation / décrémentation peuvent   être appliqué avant (préfixe) ou après   (postfixe) l'opérande. Le code   résultat ++; et ++ résultat; vont tous deux finir   en résultat étant incrémenté de un.   La seule différence est que le préfixe   version (++ résultat) évalue à la   valeur incrémentée, tandis que le   La version de postfix (résultat ++) évalue   à la valeur d'origine. Si vous êtes   juste effectuer un simple   incrément / décrément, ce n'est pas vraiment   importe quelle version vous choisissez. Mais   si vous utilisez cet opérateur dans une partie d'un   plus grande expression, celle que vous   choisir peut faire une importante   différence.

Pour illustrer cela, essayez ce qui suit:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

Ce qui imprimera 1 et 0.


8
2017-09-30 14:27



Vous obtenez effectivement le comportement suivant.

  1. saisir la valeur de x (qui est 0) comme "le résultat" du côté droit
  2. incrémenter la valeur de x (donc x est maintenant 1)
  3. assigne le résultat du côté droit (qui a été enregistré sous la forme 0) à x (x vaut 0)

L'idée étant que l'opérateur de post-incrément (x ++) incrémente cette variable APRES avoir renvoyé sa valeur pour l'utiliser dans l'équation dans laquelle elle est utilisée.

Editer: Ajouter un peu à cause du commentaire. Considérez-le comme ce qui suit.

x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.

7
2017-09-30 14:16