web-dev-qa-db-fra.com

Quelles sont les règles pour l'ordre d'évaluation en Java?

Je lis du texte Java et j'ai le code suivant:

int[] a = {4,4};
int b = 1;
a[b] = b = 0;

Dans le texte, l'auteur n'a pas donné d'explication claire et l'effet de la dernière ligne est le suivant: a[1] = 0;

Je ne suis pas sûr de comprendre: comment s'est déroulée l'évaluation?

81
ipkiss

Permettez-moi de le dire très clairement, car les gens le comprennent mal tout le temps:

L'ordre d'évaluation des sous-expressions est indépendant d'associativité et de précédence. L'associativité et la priorité déterminent dans quel ordre les opérateurs sont exécutés mais ne ne déterminent pas dans quel ordre les sous-expressions sont évaluées. Votre question concerne l’ordre dans lequel les sous-expressions sont évaluées. 

Considérez A() + B() + C() * D(). La multiplication est une priorité supérieure à l'addition, et l'addition est associative à gauche, ce qui équivaut à (A() + B()) + (C() * D()) Mais sachant que cela vous indique uniquement que le premier ajout aura lieu avant le deuxième ajout et que la multiplication aura lieu avant le deuxième ajout. Cela ne vous dit pas dans quel ordre A (), B (), C() et D() seront appelés! (Cela ne vous dit pas non plus si la multiplication a lieu avant ou après le premier ajout.) Il serait parfaitement possible d'obéir aux règles de priorité et associativité en compilant ceci comme suit:

d = D()          // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b      // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last

Toutes les règles de préséance et d'associativité y sont suivies - le premier ajout intervient avant le deuxième ajout et la multiplication avant le deuxième ajout. Il est clair que nous pouvons faire les appels à A (), B (), C() et D() dans l'ordre (quelconque) et toujours obéir aux règles de priorité et d'associativité!

Nous avons besoin d’une règle non liée aux règles de priorité et d’associativité pour expliquer l’ordre dans lequel les sous-expressions sont évaluées. La règle pertinente en Java (et C #) est "les sous-expressions sont évaluées de gauche à droite". Puisque A() apparaît à la gauche de C (), A() est évalué en premier, indépendamment du fait que C() est impliqué dans une multiplication. et A() n'est impliqué que dans une addition.

Alors maintenant, vous avez assez d'informations pour répondre à votre question. Dans a[b] = b = 0, les règles d'associativité indiquent qu'il s'agit de a[b] = (b = 0); mais cela ne signifie pas que le b=0 est exécuté en premier! Les règles de priorité indiquent que l'indexation est une priorité supérieure à l'affectation, mais cela ne signifie pas que l'indexeur s'exécute avant l'affectation la plus à droite.

(MISE À JOUR: Une version antérieure de cette réponse comportait quelques omissions minimes et pratiquement sans importance dans la section qui suit, que j'ai corrigées. J'ai également écrit un article de blog décrivant pourquoi ces règles sont raisonnables en Java et en C # ici: https: //ericlippert.com/2019/01/18/indexer-error-cases/ )

La priorité et l'associativité nous indiquent seulement que l'attribution de zéro à b doit avoir lieu avant l'affectation à a[b], car l'affectation de zéro calcule la valeur affectée dans l'opération d'indexation. La priorité et l'associativité ne disent rien sur le fait que le a[b] soit évalué avant ou après le b=0

Encore une fois, c’est la même chose que: A()[B()] = C() - Tout ce que nous savons, c’est que l’indexation doit avoir lieu avant la mission. Nous ne savons pas si A (), B () ou C() s'exécute en premier selon la priorité et l'associativité. Nous avons besoin d’une autre règle pour nous le dire.

La règle est, encore une fois, "quand vous avez le choix de ce qu'il faut faire en premier, allez toujours de gauche à droite". Cependant, il y a une ride intéressante dans ce scénario spécifique. L'effet secondaire d'une exception levée provoqué par une collection nulle ou un index hors plage est-il considéré comme faisant partie du calcul du côté gauche de l'affectation ou du calcul de l'affectation elle-même? Java choisit ce dernier. (Bien entendu, il s'agit d'une distinction qui ne compte que (si le code est déjà faux}, car un code correct ne supprime pas la valeur null ou ne transmet pas un index incorrect.)

Alors qu'est-ce qui se passe? 

  • Le a[b] se trouve à gauche du b=0, de sorte que a[b] exécute premier, ce qui entraîne a[1]. Cependant, la vérification de la validité de cette opération d'indexation est retardée.
  • Ensuite, le b=0 se produit. 
  • Ensuite, la vérification que a est valide et que a[1] est dans la plage se produit
  • L'affectation de la valeur à a[1] a lieu en dernier. 

Donc, bien que dans ce cas {spécifique} _, il y ait quelques subtilités à prendre en compte pour les rares cas d'erreur qui ne devraient pas se produire dans le code correct en premier lieu, en général, vous pouvez raisonner: il se passe des choses à gauche avant les choses à droite. C'est la règle que vous recherchez. Parler de priorité et d'associativité est à la fois déroutant et non pertinent.

Les gens se trompent tout le temps, même les gens qui devraient savoir mieux. J'ai édité beaucoup trop} livres de programmation qui énonçaient les règles de manière incorrecte. Il n'est donc pas surprenant que de nombreuses personnes aient des convictions totalement erronées sur la relation entre la précédence/associativité et l'ordre d'évaluation - à savoir que la réalité il n'y a pas une telle relation; ils sont indépendants.Si ce sujet vous intéresse, consultez mes articles sur le sujet pour en savoir plus:.

http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/

Ils sont à propos de C #, mais la plupart de ces choses s'appliquent également à Java.

They are about C#, but most of this stuff applies equally well to Java.

165
Eric Lippert

La réponse magistrale d'Eric Lippert n'est néanmoins pas utile car elle parle d'un langage différent. Il s’agit de Java, où la spécification du langage Java est la description définitive de la sémantique. En particulier, §15.26.1 est pertinent, car il décrit la commande d'évaluation de l'opérateur = (nous savons tous qu'il est à droite associative, oui?). Réduire un peu les éléments qui nous intéressent dans cette question:

Si l'expression de l'opérande de gauche est une expression d'accès au tableau ( §15.13 ), plusieurs étapes sont nécessaires:

  • Tout d'abord, la sous-expression de référence de tableau de l'expression d'accès au tableau d'opérandes de gauche est évaluée. Si cette évaluation se termine brusquement, l'expression d'affectation se termine brusquement pour la même raison; la sous-expression d'index (de l'expression d'accès au tableau d'opérandes de gauche) et d'opérande de droite ne sont pas évaluées et aucune affectation n'est effectuée.
  • Sinon, la sous-expression d'index de l'expression d'accès au tableau d'opérandes de gauche est évaluée. Si cette évaluation se termine de manière abrupte, l'expression d'affectation se termine de manière abrupte pour la même raison. L'opérande de droite n'est pas évalué et aucune affectation ne se produit.
  • Sinon, l'opérande de droite est évalué. Si cette évaluation se termine brusquement, l'expression d'affectation se termine brusquement pour la même raison et aucune affectation ne se produit.

[… Il décrit ensuite le sens réel de la tâche elle-même, que nous pouvons ignorer ici pour des raisons de concision…]

En bref, Java a un ordre évaluation très étroitement défini qui est à peu près exactement de gauche à droite dans les arguments d'un opérateur ou d'un appel de méthode. Les assignations de tableaux sont l’un des cas les plus complexes, mais même dans ce cas, il s’agit toujours de L2R. (Le JLS vous recommande de vous n'écrivez pas de code nécessitant ce type de contraintes sémantiques complexes}, et moi aussi: vous pouvez avoir assez de problèmes avec juste une affectation par instruction!)

C et C++ sont nettement différents de Java dans ce domaine: leurs définitions de langage laissent délibérément l'ordre d'évaluation indéfini pour permettre davantage d'optimisations. C # est apparemment comme Java, mais je ne connais pas suffisamment sa littérature pour pouvoir indiquer la définition formelle. (Cela varie beaucoup selon la langue, Ruby est strictement L2R, de même que Tcl - bien qu'il manque un opérateur d'affectation en soi pour des raisons non pertinentes ici - et Python est L2R mais R2L en ce qui concerne l'affectation , que je trouve étrange mais voilà.)

32
Donal Fellows
a[b] = b = 0;

1) L’opérateur d’indexation sur un tableau a une priorité plus élevée que l’opérateur d’affectation (voir cette réponse ):

(a[b]) = b = 0;

2) Selon 15.26. Opérateurs d'assignation de JLS

Il y a 12 opérateurs d'affectation; tous sont syntaxiquement associatifs à droite (ils se regroupent de droite à gauche). Ainsi, a = b = c signifie a = (b = c), ce qui attribue la valeur de c à b, puis la valeur de b à a.

(a[b]) = (b=0);

3) Selon 15.7. Ordre d'évaluation de JLS

Le langage de programmation Java garantit que les opérandes des opérateurs semblent être évalués dans un ordre d'évaluation spécifique, à savoir de gauche à droite.

et

L'opérande gauche d'un opérateur binaire semble être entièrement évalué avant qu'une partie de l'opérande droit ne soit évaluée. 

Alors:

a) (a[b]) évalué en premier à a[1]

b) alors (b=0) évalué à 0

c) (a[1] = 0) évalué en dernier

5
bup

Votre code est équivalent à:

int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;

ce qui explique le résultat.

1
Jérôme Verstrynge

Prenons un autre exemple plus détaillé ci-dessous.

En règle générale:

Il est préférable d’avoir un tableau des règles de l’ordre de priorité et de l’associativité à lire pour résoudre ces questions, par exemple. http://introcs.cs.princeton.edu/Java/11precedence/

Voici un bon exemple: 

System.out.println(3+100/10*2-13);

Question: Quel est le résultat de la ligne ci-dessus?

Réponse: appliquer les règles de priorité et d'associativité

Étape 1: Selon les règles de priorité: les opérateurs/et * ont priorité sur les opérateurs + -. Par conséquent, le point de départ pour exécuter cette équation sera réduit à:

100/10*2

Étape 2: Selon les règles et la priorité:/et * sont égaux en priorité. 

Comme les opérateurs/et * ont la même priorité, nous devons examiner l’associativité entre ces opérateurs. 

Selon les règles d’association de ces deux opérateurs particuliers, Nous commençons à exécuter l’équation de gauche à droite, c’est-à-dire que 100/10 est exécuté en premier:

100/10*2
=100/10
=10*2
=20

Étape 3: L'équation est maintenant dans l'état d'exécution suivant: 

=3+20-13

Selon les règles et la priorité: + et - sont égaux en priorité. 

Nous devons maintenant examiner l’associativité entre les opérateurs + et -. En fonction de l’associativité de ces deux opérateurs particuliers, Nous commençons à exécuter l’équation de gauche à droite, c’est-à-dire que 3 + 20 est exécuté en premier:

=3+20
=23
=23-13
=10

10 est la sortie correcte une fois compilé

Encore une fois, il est important d’avoir avec vous un tableau des règles de l’ordre de priorité et de l’associativité lorsque vous résolvez ces questions, par exemple. http://introcs.cs.princeton.edu/Java/11precedence/

0
Mark Burleigh