IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 3 - Contrôle du flux du programme

pages : 1 2 3 4 5 6 

Cet opérateur est inhabituel parce qu'il a trois opérandes. C'est un véritable opérateur dans la mesure où il produit une valeur, à l'inverse de l'instruction habituelle if-else que nous étudierons dans la prochaine section de ce chapitre. L'expression est de la forme :

expression-booléenne ? valeur0 : valeur1

Si le résultat de expression-booléenne est true, l'expression valeur0 est évaluée et son résultat devient le résultat de l'opérateur. Si expression-booléenne est false, c'est l'expression valeur1 qui est évaluée et son résultat devient le résultat de l'opérateur.

Bien entendu, il est possible d'utiliser à la place une instruction if-else (qui sera décrite plus loin), mais l'opérateur ternaire est plus concis. Bien que C (d'où est issu cet opérateur) s'enorgueillit d'être lui-même un langage concis, et que l'opérateur ternaire ait été introduit, entre autres choses, pour des raisons d'efficacité, il faut se garder de l'utiliser à tout bout de champ car on aboutit très facilement à un code illisible.

Cet opérateur conditionnel peut être utilisé, soit pour ses effets de bord, soit pour la valeur produite, mais en général on recherche la valeur puisque c'est elle qui rend cet opérateur distinct du if-else. En voici un exemple :

static int ternary(int i) {
  return i < 10 ? i * 100 : i * 10;
}

Ce code est plus compact que celui qu'on aurait écrit sans l'opérateur ternaire :

static int alternative(int i) {
  if (i < 10)
    return i * 100;
  else
    return i * 10;
}

La deuxième forme est plus compréhensible, et ne nécessite pas de commentaire. Il est donc nécessaire de bien peser tous les arguments avant d'opter pour l'opérateur ternaire.

L'opérateur virgule

La virgule est utilisée en C et C++ non seulement comme séparateur dans la liste des argument des fonctions, mais aussi en tant qu'opérateur pour une évaluation séquentielle. L'opérateur virgule est utilisé en Java uniquement dans les boucles for, qui seront étudiées plus loin dans ce chapitre.

L'opérateur + pour les String

Un des opérateurs a une utilisation spéciale en Java : l'opérateur + peut être utilisé pour concaténer des chaînes de caractères, comme on l'a déjà vu. Il semble que ce soit une utilisation naturelle de l'opérateur +, même si cela ne correspond pas à son utilisation traditionnelle. Étendre cette possibilité semblait une bonne idée en C++, aussi la surcharge d'opérateurs fut ajoutée au C++ afin de permettre au programmeur d'ajouter des significations différentes à presque tous les opérateurs. En fait, la surcharge d'opérateurs, combinée à d'autres restrictions du C++, s'est trouvée être très compliquée à mettre en oeuvre par les programmeurs pour la conception de leurs classes. En Java, la surcharge d'opérateurs aurait été plus simple à implémenter qu'elle ne l'a été en C++ ; mais cette fonctionnalité a été jugée trop complexe, et les programmeurs Java, à la différence des programmeurs C++, ne peuvent implémenter leurs propres surcharges d'opérateurs.

L'utilisation de l'opérateur + pour les String présente quelques caractéristiques intéressantes. Si une expression commence par une String, alors tous les opérandes qui suivent doivent être des String (souvenez-vous que le compilateur remplace une séquence de caractères entre guillemets par une String) :

int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);

Ici, le compilateur Java convertit x, y, et z dans leurs représentations String au lieu d'ajouter d'abord leurs valeurs. Et si on écrit :

System.out.println(x + sString);

Java remplacera x par une String.

Les pièges classiques dans l'utilisation des opérateurs

L'un des pièges dûs aux opérateurs est de vouloir se passer des parenthèses alors qu'on n'est pas tout à fait certain de la manière dont sera évaluée l'opération. Ceci reste vrai en Java.

Une erreur très classique en C et C++ ressemble à celle-ci :

while(x = y) {
    // ....
}

Le programmeur voulait tester l'équivalence (==) et non effectuer une affectation. En C et C++ le résultat de cette affectation est toujours true si y est différent de zéro, et on a toutes les chances de partir dans une boucle infinie. En Java, le résultat de cette expression n'est pas un boolean ; le compilateur attendant un boolean, et ne transtypant pas l'int, générera une erreur de compilation avant même l'exécution du programme. Par suite cette erreur n'apparaîtra jamais en Java. Il n'y aura aucune erreur de compilation que dans le seul cas où x et y sont des boolean, pour lequel x = y est une expression légale ; mais il s'agirait probablement d'une erreur dans l'exemple ci-dessus.

Un problème similaire en C et C++ consiste à utiliser les AND et OR bit à bit au lieu de leurs versions logiques. Les AND et OR bit à bit utilisent un des caractères (& ou |) alors que les AND et OR logique en utilisent deux (&& et ||). Tout comme avec = et ==, il est facile de ne frapper qu'un caractère au lieu de deux. En Java, le compilateur interdit cela et ne vous laissera pas utiliser cavalièrement un type là où il n'a pas lieu d'être.

Les opérateurs de transtypage

Le mot transtypage est utilisé dans le sens de « couler dans un moule ». Java transforme automatiquement un type de données dans un autre lorsqu'il le faut. Par exemple, si on affecte une valeur entière à une variable en virgule flottante, le compilateur convertira automatiquement l'int en float. Le transtypage permet d'effectuer cette conversion explicitement, ou bien de la forcer lorsqu'elle ne serait pas effectuée implicitement.

Pour effectuer un transtypage, il suffit de mettre le type de données voulu (ainsi que tous ses modificateurs) entre parenthèses à gauche de n'importe quelle valeur. Voici un exemple :

void casts() {
  int i = 200;
  long l = (long)i;
  long l2 = (long)200;
}

Comme on peut le voir, il est possible de transtyper une valeur numérique aussi bien qu'une variable. Toutefois, dans les deux exemples présentés ici, le transtypage est superflu puisque le compilateur promouvra une valeur int en long si nécessaire. Il est tout de même possible d'effectuer un tel transtypage, soit pour le souligner, soit pour rendre le code plus clair. Dans d'autres situations, un transtypage pourrait être utilisé afin d'obtenir un code compilable.

En C et C++, le transtypage est parfois la source de quelques migraines. En Java, le transtypage est sûr, avec l'exception suivante : lorsqu'on fait ce qu'on appelle une conversion rétrécissante (c'est à dire lorsqu'on transtype depuis un type de données vers un autre, le premier pouvant contenir plus d'information que le second) on court le risque de perdre de l'information. Dans ce cas le compilateur demande un transtypage explicite, en émettant un message à peu près formulé ainsi « ceci peut être dangereux - néanmoins, si c'est ce que vous voulez vraiment faire, je vous en laisse la responsabilité ». Avec une conversion élargissante, le transtypage explicite n'est pas obligatoire car il n'y a pas de risque de perte d'information, le nouveau type pouvant contenir plus d'information que l'ancien.

Java permet de transtyper n'importe quel type primitif vers n'importe quel autre type primitif, excepté le type boolean, pour lequel il n'existe aucun transtypage. Les types Class ne peuvent être transtypés. Pour convertir une classe en une autre il faut utiliser des méthodes spéciales. (String est un cas à part, et on verra plus loin dans ce livre que les objets peuvent être transtypés à l'intérieur d'une famille de types ; un Chêne peut être transtypé en Arbre et vice versa, mais non dans un type étranger tel que Roche).

Les littéraux

Habituellement le compilateur sait exactement quel type affecter aux valeurs littérales insérées dans un programme. Quelquefois, cependant, le type est ambigu. Dans de tels cas il faut guider le compilateur en ajoutant une information supplémentaire sous la forme de caractères associés à la valeur littérale. Le code suivant montre l'utilisation de ces caractères :

//: c03:Literals.java

class Literals {
  char c = 0xffff; // plus grande valeur char en hexadécimal
  byte b = 0x7f; // plus grande valeur byte en hexadécimal
  short s = 0x7fff; // plus grande valeur short en hexadécimal
  int i1 = 0x2f; // Hexadécimal (minuscules)
  int i2 = 0X2F; // Hexadécimal (majuscules)
  int i3 = 0177; // Octal (avec zéro en tête)
  // Hexadécimal et Octal avec des long.
  long n1 = 200L; // suffixe long
  long n2 = 200l; // suffixe long
  long n3 = 200;
  //! long l6(200); // non autorisé
  float f1 = 1;
  float f2 = 1F; // suffixe float
  float f3 = 1f; // suffixe float
  float f4 = 1e-45f; // 10 puissance
  float f5 = 1e+9f; // suffixe float
  double d1 = 1d; // suffixe double
  double d2 = 1D; // suffixe double
  double d3 = 47e47d; // 10 puissance
} ///:~

L'hexadécimal (base 16), utilisable avec tous les types entier, est représenté par 0x ou 0X suivi de caractères 0-9 et/ou a-f en majuscules ou en minuscules. Si on tente d'initialiser une variable avec une valeur plus grande que celle qu'elle peut contenir (indépendamment de la forme numérique de la valeur), le compilateur émettra un message d'erreur. Le code ci-dessus montre entre autres la valeur hexadécimale maximale possible pour les types char, byte, et short. Si on dépasse leur valeur maximale, le compilateur crée automatiquement une valeur int et émet un message nous demandant d'utiliser un transtypage rétrécissant afin de réaliser l'affectation : Java nous avertit lorsqu'on franchit la ligne.

L'octal (base 8) est représenté par un nombre dont le premier digit est 0 (zéro) et les autres 0-7. Il n'existe pas de représentation littérale des nombres binaires en C, C++ ou Java.

Un caractère suivant immédiatement une valeur littérale établit son type : L, majuscule ou minuscule, signifie long ; F, majuscule ou minuscule, signifie float, et D, majuscule ou minuscule, double.

Les exposants utilisent une notation qui m'a toujours passablement consterné : 1.39 e-47f. En science et en ingénierie, « e » représente la base des logarithmes naturels, approximativement 2.718 (une valeur double plus précise existe en Java, c'est Math.E). e est utilisé dans les expressions d'exponentiation comme 1.39 x e-47, qui signifie 1.39 x 2.718-47. Toutefois, lorsque FORTRAN vit le jour il fut décidé que e signifierait naturellement « dix puissance », décision bizarre puisque FORTRAN a été conçu pour résoudre des problèmes scientifiques et d'ingénierie, et on aurait pu penser que ses concepteurs auraient fait preuve de plus de bons sens avant d'introduire une telle ambiguïté.[25] Quoi qu'il en soit, cette habitude a continué avec C, C++ et maintenant Java. Ceux d'entre vous qui ont utilisé e en tant que base des logarithmes naturels doivent effectuer une translation mentale en rencontrant une expression telle que 1.39 e-47f en Java ; elle signifie 1.39 x 10-47.

Noter que le caractère de fin n'est pas obligatoire lorsque le compilateur est capable de trouver le type approprié. Avec :

long n3 = 200;

il n'y a pas d'ambiguïté, un L suivant le 200 serait superflu. Toutefois, avec :

float f4 = 1e-47f; // 10 puissance

le compilateur traite normalement les nombres en notation scientifique en tant que double, et en l'absence du f final générerait un message d'erreur disant qu'on doit effectuer un transtypage explicite afin de convertir un double en float.

La promotion

Lorsqu'on effectue une opération mathématique ou bit à bit sur des types de données primitifs plus petits qu'un int (c'est à dire char, byte, ou short), on découvre que ces valeurs sont promues en int avant que les opérations ne soient effectuées, et que le résultat est du type int. Par suite, si on affecte ce résultat à une variable d'un type plus petit, il faut effectuer un transtypage explicite qui peut d'ailleurs entraîner une perte d'information. En général, dans une expression, la donnée du type le plus grand est celle qui détermine le type du résultat de cette expression ; en multipliant un float et un double, le résultat sera un double  ; en ajoutant un int et un long, le résultat sera un long.

Java n'a pas de « sizeof »

En C and C++, l'opérateur sizeof( ) satisfait un besoin spécifique : il renseigne sur le nombre d'octets alloués pour les données individuelles. En C et C++, la principale raison d'être de l'opérateur sizeof( ) est la portabilité. Plusieurs types de données peuvent avoir des tailles différentes sur des machines différentes, et le programmeur doit connaître la taille allouée pour ces types lorsqu'il effectue des opérations sensibles à la taille des données. Par exemple, un ordinateur peut traiter les entiers sur 32 bits, alors qu'un autre les traitera sur 16 bits : les programmes peuvent ranger de plus grandes valeurs sur la première machine. Comme on peut l'imaginer, la portabilité est un énorme casse-tête pour les programmeurs C et C++.

Java n'a pas besoin d'un opérateur sizeof( ) car tous les types de données ont la même taille sur toutes les machines. Il n'est absolument pas besoin de parler de portabilité à ce niveau - celle-ci est déjà intégrée au langage.

Retour sur la priorité des opérateurs

Lors d'un séminaire, entendant mes jérémiades au sujet de la difficulté de se souvenir de la priorité des opérateurs, un étudiant suggéra un procédé mnémotechnique qui est également un commentaire : « Ulcer Addicts Really Like C A lot ». (« Les Accros de l'Ulcère Adorent Réellement C »)

Mnémonique Type d'opérateur Opérateurs
Ulcer Unaire + - ++--
Addicts Arithmétique (et décalage) * / % + - << >>
Really Relationnel > < >= <= == !=
Like Logique (et bit à bit) && || & | ^
C Conditionnel (ternaire) A > B ? X : Y
A Lot Affectation = (et affectation composée comme*=)

Bien entendu, ce n'est pas un moyen mnémotechnique parfait puisque les opérateurs de décalage et les opérateurs bit à bit sont quelque peu éparpillés dans le tableau, mais il fonctionne pour les autres opérateurs.

Résumé sur les opérateurs

L'exemple suivant montre les types de données primitifs qu'on peut associer avec certains opérateurs. Il s'agit du même exemple de base répété plusieurs fois, en utilisant des types de données primitifs différents. Le fichier ne doit pas générer d'erreur de compilation dans la mesure où les lignes pouvant générer de telles erreurs ont été mises en commentaires avec //! :

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Jean-Pierre Vidal ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java