Conversion de données
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. Il faut néanmoins faire attention lors de 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 représenter que des valeurs comprises entre -127 et 128. Si on fait la conversion, on va donc perdre de l'information.
Type de conversion
Il y a deux types de conversion. 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, reprises sur la figure 18. 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, la grandeur du nombre restant la même.
De | Vers |
---|---|
byte |
short , int , long , float , double |
short |
int , long , float , double |
char |
int , long , float , double |
int |
long , float , double |
long |
float , double |
float |
double |
La figure 19 résume 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.
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 perte de précision 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 que cela peut se produire.
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é.
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.
Conversion avec perte d'information
Il y a 22 conversions avec perte d'information. Il faut bien entendu essayer de les éviter puisqu'elles peuvent résulter en une perte d'information et de précision. Ces pertes ne se produisent pas dans tous les cas. Elles sont toutes reprises sur la figure 20. Ces pertes d'informations 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$.
De | Vers |
---|---|
short |
byte , char |
char |
byte , short |
int |
byte , short , char |
long |
byte , short , char , int |
float |
byte , short , char , int , long |
double |
byte , short , char , int , long , float |
On remarque 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, vous aurez sans doute remarqué que le type boolean
n'apparait dans aucun des tableaux. En effet, on ne peut convertir une donnée boolean
vers aucun autre type primitif et cette interdiction est également valable dans l'autre sens.
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 quand celles-ci se produisent 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. Prenons par exemple les instructions suivantes :
L'exécution de ces dernières affiche 25.0
à l'écran. 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, c'est-à-dire une conversion par affectation du type float
vers le type int
. Dans ce cas, on aurait eu une erreur de compilation puisque cette conversion est avec perte d'information. Prenons le programme de la figure 21, une erreur se produit lors de la compilation. L'erreur 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 ConversionError.java ConversionError.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
.
Soient les instructions suivantes :
L'exécution du programme affiche 5.0
à l'écran puisqu'un des deux opérandes de l'opérateur /
est de type float
, ce qui implique que le résultat sera également un float
. La valeur de la variable denom
est convertie en un float
avant la division.
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 les règles suivantes 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 : 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.
Voyons un exemple où une telle conversion se produit :
La première instruction 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 suivantes ne compileront pas :
Le compilateur peut autoriser des conversions avec perte d'information 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 de l'exemple est une expression constante. 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 le résultat de la somme (25050) 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
. Ainsi, le programme de la 22 ne compile pas et produit l'erreur suivante :
> javac ConversionConstant.java ConversionConstant.java:8: possible loss of precision found : int required: short short sum = B + S; ^ 1 error
Cast ou conversion explicite
Les conversions qu'on vient de voir se font de manière implicite, automatiquement. Mais on ne peut faire que des conversions sans perte d'information. Il est néanmoins parfois utile de réaliser des conversions avec perte d'information ou de convertir un byte
en un char
. Pour cela, on doit faire un cast, aussi appelé conversion explicite.
On fait un cast grâce à l'opérateur de cast. Il s'agit d'un opérateur unaire, de priorité 3, associatif de droite à gauche. Grâce à cet opérateur, on va pouvoir convertir une donnée dans un type spécifié, 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 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 :
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, 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 converti en char
. Cette dernière conversion étant classée parmi les conversions avec perte d'information, il faut utiliser l'opérateur de cast.