Conversion de donnée
Java est très strict du point de vue des données et il est parfois intéressant de convertir une donnée d'un type primitif vers un autre type primitif. Mais il faut faire attention lors de ces conversions car il y a risque de perdre de l'information. En effet, imaginons vouloir convertir un int qui vaut 1254 en un byte. Ça ne sera pas possible puisque qu'avec le type byte, on ne peut coder que des valeurs comprises entre -127 et 128. Donc, si on fait la conversion, on va perdre de l'information.
Types de conversion de donnée primitive
Il y a deux catégories de conversions. Les conversions sans perte d'information et celles qui impliquent une perte d'information. Il n'y a pas de perte d'information lorsque la grandeur du nombre converti est conservée.
Conversion sans perte d'information
Il y a 19 conversions sans perte d'informations qui sont reprises dans le tableau ci-dessous. Ces conversions ne perdent jamais d'information du point de vue de la quantité, mais les conversions vers un type primitif décimal peuvent mener à une perte de précision. Par exemple, lorsqu'on convertit un int ou un long vers un double ou un float, les chiffres les moins significatifs peuvent être perdus alors que la grandeur du nombre est la même.
| De | Vers |
|---|---|
byte |
short, int, long, float ou double |
short |
int, long, float ou double |
char |
int, long, float ou double |
int |
long, float ou double |
long |
float ou double |
float |
double |
La figure suivante montre les conversions sans perte d'information. Pour qu'une conversion sans perte d'information soit possible entre deux types, il faut juste que vous puissiez les rejoindre en suivant les flèches. Les flèches pleines représentent les conversions sans perte de précision et les flèches pointillées celles où une perte de précision peut avoir lieu.

Donc, pour résumer, les conversions sans perte d'informations ne perdent pas d'information à propos de la grandeur du nombre. Les conversions d'un nombre entier vers un nombre entier ne perdent aucune information, ni précision; la conversion est parfaite. Enfin, les conversions de int vers float et de long vers float et double peuvent provoquer une perte de précision. Si une telle perte se produit, le programme continue à s'exécuter sans problème, aucune erreur d'exécution ne se produit; il faut donc être bien conscient de cela car des problèmes peuvent parfois arriver.
On remarque également que l'on peut passer d'un char vers un int, long, float ou double. En effet, rappelez-vous, les caractères correspondent en fait à des nombres entiers positifs qui dépendent de l'encodage utilisé. On peut donc passer d'un caractère vers un entier ou un flottant.
Mot réservé strictfp
Pour que la conversion du type float vers le type double se fasse sans perte de précision, il faut utiliser le mot réservé strictfp. L'exemple suivant montre une telle conversion sans perte de précision. Vous pouvez également y voir une conversion de int vers float avec perte de précision.
|
|
strictfp.Conversion avec perte d'information
Il y a 22 conversions avec perte d'information. Il faut bien entendu essayer d'éviter ces conversions. Elles sont toutes reprises dans le tableau suivant.
| De | Vers |
|---|---|
short |
byte ou char |
char |
byte ou short |
int |
byte, short ou char |
long |
byte, short, char ou int |
float |
byte, short, char, int ou long |
double |
byte, short, char, int, long ou float |
Les pertes de précision ne se produisent pas dans tous les cas. Elles sont dues au fait qu'on passe d'un type de donnée qui utilise n bits vers un autre qui en utilise m, avec m étant strictement plus petit que n.
On remarque néanmoins que la conversion du type short vers le type char provoque une perte d'information alors qu'ils utilisent tous les deux 16 bits en mémoire. Cette perte n'est pas due à la place en mémoire, mais bien au caractère signé des short. En effet, les char s'étendent de 0 à 65535, tandis que les short s'étendent de -32768 à 32767 et la conversion d'un nombre négatif vers un caractère n'a pas de sens. Un raisonnement similaire explique que la conversion de char vers short peut également résulter en une perte d'information.
Conversion du type byte vers le type char
Il reste une conversion qu'on n'a pas encore traitée : il s'agit de la conversion du type byte vers le type char. On passe d'un type qui prend 8 bits en mémoire à un type qui en prend 16. On ne va pas entrer dans les détails de cette conversion, mais il faut savoir qu'elle est possible sans perte d'information.
Mais elle n'est pas pour autant classée parmi les conversions sans perte d'information car ce qui se passe en réalité, c'est que le byte est d'abord converti en un int sans perte d'information et que le int obtenu est ensuite converti en char avec perte d'information. Ce type de conversion est le seul qui est avec et sans perte d'information.
C'est un peu spécial, mais c'est ainsi, on n'y peut rien ! Il faut néanmoins en être conscient et bien se rappeler que si on part d'une donnée en byte, qu'on la convertit en char, puis qu'on reconvertit le résultat obtenu en byte, on retombe toujours sur la valeur initiale.
Type boolean
Enfin, on remarquera que le type boolean n'apparait dans aucun des tableaux. En effet, on ne peut convertir un boolean en rien et rien ne peut être convertit en un boolean.
Bien que dans d'autres langages de programmation, il y a une correspondance entre la valeur true et l'entier 1 et entre la valeur false et l'entier 0, ce n'est pas du tout le cas en Java.
Conversion implicite
Maintenant qu'on a vu les deux types de conversions qui existent, voyons comment ces conversions apparaissent dans un programme Java. Il y a tout d'abord les conversions implicites qui se produisent automatiquement de manière implicite.
Conversion par affectation
Une conversion par affectation intervient lorsqu'une valeur d'un certain type est affectée à une variable d'un autre type. Lorsqu'on écrit x = y, la valeur de y va être convertie dans le type de la variable x. La conversion se fait donc à partir du type d'une expression vers le type d'une variable. Seules les conversions sans perte d'information sont possibles ; les autres, ainsi que la conversion de byte vers char, vont produire une erreur lors de la compilation.
|
|
L'exécution du programme ci-dessus affichera à l'écran 25.0. Pourquoi le .0 ? Car la valeur 25 de type int à été convertie en un float lors de l'affectation money = dollars. Cette conversion étant bien sans perte d'information, elle a pu se produire implicitement.
Maintenant, si on avait le contraire, on aurait eu une erreur de compilation étant donné que passer d'un float vers un int est une conversion avec perte d'information.
|
|
L'erreur de compilation produite est de type « possible loss of precision ». Le compilateur indique également le type de donnée présent et celui auquel il s'attend :
> javac Conversion2.java Conversion2.java:7: possible loss of precision found : float required : int dollars = money ; // Conversion de float vers int ^ 1 error
Remarquez que si on avait utilisé le littéral 25.0 au lieu de 25.0F, on aurait eu une autre erreur de compilation. La différence entre les deux littéraux est que le premier est de type double, tandis que le second est de type float. Et étant donné que la conversion de double vers float (le type de la variable money) est avec perte d'information, le compilateur aurait généré une erreur.
Promotion arithmétique
La promotion arithmétique intervient automatiquement lorsque certains opérateurs mathématiques doivent modifier leurs opérandes pour pouvoir exécuter l'opération. Par exemple, lorsque vous tentez de diviser un float par un int, la valeur du dénominateur sera promue en un type float avant la division afin que le résultat de la division soit un float. Les opérandes d'un opérateur sont donc convertis vers le type requis par l'opérateur. Pour rappel, les opérateurs arithmétiques existent en quatre versions : double, float, long et int.
|
|
Le programme ci-dessus va afficher 5.0 à l'écran puisqu'un des deux opérandes de / est de type float et le résultat est donc également un float. Petite question, aurait-on pu écrire plutôt float num = 10; ? Pourquoi ou pourquoi pas ?
Quels sont les opérateurs qui vont convertir automatiquement leurs opérandes ? Il y a tout d'abord les opérateurs unaires -, +, –- et ++. Si l'opérande est de type byte, short ou char, il est automatiquement converti en un int. Dans tous les autres cas, il n'est pas converti et conserve son type.
Enfin, il reste les opérateurs +, -, *, /, %, <, <=, >, >=, == et !=. Pour ceux-ci, il faut regarder le type des deux opérandes, et appliquer ces règles dans l'ordre
- Si l'un des opérandes est de type
double, l'autre est converti endouble; - Si l'un des opérandes est de type
float, l'autre est converti enfloat; - Si l'un des opérandes est de type
long, l'autre est converti enlong; - Dans tous les autres cas, les deux opérandes sont convertis en
int.
Voyons maintenant un exemple dans lequel deux conversions implicites se produisent :
|
|
Dans cet exemple, on utilise l'opérateur + sur des opérandes de type float et int. L'opérande de droite est donc converti depuis le type int vers le type float. L'addition est ensuite effectuée en float et son résultat, qui est un float, est affecté à une variable de type double. Il y a donc deux conversion implicites lors de l'exécution de la troisième instruction de ce programme : une promotion arithmétique suivie d'une conversion par affectation.
Conversion par le compilateur
Il reste un dernier type de conversion implicite, ce sont les conversions par le compilateur. Il s'agit de conversions avec perte d'information, mais qui sont autorisées et effectuées de manière implicite. Rappelez-vous que les conversions avec perte d'information ne résultent pas toujours en une perte de précision; et donc, si le compilateur est sûr qu'une telle perte n'aura pas lieu, il autorise la conversion à se faire.
|
|
La première instruction du programme déclare une nouvelle constante B de type byte et l'initialise avec le littéral 50. Pour rappel, ce littéral est de type int et comme la conversion de int à byte est avec perte d'information, il ne s'agit pas d'une conversion par affectation.
Néanmoins, rappelez-vous également que les byte sont stockés sur 8 bits et sont donc compris entre -128 et 127. Le compilateur se rend compte que la valeur 50 est comprise entre les deux limites et que la conversion ne résultera donc pas en une perte de précision. Celle-ci est donc autorisée par le compilateur.
Ce genre de conversion ne se produit que pour des affectations de littéraux entiers de type int dans des variables de type byte ou short. Par exemple, les instructions du listing suivant ne compileront pas.
|
|
Le compilateur est en fait capable de faire ces conversions lorsque l'expression affectée à la variable est une expression constante; c'est-à-dire soit un littéral, une constante ou une expression dont tous les opérandes sont des littéraux ou des constantes.
La troisième instruction du programme d'exemple est une expression constante. En effet, les deux opérandes de l'expression B + S sont tous les deux des constantes. L'instruction short sum = B + S; compile donc sans erreur puisque le compilateur est capable de calculer que 25050 (qui correspond à la somme de 50 et 25000) se trouve bien dans les limites du type short qui sont -32768 et 32767.
Si les deux variables B et S n'étaient pas des constantes, une erreur aurait été générée lors de la compilation puisque le compilateur n'est pas capable de calculer la valeur de l'expression B + S :
> javac Conversion5.java Conversion5.java:8: possible loss of precision found : int required : short short sum = B + S; ^ 1 error
Cast ou conversion explicite
Les conversions que l'on vient de voir se font de manière implicite, automatique. Mais on ne peut faire que des conversions sans perte d'information. Il est parfois utile de faire une conversion avec perte de donnée ou de convertir un byte en char. Pour cela, on doit faire une cast qui est une conversion explicite.
On fait un cast grâce à l'opérateur de cast. Il s'agit d'un opérateur unaire, de priorité 3, qui est associatif de droite à gauche. Grâce à cet opérateur, on va pouvoir convertir une donnée en un autre type, que la conversion soit avec ou sans perte d'information. L'opérateur est constitué de deux parenthèses entre lesquelles se trouve un type de donnée, précisément celui vers lequel la conversion doit avoir lieu. Ainsi, pour convertir une donnée en int, la forme prise par l'opérateur est (int).
L'exemple du listing suivant montre plusieurs conversions utilisant l'opérateur de cast. Il y a une conversion de int vers float (sans perte d'information), de int vers byte (avec perte d'information) et enfin de byte vers char.
|
|
Voici ce qui est affiché à l'écran après exécution de ce programme :
> java Conversion6 292.0 36 $ 36
On voit effectivement que la première conversion n'a pas fait perdre d'information et l'entier 292 est devenu le flottant 292.0. L'opérateur de cast n'est pas nécessaire puisque cette conversion aurait pu avoir lieu de manière implicite (conversion par affectation). On peut néanmoins l'utiliser pour mettre en évidence le changement de type, le rendre explicite.
La deuxième conversion provoque une perte d'information. En effet, comme vous pouvez l'observer sur le résultat produit par le programme, la valeur n'est plus la même puisqu'on passe de 292 à 36. Pour comprendre pourquoi on obtient la valeur 36, il faut comprendre comment les entiers sont représentés dans l'ordinateur; ce sujet est traité à la section suivante.
Enfin la troisième conversion va du type byte vers le type char. Aucune information n'est perdue, mais cette conversion ne peut se faire que de manière explicite. En effet, pour rappel, le byte est d'abord converti en un int qui est ensuite convertir en char. Cette dernière conversion étant classée parmi les conversions avec perte d'information, il faut utiliser un cast.











Chapitre 2









