Previous Up Next

B.3.4  Principales expressions et instructions

Nous décrivons maintenant ce qui se trouve dans le corps des méthodes, c’est-à-dire le code qui fait le vrai travail. Le corps d’une méthode est une séquence d’instructions). Les instructions sont exécutées. Une instruction (par ex. une affectation) peut inclure une expression. Les expressions sont évaluées en un résultat.

La distinction entre expressions (dont l’évaluation produit un résultat) et instructions (dont l’exécution ne produit rien) est assez hypocrite, car toute expression suivie de « ; » devient une instruction (le résultat est jeté).

Les expressions les plus courantes sont :

Constantes
Soit 1 (entier), true (booléen), "coucou !" (chaîne), etc. Une constante amusante est null, qui est un objet sans champs ni méthodes.
Usage de variable
Soit « x », où x est le nom d’une variable (locale) déclarée par ailleurs.
Mot-clé this
Dans une méthode dynamique , this désigne l’objet dont on a appelé la méthode. Dans un constructeur, this désigne l’objet en cours de construction. Il en résulte que this n’est jamais null, car si on en est arrivé à exécuter un corps de méthode, c’est bien que l’objet dont a appelé la méthode existait.
Accès à un champ
Si x est un nom de champ et classe un nom de classe C, alors « C.x » désigne le contenu du champ m de C. De même « objet.x » désigne le champ de nom x de l’objet objet. Notez que, contrairement à x, objet n’est pas forcément un nom, c’est une expression dont la valeur est un objet. Heureusement ou malheureusement, il existe des notations abrégées qui allègent l’écriture (voir B.3.5), mais font parfois passer l’accès à un champ pour un usage de variable.
Appel de méthode
C’est un peu comme pour les champs :
statique
Soit, C.m(…),
dynamique
ou bien, objet.m(…).
m est un nom de méthode et (…) est une séquence d’expressions séparées par des virgules. Notez bien que les mêmes notations abrégées que pour les champs s’appliquent au nom de la méthode. Incidemment, si une méthode a un type de retour void, son appel n’est pas vraiment une expression, c’est une instruction.
Appel de constructeur
Généralement de la forme new C (…). La construction des tableaux est l’occasion de nombreuses variantes, voir B.3.6
Usage des opérateurs
Par exemple i+1 (addition) ou i == 1 || i == 2 (opérateur égalité et opérateur ou logique). Quelques opérateurs inattendus sont donnés en B.7.2. Notons qu’un « opérateur » en informatique est simplement une fonction dont l’application se fait par une syntaxe spéciale. L’application des opérateurs est souvent infixe, c’est-à-dire que l’opérateur apparaît entre ses arguments. Mais elle peut être préfixe, c’est-à-dire que l’opérateur est avant son argument, comme dans le cas de la négation des booléens ! ; ou encore postfixe, comme pour l’opérateur de post-incrément i++. En Java, comme souvent, les opérateurs eux-mêmes sont exclusivement composés de caractères non alphabétiques (+, -, =, etc.).
Accès dans les tableaux
Par exemple t[i], où t est un tableau défini par ailleurs. Il n’est pas surprenant que l’on puisse mettre une expression à la place de i. Il est un peu plus surprenant que cela soit également le cas pour t, comme par exemple dans t[i][j], à comprendre comme (t[i])[j] (t est un tableau de tableaux).
Affectation
Par exemple i = 1, l’expression à droite de = est calculée sa valeur est rangée dans la variable donnée à gauche de =. En fait, on peut trouver autre chose qu’une variable à gauche de =, on peut trouver tout ce qui désigne une case de mémoire, par exemple, un élément de tableau t[i] ou une désignation de champ objet.x.

L’affectation est une expression dont le résultat est la valeur affectée. Ce qui permet des trucs du genre i = j = 0, pour initialiser i et j à zéro. Cela se comprend si on lit cette expression comme i = (j = 0).

Expression parenthésée
Si e est une expression, alors (e) est aussi une expression. Cela permet essentiellement de contourner les priorités relatives des opérateurs, mais aussi de rendre un source plus clair. Par exemple, on peut écrire (i == 1) || (i == 2), c’est peut-être plus lisible que i == 1 || i == 2.

Java a beaucoup emprunté au langage C, il reprend quelques expressions assez peu ordinaires.

Incrément, décrément
Soit i variable de type entier (en fait, soit e désignation de case mémoire qui contient un entier). Alors l’expression i++ range i+1 dans i et renvoie l’ancienne valeur de i. L’expression i-- fait la même chose avec i-1. Enfin l’expression ++i (resp. --i) est similaire, mais elle renvoie la valeur incrémentée (resp. décrémentée) de i en résultat.
Affectations particulières
La construction op= expression est sensiblement équivalente à i = i op expression. Par exemple:
  i *= 2
range deux fois le contenu de i dans i et renvoie donc ce contenu doublé. Les finauds noteront que ++i est aussi i += 1.

Ces expressions avancées sont géniales, mais il est de bon goût de les employer avec parcimonie. Que l’on essaie de deviner ce que fait t[++i] *= --t[i++] et on comprendra.

Les instructions les plus courantes sont les suivantes:

Expression comme une instruction : « e ; »
Évidemment, cette construction n’est utile que si e fait des effets de bord (c’est-à-dire fait autre chose que rendre son résultat). C’est bien sûr le cas d’une affectation.
Séquence
On peut grouper plusieurs instructions en une séquence d’instruction, les instructions s’exécutent dans l’ordre.
  i = i + 1;
  i = i + 1;
Déclarations de variables locales
Une déclaration de variable (suivie de ;) est une instruction qui réserve de la place pour la variable déclarée et l’initialise éventuellement.
  int i = 0;
Il ne faut pas confondre affectation et déclaration, dans le premier cas, on modifie le contenu d’une variable qui existe déjà, dans le second on crée une nouvelle variable.
Bloc
On peut mettre une séquence d’instructions dans un bloc {}. La portée des déclaration internes au bloc s’éteint à la sortie du bloc. Par exemple, le programme
  int i = 0 ;
  {
    int i = 1; // Déclaration d'un nouvel i
    System.out.println("i=" + i);
  }
  System.out.println("i=" + i);
affiche une première ligne i=1 puis une seconde i=0. Il faut bien comprendre que d’un affichage à l’autre l’usage de variable « i » ne fait pas référence à la même variable. Lorsque l’on veut savoir à quelle déclaration correspond un usage, la règle est de remonter le source du regard vers le haut, jusqu’à trouver la bonne déclaration. C’est ce que l’on appelle parfois, la portée lexicale.

Attention, seule la portée des variables est limitée par les blocs, en revanche l’effet des instruction passe allègrement les frontières de blocs. Par exemple, le programme

  int i = 0 ;
  {
    i = 1; // Affectation de i
    System.out.println("i=" + i);
  }
  System.out.println("i=" + i);

affiche deux lignes i=1.

Retour de méthode
On peut dans le corps d’une méthode retourner à tout moment par l’instruction « return expression; », où expression est une expression dont le type est celui des valeurs retournées par la méthode.

Par exemple, la méthode twice qui double son argument entier s’écrit

  int twice (int i) {
    return i+i ;
  }

Si la méthode ne renvoie rien, alors return n’a pas d’argument.

  void rien () {
    return ;
  }

À noter que, si la dernière instruction d’une méthode est return ; (sans argument), alors on peut l’omettre. De sorte que la méthode rien s’écrit aussi :

  void rien () { }
Conditionnelle
C’est la très classique instruction if:
  if (i % 2 == 0) { // opérateur modulo
    System.out.println("C'est pair") ;
  } else {
    System.out.println("C'est impair") ;
  }
Instruction switch
C’est une généralisation de la conditionnelle aux types d’entiers. Elle permet de « brancher » selon la valeur d’un entier (de byte à long, mais aussi char). Par exemple :
switch (i) {
  case -1:
    System.out.println("moins un") ;
    break ;
  case 0:
    System.out.println("zéro") ;
    break ;
  case 1:
    System.out.println("un") ;
    break ;
  default:
    System.out.println("beaucoup") ;
  }
}
Selon la valeur de l’entier i on sélectionnera l’une des trois clauses case, ou la clause par défaut default. Il faut surtout noter le break, qui renvoie le contrôle à la fin du switch. En l’absence de break l’exécution se poursuit en séquence, donc la clause suivante est exécutée. Ainsi si on omet les break et que i vaut -1 on a l’affichage
moins un
zéro
un
beaucoup
Cette abominable particularité permet de grouper un peu les cas. Par exemple,
switch (i) {
  case -1: case 0: case 1:
    System.out.println("peu") ; break ;
  default:
    System.out.println("beaucoup") ;
}
Noter que si la clause se termine par return, alors break n’est pas utile.
static String estimer(int i) {
  switch (i) {
    case -1: return "moins un" ;
    case 0: return "zéro" ;
    case 1: return "un" ;
    defaultreturn "beaucoup" ;
  }
}
Boucle while
C’est la boucle la plus classique, celle que possèdent tous les langages de programmation, ou presque. Voici les entiers de zéro à neuf.
  int i = 0 ;
  while (i < 10) {
    System.out.println(i) ;
    i = i+1 ;
  }
Soit while (expression) instruction, on exécute expression, qui est une expression booléenne, si le résultat est false c’est fini, sinon on exécute instruction et on recommence.
Boucle do
C’est la même chose mais on teste la condition à la fin du tour de boucle, conclusion : on passe au moins une fois dans la boucle. Voici une autre façon d’afficher les entiers de 0 à 9.
  int i = 0 ;
  do {
    System.out.println(i) ;
    i = i+1 ;
  } while (i < 10)
Boucle for
C’est celle du langage C.
  for (int i=0 ; i < 10 ; i = i+1)
    System.out.println(i);
La syntaxe générale est
  for (einit ; econd ;  enext)
    instruction
L’expression einit est évaluée une fois initialement, exceptionnellement « l’expression » einit peut être une déclaration de variable, auquel cas la portée de la variable est limitée à la boucle. L’expression econd est de type boolean, c’est la condition testée avant chaque itération (y compris la première), l’itération a lieu si econd vaut true. Enfin, enext est évaluée à la fin de chaque itération. Autrement dit une boucle for est presque la même chose que la boucle while suivante.
  {
     einit ;
     while (econd) {
      instruction
      enext ;
     }
  }
Enfin, notez que econd peut être omise, en ce cas la condition est considérée vraie. Cette particularité est principalement utilisée dans l’idiome de la boucle infinie.
  for ( ; ; ) { // Boucle infinie, on sort par break ou return
    …
  }

Gestion du contrôle
Certaines instructions permettent de « sauter » quelque-part de l’intérieur des boucles. Ce sont des formes polies de goto. Il s’agit de break, qui fait sortir de la boucle, et de continue qui commence l’itération suivante en sautant par dessus ce qui reste de l’itération en cours.

Ainsi pour rechercher si l’entier foo est présent dans le tableau t, on peut écrire

   boolean trouve = false ;
   for (int i = 0 ; i < t.length ; i++) {
     if (t[i] == foo) {
       trouve = true ;
       break ;
     }
   }
   // trouve == true <=> foo est dans t

Ou encore, si on veut cette fois compter les occurrences de foo dans t, on peut écrire

  int count = 0 ;
   for (int i = 0 ; i < t.length ; i++) {
     if (t[i] != foo) continue ;
     count++ ;
   }

Noter que dans les deux cas on fait des choses un peu compliquées. Dans le premier cas, on pourrait faire une méthode et utiliser return, ou une boucle while sur trouve. Dans le second cas, on pourrait écrire le test d’égalité. Dans certaines situations, ces instructions sont pratiques (break plus fréquemment que continue).


Previous Up Next