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 devrait d’abord s’attribuer x
à lui-même, puis l’incrémenter. Pourquoi x
continue-t-il avec 0
comme valeur?
- mise à 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 ...
Note : À l'origine, j'ai posté du code C # dans cette réponse à des fins d'illustration, car C # vous permet de passer int
paramètres par référence à l'aide du ref
mot clé. J'ai décidé de le mettre à jour avec le code légal Java) actuel en utilisant le premier MutableInt
classe trouvée sur Google pour trier approximativement ce que ref
fait en C # .Je ne sais pas vraiment si cela aide ou fait mal la réponse. Je dirai que personnellement, je n'ai pas fait grand chose Java développement; donc pour tout ce que je sachez qu'il pourrait y avoir des moyens beaucoup plus idiomatiques pour illustrer ce point.
Peut-être que si nous écrivons une méthode pour faire l'équivalent de ce que x++
Cela rendra plus clair.
public MutableInt postIncrement(MutableInt x) {
int valueBeforeIncrement = x.intValue();
x.add(1);
return new MutableInt(valueBeforeIncrement);
}
Droite? Incrémenter la valeur passée et retourner la valeur d'origine: c'est la définition de l'opérateur postincrément.
Voyons maintenant comment cela se passe dans votre exemple de code:
MutableInt x = new MutableInt();
x = postIncrement(x);
postIncrement(x)
fait quoi? Incrémente x
, oui. Et puis retourne ce que x
était avant l'incrément . Cette valeur de retour est ensuite assignée à x
.
Donc, l'ordre des valeurs assignées à x
est 0, puis 1, puis 0.
Cela pourrait être plus clair encore si nous réécrivions 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
à gauche de l'affectation ci-dessus par y
, vous pouvez voir qu'elle incrémente d'abord x et l'attribue ensuite à y "me frappe comme confus. Ce n'est pas x
qui est assigné à y
; c'est la valeur précédemment attribuée à x
. En réalité, injecter y
ne fait pas la différence avec le 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.
Il est donc clair que x = x++
Ne change pas la valeur de x. Il fait toujours en sorte que x ait les valeurs x, alors x + 1, puis x encore.
Update : Incidemment, de peur que vous doutiez que x
soit jamais affecté à 1 "entre" l'opération d'incrémentation et l'affectation dans l'exemple ci-dessus. , J'ai réuni une démo rapide pour illustrer le fait que cette valeur intermédiaire "existe", bien qu'elle ne soit jamais "vue" sur le thread en cours d'exécution.
La démo appelle x = x++;
Dans une boucle, tandis qu'un thread séparé imprime en permanence la valeur de x
sur 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);
}
}
}
Ci-dessous un extrait de la sortie du programme ci-dessus. Notez l'occurrence irrégulière des 1 et des 0.
Début du fil de fond ... 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 1
x = x++
fonctionne de la manière suivante:
x++
. L'évaluation de cette expression produit une valeur d'expression (qui est la valeur de x
avant incrément) et incrémente x
.x
, écrasant ainsi la valeur incrémentée.Ainsi, la séquence d’événements ressemble à ce qui suit (c’est un bytecode décompilé, tel que 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émenter x (ne modifie pas la pile) 12: istore_1 // Écrire une valeur mémorisée dans la pile à x
En comparaison, x = ++x
:
8: iinc 1, 1 // Incrément x 11: iload_1 // Envoie la valeur de x à la pile 12: istore_1 // Ajoute une valeur de la pile à x
Cela se produit parce que la valeur de x
n’est pas incrémentée 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 classe, 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
}
La machine virtuelle Java est maintenant basée sur la pile, ce qui signifie que pour chaque opération, les données seront placées dans la pile et à partir de la pile, les données sortiront 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 reçoivent des identifiants qui ne sont que les index du tableau.
Regardons la méthode mnémoniques dans main()
:
iconst_0
: La valeur constante 0
Est appliquée à la pile.istore_1
: L'élément supérieur de la pile est sorti et stocké dans la variable locale avec index 1
x
.iload_1
: La valeur à l'emplacement 1
Qui correspond à la valeur de x
qui correspond à 0
Est insérée dans la pile.iinc 1, 1
: La valeur à l'emplacement de mémoire 1
Est incrémentée de 1
. Donc, x
devient maintenant 1
.istore_1
: La valeur en haut de la pile est stockée dans l'emplacement de mémoire 1
. C'est-à-dire que 0
Est attribué à x
écrasement sa valeur incrémentée.Par conséquent, la valeur de x
ne change pas, ce qui entraîne la boucle infinie.
Toutefois "=
"a une priorité inférieure à celle de" ++
".
Alors x=x++;
devrait évaluer comme suit
x
préparé pour l'affectation (évalué)x
incrémentéx
attribuée à x
.Aucune des réponses n'était assez bien placée, alors voici:
Quand vous écrivez int x = x++
, vous n'affectez pas x
pour être lui-même à la nouvelle valeur, vous attribuez x
à la valeur de retour du x++
expression. Ce qui se trouve être la valeur initiale de x
, comme indiqué dans réponse de Colin Cochrane .
Pour le plaisir, 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, lors de la lecture de la valeur de x
, nous recevons la valeur mise à jour, c'est-à-dire une.
Cela a déjà été bien expliqué par d'autres. Je viens d’inclure les liens vers les sections correspondantes de la spécification Java).
x = x ++ est une expression. Java suivra le ordre d'évaluation . Il évaluera d'abord l'expression x ++, qui incrémentera x et définira la valeur du résultat sur la valeur précédente de x . Ensuite il va assigner le résultat de l'expression à la variable x. À la fin, x est de retour à sa valeur précédente.
Cette déclaration:
x = x++;
évalue comme ceci:
x
sur la pile;x
;x
de la pile.Donc, la valeur est inchangée. Comparez cela à:
x = ++x;
qui évalue comme:
x
;x
sur la pile;x
de la pile.Ce que tu veux c'est:
while (x < 3) {
x++;
System.out.println(x);
}
La réponse est assez simple. Cela a à voir avec 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.
De http://download.Oracle.com/javase/tutorial/Java/nutsandbolts/op1.html
Les opérateurs d'incrémentation/décrémentation peuvent être appliqués avant (préfixe) ou après (postfixe) l'opérande. Le résultat du code ++; et ++ résultat; les deux aboutiront à un résultat incrémenté de un. La seule différence est que la version du préfixe (résultat ++) correspond à la valeur incrémentée, alors que la version de postfix (résultat ++) correspond à la valeur initiale. Si vous effectuez simplement une simple incrémentation/décrémentation, peu importe la version que vous choisissez. Mais si vous utilisez cet opérateur dans le cadre d'une expression plus grande, celle que vous choisissez peut faire une différence significative.
Pour illustrer, 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.
Vous n'avez pas vraiment besoin du code machine pour comprendre ce qui se passe.
Selon les définitions:
L'opérateur d'affectation évalue l'expression de droite et la stocke dans une variable temporaire.
1.1. La valeur actuelle de x est copiée dans cette variable temporaire
1.2. x est incrémenté maintenant.
La variable temporaire est ensuite copiée dans la partie gauche de l'expression, qui est x par hasard! C'est pourquoi l'ancienne valeur de x est à nouveau copiée sur elle-même.
C'est assez simple.
Vous obtenez effectivement le comportement suivant.
L'idée est que l'opérateur de post-incrémentation (x ++) incrémente la variable en question APRÈS avoir renvoyé sa valeur pour qu'elle soit utilisée dans l'équation dans laquelle elle est utilisée.
Edit: Ajoute un petit peu à cause du commentaire. Considérez cela comme 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.
C'est parce qu'il ne s'incrémente jamais dans ce cas. x++
utilisera d'abord sa valeur avant de s'incrémenter comme dans ce cas, il ressemblera à:
x = 0;
Mais si vous faites ++x;
cela va augmenter.
La valeur reste à 0 car la valeur de x++
vaut 0. Dans ce cas, peu importe que la valeur de x
soit augmentée ou non, l’affectation x=0
est exécuté. Cela écrasera la valeur incrémentée temporaire de x
(qui était 1 pour un "temps très court").
Lorsque le ++ est sur le rhs, le résultat est renvoyé avant que le nombre ne soit incrémenté. Changez pour ++ x et ça aurait été bien. Java l'aurait optimisé pour effectuer une seule opération (l'affectation de x à x) plutôt que l'incrément.
Cela fonctionne comme vous vous attendez à l'autre. C'est la différence entre prefix et postfix.
int x = 0;
while (x < 3) x = (++x);
Pour autant que je sache, l'erreur se produit, en raison de l'affectation remplaçant la valeur incrémentée, avec la valeur antérieure à l'incrémentation, c'est-à-dire qu'elle annule l'incrément.
Plus précisément, l'expression "x ++" a la valeur "x" avant l'incrément, par opposition à "++ x", qui a la valeur "x" après l'incrémentation.
Si vous souhaitez étudier le code intermédiaire, nous allons examiner les trois lignes en question:
7: iload_1
8: iinc 1, 1
11: istore_1
7: iload_1 # mettra la valeur de la 2ème variable locale sur la pile
8: iinc 1,1 # incrémentera la 2ème variable locale avec 1, notez qu'elle laisse la pile intacte!
9: istore_1 # Ouvre le haut de la pile et enregistre la valeur de cet élément dans la 2e variable locale
(Vous pouvez lire les effets de chaque instruction JVM ici )
C'est pourquoi le code ci-dessus sera mis en boucle indéfiniment, alors que la version avec ++ x ne le sera pas. Le bytecode pour ++ x devrait avoir un aspect assez différent, pour autant que je me souvienne du compilateur 1.3 Java que j'ai écrit il y a un peu plus d'un an, le bytecode devrait ressembler à ceci:
iinc 1,1
iload_1
istore_1
Donc, simplement échanger les deux premières lignes, change la sémantique de sorte que la valeur laissée en haut de la pile, après l’incrément (c’est-à-dire la "valeur" de l’expression) soit la valeur après l’incrément.
Pensez à x ++ en tant qu'appel de fonction qui "retourne" ce que X était avant l'incrément (c'est pourquoi il est appelé post-incrément).
Donc, l'ordre d'opération est:
1: met en cache la valeur de x avant de l'incrémenter
2: incrément x
3: retourne la valeur en cache (x avant son incrémentation)
4: la valeur renvoyée est attribuée à x
x++
=: (x = x + 1) - 1
Alors:
x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!
Tandis que
++x
=: x = x + 1
Alors:
x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x
Bien sûr, le résultat final est le même que juste x++;
ou ++x;
sur une ligne à part.
Ça se passe parce que c'est post incrémenté. Cela signifie que la variable est incrémentée après l'évaluation de l'expression.
int x = 9;
int y = x++;
x est maintenant 10, mais y est 9, la valeur de x avant son incrémentation.
Voir plus dans définition du post-incrément.
x = x++; (increment is overriden by = )
à cause de la déclaration ci-dessus, x n'atteint jamais 3;
Vérifiez le code ci-dessous,
int x=0;
int temp=x++;
System.out.println("temp = "+temp);
x = temp;
System.out.println("x = "+x);
la sortie sera,
temp = 0
x = 0
post increment
Signifie incrémente la valeur et renvoie la valeur avant l'incrément. C'est pourquoi la valeur temp
est 0
. Alors que faire si temp = i
Et cela est dans une boucle (sauf pour la première ligne de code). juste comme dans la question !!!!
Je pense que parce que dans Java ++ a une priorité plus élevée que = (affectation) ... Est-ce que c'est? Regardez http://www.cs.uwf.edu/~ eelsheik/cop2253/resources/op_precedence.html ...
De même si vous écrivez x = x + 1 ... + a une priorité plus élevée que = (affectation)
Avant d'incrémenter la valeur de un, la valeur est affectée à la variable.
Le x++
expression est évaluée à x
. Le ++
La partie affecte la valeur après le evaluation, pas après le statement. alors x = x++
est effectivement traduit en
int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
Je me demande s’il existe quelque chose dans la spécification Java) qui définit précisément le comportement de ceci. (L’implication évidente de cette déclaration étant que je suis trop paresseux pour vérifier.)
Remarquez à partir du bytecode de Tom, les lignes clés sont 7, 8 et 11. La ligne 7 charge x dans la pile de calcul. La ligne 8 augmente x. La ligne 11 stocke la valeur de la pile dans x. Dans les cas normaux où vous ne vous assignez pas de valeurs, je ne pense pas qu'il y aurait une raison pour laquelle vous ne pourriez pas charger, stocker, puis incrémenter. Vous obtiendriez le même résultat.
Par exemple, supposons que vous ayez un cas plus normal dans lequel vous avez écrit quelque chose comme: z = (x ++) + (y ++);
Que ce soit dit (pseudo-code pour ignorer les détails techniques)
load x
increment x
add y
increment y
store x+y to z
ou
load x
add y
store x+y to z
increment x
increment y
devrait être hors de propos. Soit la mise en œuvre devrait être valide, je pense.
Je serais extrêmement prudent en écrivant du code qui dépend de ce comportement. Cela me semble très dépendant de la mise en œuvre, entre les fissures. Le seul cas où cela ferait une différence serait que vous fassiez quelque chose de fou, comme dans l'exemple ci-dessous, ou que vous ayez deux threads en cours d'exécution et que vous dépendiez de l'ordre d'évaluation dans l'expression.