Pourquoi x == (x = y) n'est-il pas identique à (x = y) == x?


207

Prenons l'exemple suivant:

class Quirky {
    public static void main(String[] args) {
        int x = 1;
        int y = 3;

        System.out.println(x == (x = y)); // false
        x = 1; // reset
        System.out.println((x = y) == x); // true
     }
}

Je ne sais pas s'il y a un élément dans la spécification du langage Java qui dicte le chargement de la valeur précédente d'une variable pour la comparaison avec le côté droit ( x = y) qui, par l'ordre impliqué par les crochets, devrait être calculé en premier.

Pourquoi la première expression est-elle évaluée false, mais la seconde est-elle évaluée true? Je m'attendais (x = y)à être évalué en premier, puis il se comparerait xà lui-même ( 3) et reviendrait true.


Cette question est différente de l' ordre d'évaluation des sous-expressions dans une expression Java en ce qu'elle xn'est certainement pas une «sous-expression» ici. Il doit être chargé pour la comparaison plutôt que d'être «évalué». La question est spécifique à Java et l'expression x == (x = y), à la différence des constructions improbables farfelues généralement conçues pour des questions d'entrevue délicates, est venue d'un vrai projet. Il était censé être un remplacement d'une ligne pour l'idiome de comparaison et de remplacement

int oldX = x;
x = y;
return oldX == y;

qui, étant encore plus simple que l'instruction x86 CMPXCHG, méritait une expression plus courte en Java.


62
Le côté gauche est toujours évalué avant le côté droit. Les crochets ne font aucune différence.
Louis Wasserman

11
L'évaluation de l'expression x = yest certainement pertinente et provoque l'effet secondaire xdéfini sur la valeur de y.
Louis Wasserman

50
Faites-vous plaisir à vous et à vos coéquipiers et ne mélangez pas la mutation d'état dans la même ligne que l'examen d'état. Cela réduit considérablement la lisibilité de votre code. (Il y a des cas où c'est absolument nécessaire à cause des exigences d'atomicité, mais les fonctions pour celles-ci existent déjà et leur but serait instantanément reconnu.)
jpmc26

50
La vraie question est de savoir pourquoi vous voulez écrire du code comme celui-ci.
klutt

26
La clé de votre question est votre fausse croyance selon laquelle les parenthèses impliquent un ordre d'évaluation. C'est une croyance commune à cause de la façon dont nous enseignons les mathématiques à l'école primaire et parce que certains livres de programmation pour débutants se trompent toujours , mais c'est une fausse croyance. C'est une question assez fréquente. Vous pourriez bénéficier de la lecture de mes articles sur le sujet; ils concernent C # mais ils s'appliquent à Java: ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order ericlippert.com/2009/08/10/precedence-vs-order-redux
Eric Lippert

Réponses:


97

qui, par l'ordre implicite entre parenthèses, doit être calculé en premier

Non. Il est faux de penser que les parenthèses ont un effet (général) sur l'ordre de calcul ou d'évaluation. Ils contraignent uniquement les parties de votre expression dans une arborescence particulière, liant les bons opérandes aux bonnes opérations pour le travail.

(Et, si vous ne les utilisez pas, ces informations proviennent de la "priorité" et de l'associativité des opérateurs, ce qui est le résultat de la façon dont l'arbre de syntaxe du langage est défini. En fait, c'est toujours exactement comme cela que vous travaillez lorsque vous utilisez des parenthèses, mais nous simplifions et disons que nous ne nous appuyons pas sur des règles de priorité.)

Une fois cela fait (c'est-à-dire une fois que votre code a été analysé dans un programme), ces opérandes doivent encore être évalués, et il existe des règles distinctes sur la façon dont cela est fait: lesdites règles (comme Andrew nous l'a montré) indiquent que le LHS de chaque opération est évalué en premier en Java.

Notez que ce n'est pas le cas dans toutes les langues; par exemple, en C ++, à moins que vous n'utilisiez un opérateur de court-circuit comme &&ou ||, l'ordre d'évaluation des opérandes n'est généralement pas spécifié et vous ne devez pas vous y fier dans les deux cas.

Les enseignants doivent cesser d'expliquer la priorité de l'opérateur en utilisant des phrases trompeuses telles que «ceci fait que l'ajout se produit en premier». Étant donné une expression, x * y + zl'explication appropriée serait "la priorité de l'opérateur fait que l'addition a lieu entre x * yet z, plutôt qu'entre yet z", sans mention d'aucun "ordre".


6
Je souhaite que mes professeurs aient fait une certaine séparation entre les mathématiques sous-jacentes et la syntaxe qu'ils utilisaient pour le représenter, comme si nous passions une journée avec des chiffres romains ou une notation polonaise ou autre et que cet ajout a les mêmes propriétés. Nous avons appris l'associativité et toutes ces propriétés au collège, donc il y avait beaucoup de temps.
John P

1
Heureux que vous ayez mentionné que cette règle ne s'applique pas à toutes les langues. De plus, si l'un ou l'autre côté a un autre effet secondaire, comme écrire dans un fichier ou lire l'heure actuelle, il n'est pas défini (même en Java) dans quel ordre cela se produit. Cependant, le résultat de la comparaison sera comme s'il avait été évalué de gauche à droite (en Java). Un autre côté: un certain nombre de langues interdisent simplement le mélange de l'affectation et de la comparaison de cette manière par des règles de syntaxe, et le problème ne se poserait pas.
Abel

5
@JohnP: Cela empire. 5 * 4 signifie-t-il 5 + 5 + 5 + 5 ou 4 + 4 + 4 + 4 + 4? Certains enseignants insistent sur le fait qu'un seul de ces choix est juste.
Brian

3
@Brian Mais ... mais ... la multiplication des nombres réels est commutative!
Courses de légèreté en orbite

2
Dans mon monde de pensée, une paire de parenthèses représente "est nécessaire pour". En calculant ´a * (b + c) ´, les parenthèses exprimeraient que le résultat de l'addition est nécessaire pour la multiplication. Toute préférence d'opérateur implicite peut être exprimée par des parenthèses, à l' exception des règles LHS en premier ou RHS en premier. (Est-ce vrai?) @Brian En mathématiques, il y a quelques rares cas où la multiplication peut être substituée par l'addition répétée mais ce n'est de loin pas toujours vrai (en commençant par des nombres complexes mais sans s'y limiter). Donc, vos éducateurs devraient vraiment avoir un œil sur ce que les gens disent ...
syck

164

==est un opérateur d'égalité binaire .

L'opérande de gauche d'un opérateur binaire semble être entièrement évalué avant l' évaluation de toute partie de l'opérande de droite .

Spécification Java 11> Ordre d'évaluation> Évaluez d'abord l'opérande de gauche


42
Le libellé "semble être" ne semble pas sûr, tbh.
M. Lister

86
"semble être" signifie que la spécification n'exige pas que les opérations soient réellement effectuées dans cet ordre chronologiquement, mais elle exige que vous obteniez le même résultat que vous obtiendriez si elles l'étaient.
Robyn

24
@MrLister "semble être" semble être un mauvais choix de mots de leur part. Par "apparaître", ils signifient "se manifester comme un phénomène pour le développeur". "est effectivement" peut être une meilleure expression.
Kelvin

18
dans la communauté C ++, c'est l'équivalent de la règle "comme si ... ... l'opérande doit se comporter" comme si "il était implémenté selon les règles suivantes, même si techniquement ce n'est pas le cas.
Michael Edenfield

2
@Kelvin Je suis d'accord, j'aurais choisi ce mot non plus, plutôt que "semble l'être".
MC Emperor

149

Comme l'a dit LouisWasserman, l'expression est évaluée de gauche à droite. Et java ne se soucie pas de ce que "évaluer" fait réellement, il se soucie seulement de générer une valeur (non volatile, finale) avec laquelle travailler.

//the example values
x = 1;
y = 3;

Donc, pour calculer la première sortie de System.out.println(), la procédure suivante est effectuée:

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

et pour calculer la seconde:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

Notez que la deuxième valeur sera toujours évaluée à true, quelles que soient les valeurs initiales de xet y, parce que vous comparez efficacement l'affectation d'une valeur à la variable à laquelle elle est affectée, a = bet bsera, évaluée dans cet ordre, toujours la même par définition.


"De gauche à droite" est également vrai en mathématiques, soit dit en passant, juste au moment où vous arrivez à une parenthèse ou à une priorité, vous les parcourez et évaluez tout à l'intérieur de gauche à droite avant d'aller plus loin sur le niveau principal. Mais les mathématiques ne feraient jamais cela; la distinction n'a d'importance que parce qu'il ne s'agit pas d'une équation mais d'une opération combinée, faisant à la fois une affectation et une équation d'un seul coup . Je ne ferais jamais cela parce que la lisibilité est médiocre, sauf si je faisais du golf de code ou cherchais un moyen d'optimiser les performances, puis il y aurait des commentaires.
Harper - Réintègre Monica

25

Je ne sais pas s'il y a un élément dans la spécification du langage Java qui dicte le chargement de la valeur précédente d'une variable ...

Il y a. La prochaine fois que vous ne savez pas ce que dit la spécification, veuillez lire la spécification, puis posez la question si elle n'est pas claire.

... le côté droit (x = y)qui, selon l'ordre indiqué par les parenthèses, doit être calculé en premier.

Cette affirmation est fausse. Les parenthèses n'impliquent pas un ordre d'évaluation . En Java, l'ordre d'évaluation est de gauche à droite, quelles que soient les parenthèses. Les parenthèses déterminent où se trouvent les limites de la sous-expression, et non l'ordre d'évaluation.

Pourquoi la première expression est-elle fausse, mais la seconde est-elle vraie?

La règle pour l' ==opérateur est: évaluer le côté gauche pour produire une valeur, évaluer le côté droit pour produire une valeur, comparer les valeurs, la comparaison est la valeur de l'expression.

En d'autres termes, la signification de expr1 == expr2est toujours la même que si vous aviez écrit temp1 = expr1; temp2 = expr2;puis évalué temp1 == temp2.

La règle pour l' =opérateur avec une variable locale sur le côté gauche est: évaluer le côté gauche pour produire une variable, évaluer le côté droit pour produire une valeur, effectuer l'affectation, le résultat est la valeur qui a été affectée.

Alors mettez-le ensemble:

x == (x = y)

Nous avons un opérateur de comparaison. Évaluez le côté gauche pour produire une valeur - nous obtenons la valeur actuelle de x. Évaluer le côté droit: c'est une affectation, donc nous évaluons le côté gauche pour produire une variable - la variable x- nous évaluons le côté droit - la valeur actuelle de y- l'assigner à x, et le résultat est la valeur assignée. Nous comparons ensuite la valeur d'origine de xà la valeur qui a été attribuée.

Vous pouvez le faire (x = y) == xcomme un exercice. Encore une fois, rappelez-vous, toutes les règles d'évaluation du côté gauche se produisent avant toutes les règles d'évaluation du côté droit .

Je me serais attendu à ce que (x = y) soit évalué en premier, puis il comparerait x avec lui-même (3) et retournerait vrai.

Votre attente est basée sur un ensemble de croyances incorrectes sur les règles de Java. J'espère que vous avez maintenant des croyances correctes et que vous vous attendez à l'avenir à de vraies choses.

Cette question est différente de "l'ordre d'évaluation des sous-expressions dans une expression Java"

Cette affirmation est fausse. Cette question est tout à fait pertinente.

x n'est certainement pas une «sous-expression» ici.

Cette affirmation est également fausse. Il s'agit d'une sous-expression deux fois dans chaque exemple.

Il doit être chargé pour la comparaison plutôt que d'être «évalué».

Je n'ai aucune idée de ce que cela signifie.

Apparemment, vous avez encore beaucoup de fausses croyances. Mon conseil est que vous lisiez les spécifications jusqu'à ce que vos fausses croyances soient remplacées par de vraies croyances.

La question est spécifique à Java et l'expression x == (x = y), contrairement aux constructions improbables farfelues couramment conçues pour les questions d'entrevue délicates, provient d'un vrai projet.

La provenance de l'expression n'est pas pertinente pour la question. Les règles pour de telles expressions sont clairement décrites dans la spécification; lis le!

Il était censé être un remplacement d'une ligne pour l'idiome de comparaison et de remplacement

Étant donné que ce remplacement sur une ligne a causé beaucoup de confusion en vous, le lecteur du code, je dirais que c'était un mauvais choix. Rendre le code plus concis mais plus difficile à comprendre n'est pas une victoire. Il est peu probable de rendre le code plus rapide.

Soit dit en passant , C # a comparer et remplacer comme méthode de bibliothèque, qui peut être transposée en une instruction machine. Je crois que Java n'a pas une telle méthode, car elle ne peut pas être représentée dans le système de type Java.


8
Si quelqu'un pouvait parcourir l'intégralité du JLS, il n'y aurait aucune raison de publier des livres Java et au moins la moitié de ce site serait également inutile.
John McClane

8
@JohnMcClane: Je vous assure, il n'y a aucune difficulté à parcourir toute la spécification, mais aussi, vous n'avez pas à le faire. La spécification Java commence par une "table des matières" utile qui vous aidera à accéder rapidement aux parties qui vous intéressent le plus. Elle est également en ligne et consultable par mot-clé. Cela dit, vous avez raison: il existe de nombreuses bonnes ressources qui vous aideront à apprendre comment Java fonctionne; mon conseil est de les utiliser!
Eric Lippert

8
Cette réponse est inutilement condescendante et grossière. N'oubliez pas: soyez gentil .
walen

7
@LuisG .: Aucune condescendance n'est voulue ou implicite; nous sommes tous ici pour apprendre les uns des autres, et je ne recommande rien que je n'ai pas fait moi-même quand j'étais débutant. Ce n'est pas impoli non plus. Identifier clairement et sans ambiguïté leurs fausses croyances est une gentillesse pour l'affiche originale . Se cacher derrière la «politesse» et permettre aux gens de continuer à avoir de fausses croyances est inutile et renforce les mauvaises habitudes de pensée .
Eric Lippert

5
@LuisG.: J'avais l'habitude d'écrire un blog sur la conception de JavaScript, et les commentaires les plus utiles que j'ai jamais reçus venaient de Brendan indiquant clairement et sans ambiguïté où je me trompais. C'était génial et je l'appréciais de prendre le temps, car j'ai ensuite vécu les 20 prochaines années de ma vie sans répéter cette erreur dans mon propre travail, ou pire, en l'enseignant aux autres. Cela m'a également donné l'occasion de corriger ces mêmes fausses croyances chez les autres en m'utilisant comme un exemple de la façon dont les gens en viennent à croire de fausses choses.
Eric Lippert

16

Elle est liée à la priorité des opérateurs et à la façon dont les opérateurs sont évalués.

Les parenthèses '()' ont une priorité plus élevée et ont une associativité de gauche à droite. L'égalité '==' vient ensuite dans cette question et a l'associativité de gauche à droite. L'affectation '=' vient en dernier et a une associativité de droite à gauche.

Pile d'utilisation du système pour évaluer l'expression. L'expression est évaluée de gauche à droite.

Venons-en maintenant à la question originale:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

Le premier x (1) sera poussé pour s'empiler. puis inner (x = y) sera évalué et poussé pour s'empiler avec la valeur x (3). Maintenant x (1) sera comparé à x (3) donc le résultat est faux.

x = 1; // reset
System.out.println((x = y) == x); // true

Ici, (x = y) sera évalué, maintenant la valeur x devient 3 et x (3) sera poussé pour s'empiler. Maintenant x (3) avec une valeur modifiée après l'égalité sera poussé vers la pile. Maintenant, l'expression sera évaluée et les deux seront les mêmes, donc le résultat est vrai.


12

Ce n'est pas pareil. Le côté gauche sera toujours évalué avant le côté droit, et les crochets ne spécifient pas un ordre d'exécution, mais un regroupement de commandes.

Avec:

      x == (x = y)

Vous faites essentiellement la même chose que:

      x == y

Et x aura la valeur de y après la comparaison.

Avec:

      (x = y) == x

Vous faites essentiellement la même chose que:

      x == x

Après x ont y valeur de. Et cela reviendra toujours vrai .


9

Dans le premier test que vous vérifiez, ne 1 == 3.

Dans le deuxième test, votre vérification fait 3 == 3.

(x = y) attribue la valeur et cette valeur est testée. Dans le premier exemple, x = 1 en premier, puis x est attribué 3. Est-ce que 1 == 3?

Dans ce dernier, x se voit attribuer 3, et évidemment c'est toujours 3. Est-ce que 3 == 3?


8

Considérez cet autre exemple, peut-être plus simple:

int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true

Ici, l'opérateur de pré-incrémentation dans ++xdoit être appliqué avant la comparaison - tout comme (x = y)dans votre exemple doit être calculé avant la comparaison.

Cependant, l' évaluation de l'expression se produit toujours de gauche → à → droite , donc la première comparaison est en fait 1 == 2alors que la seconde l'est 2 == 2.
La même chose se produit dans votre exemple.


8

Les expressions sont évaluées de gauche à droite. Dans ce cas:

int x = 1;
int y = 3;

x == (x = y)) // false
x ==    t

- left x = 1
- let t = (x = y) => x = 3
- x == (x = y)
  x == t
  1 == 3 //false

(x = y) == x); // true
   t    == x

- left (x = y) => x = 3
           t    =      3 
-  (x = y) == x
-     t    == x
-     3    == 3 //true

5

Fondamentalement, la première instruction x avait sa valeur 1 Donc Java compare 1 == à la nouvelle variable x qui ne sera pas la même

Dans le second, vous avez dit x = y, ce qui signifie que la valeur de x a changé et donc lorsque vous l'appelez à nouveau, ce sera la même valeur, d'où la raison pour laquelle c'est vrai et x == x


4

== est un opérateur d'égalité de comparaison et fonctionne de gauche à droite.

x == (x = y);

ici l'ancienne valeur assignée de x est comparée à la nouvelle valeur assignée de x, (1 == 3) // false

(x = y) == x;

Alors que, ici, la nouvelle valeur assignée de x est comparée à la nouvelle valeur de maintien de x qui lui est assignée juste avant la comparaison, (3 == 3) // true

Considérez maintenant ceci

    System.out.println((8 + (5 * 6)) * 9);
    System.out.println(8 + (5 * 6) * 9);
    System.out.println((8 + 5) * 6 * 9);
    System.out.println((8 + (5) * 6) * 9);
    System.out.println(8 + 5 * 6 * 9);

Production:

342

278

702

342

278

Ainsi, les parenthèses ne jouent leur rôle majeur dans les expressions arithmétiques que dans les expressions de comparaison.


1
La conclusion est fausse. Le comportement n'est pas différent entre les opérateurs arithmétiques et de comparaison. x + (x = y)et (x = y) + xmontrerait un comportement similaire à l'original avec les opérateurs de comparaison.
JJJ

1
@JJJ Dans x + (x = y) et (x = y) + x il n'y a pas de comparaison impliquée, il s'agit simplement d'attribuer une valeur y à x et de l'ajouter à x.
Nisrin Dhoondia

1
... oui, c'est le point. "Les parenthèses ne jouent leur rôle majeur dans les expressions arithmétiques que dans les expressions de comparaison" est faux car il n'y a pas de différence entre les expressions arithmétiques et les expressions de comparaison.
JJJ

2

La chose ici est l'ordre de priorité des opérateurs arithmatiques / opérateurs relationnels des deux opérateurs par =rapport à ==celui dominant ==(les opérateurs relationnels dominent) car il précède les =opérateurs d'affectation. Malgré la priorité, l'ordre d'évaluation est LTR (GAUCHE À DROITE) la priorité apparaît dans l'image après l'ordre d'évaluation. Donc, quelle que soit l'évaluation des contraintes, le LTR.


1
La réponse est fausse. La priorité de l'opérateur n'affecte pas l'ordre d'évaluation. Lisez quelques-unes des réponses les plus votées pour l'explication, en particulier celle-ci .
JJJ

1
Correct, c'est en fait la façon dont on nous enseigne l'illusion de restrictions sur la préséance dans toutes ces choses, mais correctement souligné qu'il n'a aucun impact car l'ordre d'évaluation reste de gauche à droite
Himanshu Ahuja

-1

Il est facile dans la deuxième comparaison à gauche d'affecter après avoir assigné y à x (à gauche) puis de comparer 3 == 3. Dans le premier exemple vous comparez x = 1 avec une nouvelle assignation x = 3. Il semble que il y a toujours des états de lecture de l'état courant de gauche à droite de x.


-2

Le type de question que vous avez posée est une très bonne question si vous voulez écrire un compilateur Java ou tester des programmes pour vérifier qu'un compilateur Java fonctionne correctement. En Java, ces deux expressions doivent produire les résultats que vous avez vus. En C ++, par exemple, ils n'ont pas à le faire - donc si quelqu'un a réutilisé des parties d'un compilateur C ++ dans son compilateur Java, vous pourriez théoriquement trouver que le compilateur ne se comporte pas comme il se doit.

En tant que développeur de logiciels, écrivant du code qui est lisible, compréhensible et maintenable, les deux versions de votre code seraient considérées comme horribles. Pour comprendre ce que fait le code, il faut savoir exactement comment le langage Java est défini. Quelqu'un qui écrit à la fois du code Java et du code C ++ frémirait en regardant le code. Si vous devez vous demander pourquoi une seule ligne de code fait ce qu'elle fait, vous devez éviter ce code. (Je suppose et j'espère que les gars qui ont répondu correctement à votre question «pourquoi» éviteront eux-mêmes cet indice de code).


"Pour comprendre ce que fait le code, il faut savoir exactement comment le langage Java est défini." Mais que se passe-t-il si chaque collègue le considère comme un bon sens?
BinaryTreeee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.