Première partie
Conversion de type
Objectifs d'apprentissage
- Décrire en vos propres mots ce que l'on entend par conversions de type de données.
- Différencier entre les conversions de type implicites et explicites.
- Reconnaître les situations où le forçage du type peut se faire sans conséquence sur l'exactitude des calculs.
1. Conversion de type
Comme vu au laboratoire précédent, Java est un langage fortement typé. Il nous sera parfois utile de convertir une donnée d'un type primitif à un autre. Pour ce faire, il faudra toutefois être vigilant afin de ne pas perdre de l'information. Il existe 2 types de conversion: celles implicites et celles explicites.
Conversion implicite/sans perte d'information
Les conversions implicites sont celles où il n’y a pas de perte de données (aucune perte d'information possible). Elles se produisent automatiquement lors de l'exécution du programme.
Par exemple, l’affectation d’une valeur peut se faire par une conversion implicite:
float money;
int cash = 25;
money = cash; // Converts from int to float
System.out.println(money);
Cette conversion est implicite, car la conversion d’un entier int vers un nombre à décimal float se produit sans perte d’information. On remarque aussi que le programme imprime « 25.0 ». Le « .0 » provient du fait que le 25 de type int est converti en float.
C'est aussi ce qui se produit lorsqu'une valeur de type int est affectée dans une variable de type long. Le type long représente aussi une valeur entière, mais l'intervalle de valeurs qu'il représente est plus grand que celui d'un int ; et il contient toutes les valeurs représentées par ce dernier. La conversion automatique dans l'autre sens est impossible puisqu'il y plusieurs valeurs représentées par un long qui ne peuvent être représentées avec la quantité de mémoire réservée pour un int. Il existe autant de situations qu'il y a de combinaisons de types primitifs. Vous pouvez les retrouver dans le tableau plus bas.
Il existe un second cas dans lequel la conversion implicite s’effectue. Il s’agit de la promotion arithmétique, c’est-à-dire lorsqu’un opérateur doit modifier le type de ses opérandes afin d’exécuter l’opération.
Par exemple:
double num1 = 2.0;
int num2 = 6;
System.out.println(num2/num1);
Dans cet exemple, le programme imprime 3.0 à l’écran. La variable « num2 » est convertie en float avant la division.
À titre de référence, il existe une série de règles qu’il vous est possible d’appliquer afin de savoir quels seront les types des opérandes lors d'opérations effectuées avec +, -, *, /, %, <, <=, >, >=, == et != .
À effectuer dans l'ordre:
- Si un des opérandes est de type double, l'autre est converti en double ;
- Si un des opérandes est de type float, l'autre est converti en float ;
- Si un des opérandes est de type long, l'autre est converti en long ;
- Dans tous les autres cas, les deux opérandes sont convertis en int.
Veuillez noter qu’il n’existe aucune conversion pour le type boolean.
Le tableau ci-dessous résume l'ensemble des conversions sans perte d'information pour les différents types primitifs :
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 |
Conversion explicite/forçage de type (« type cast » en anglais)
Les conversions explicites sont celles où il peut y avoir perte de donnée. Pour ce faire, nous utilisons « l’opérateur de forçage de type ». Il s’agit en fait de deux parenthèses entre lesquelles se trouve le type de donnée dans lequel nous voulons convertir notre valeur.
Par exemple :
double d = 16.987;
float f = (float) d; // Converts from double to float without any loss of data
int i = (int) f; // Converts from float to int with loss of data
long l = (long) i; // Converts from int to long without any loss of data
System.out.println(d);
System.out.println(f);
System.out.println(i);
System.out.println(l);
On obtient alors à la console :
16.987
16.987
16
16
Dans la sortie du programme, on remarque une perte de données, soit celles des valeurs des décimales « .987 », lors de la conversion de float à int. Il faut donc être vigilant lors de l'utilisation de ce type de conversion et s'assurer que les pertes d'information ne nuisent pas au bon fonctionnement de votre programme. Vous expérimenterez davantage ce type de conversion dans la prochaine partie du laboratoire.
2. Les messages d'erreurs
Programmer efficacement. Au cours du semestre, plusieurs idées vous seront présentées afin de développer cette habileté. L'une des principales habiletés est le débogage. Au début, les étudiants perdent beaucoup de temps avec cette activité, parfois, ils cherchent dans la mauvaise section du code. Nous reviendrons sur ce sujet au cours des prochains laboratoires. Pour l'instant, nous allons nous concentrer sur les messages d'erreurs. Il est important de bien comprendre les messages d'erreurs, et de se familiariser avec chacun d'eux. Pour faciliter la compréhension, je vous suggère de créer des petits programmes tests illustrant un seul cas d'erreur.
Exercice! Créez une nouvelle classe nommée Test ayant une méthode principale. Dans le corps de la méthode principale, vous déclarerez une variable de type int, à laquelle, vous affecterez la valeur Long.MAX_VALUE. C'est la plus grande valeur pour le type Long. Essayez par vous-même.
Quel est le message d'erreur affiché par le compilateur ?
Parfois, la logique de nos programmes est telle qu'on souhaiterait tout de même faire cette conversion. Il faut alors être prudent. Utilisez toujours un test afin de vous assurer que la valeur de l'expression se situe dans un intervalle de valeurs appropriées. Lorsqu'on force une conversion (type explicite/casting), on dispense le compilateur de l'une de ses tâches essentielles, qui consiste à s'assurer que tous les types des sous-expressions sont compatibles. C'est comme si on disait au compilateur « je sais ce que je fais, s'il te plait, laisse-moi faire l'affectation de cette valeur à cette variable ».
long l;
...
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
int i = (int) l;
...
}
Puisque la valeur de la variable l est comprise dans l'intervalle des valeurs possibles pour un entier int, pourquoi faut-il forcer la conversion à l'aide de la syntaxe (int) ? Autrement dit, pourquoi ne pourrait-on tout simplement écrire ceci :
long l;
...
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
int i = l;
...
}
La réponse est simple: le compilateur n'effectue pas de conversion implicite s'il y a un risque qu'il y ait perte d'information! Vous obtiendrez ainsi l'erreur suivante.
Test.java:5: error: incompatible types: possible lossy conversion from long to int
int i = l;
^
1 error
Par ailleurs, on ne pourrait utiliser ce mécanisme (opérateur de cast) afin de transformer une chaîne de caractères (String) en un nombre ! Par contre, chaque type primitif possède une classe « wrapper ». Cette classe a un nom semblable au type auquel elle est associée. Par exemple, pour le type int, il y a la classe Integer (et de même, il y a les classes Double, Boolean, Character, etc). Comme le nom le suggère, chacune de ces classes enveloppantes sert à sauvegarder une valeur à l'intérieur d'un objet. Comme ceci :
public class Integer {
private int value;
public Integer(int v) {
value = v;
}
...
}
Nous verrons plus tard des situations où ces objets seront nécessaires (lors de la présentation des types abstraits de données). Cependant, un aspect important de ces classes est qu'elles regroupent aussi un ensemble de méthodes utiles pour ce type.
Jetez-y un oeil par vous-même:
- Allez à https://docs.oracle.com/javase/8/docs/api/overview-summary.html
Il s'agit de la documentation des classes et méthodes de Java 8.0. Au fil de la session, vous devrez vous référer à cette documentation afin de connaître et d'utiliser ces classes et méthodes.
- Visitez le package lang.
Ce dernier est toujours chargé par défaut dans vos programmes (vous n'avez donc pas besoin de faire l'usage de import). C'est notamment dans ce package que vous retrouvez les classes « wrapper ». Par ailleurs, la classe « System » que vous connaissez bien en raison de l'usage de println() s'y trouve aussi.
- Plus bas dans cette page, vous pouvez trouver la classe Integer.
- Trouvez maintenant la méthode parseInt(String s) et identifiez le type de retour de la méthode.
Il existe plusieurs erreurs de compilation. Ces erreurs peuvent être réduites si vous comprenez bien les types et que vous appliquez bien les règles de syntaxe. Le tableau ci-dessous résume les erreurs de compilation les plus courantes:
MESSAGE D'ERREUR | EXPLICATION |
---|---|
variable VARIABLE_NAME might not have been initialized |
Utilisation de la variable VARIABLE_NAME non initialisée. Attention, il peut aussi s'agir d'une faute de frappe! |
';' expected |
Un point-virgule est manquant à la fin d'une ligne de code. |
cannot assign a value to a final variable VARIABLE_NAME |
Affectation d'une valeur à une constante VARIABLE_NAME déjà initialisée. |
incompatible types |
Tentative d'affecter une valeur de type TYPE1 à une variable de type TYPE2 . |
cannot find symbol |
Le compilateur trouve un mot WORD qu'il ne comprend pas. Vérifiez qu'il ne s'agit pas d'une faute de frappe. |
illegal start of expression |
Le compilateur trouve un élément qui ne devrait pas se trouver à cet endroit. |
not a statement |
La ligne de code n'est pas valide. |
reached end of file while parsing |
Il y a probablement une erreur dans vos blocs de code. Vérifiez vos accolades { }. |
Bref, les messages d'erreur vous donnent beaucoup d'information (endroit de l'erreur, type d'erreur, etc.) Apprenez à bien les lire et les comprendre. Cela facilitera grandement le processus de débogage de votre code.
Exercices!
Qu'est-ce qui sera affiché à la console lors de la compilation et l'exécution des courts programmes ci-dessous?
Question 1.21 :
public class Test {
public static void main ( String[] args ) {
int num1 = 9 ;
double num2 = 12.0;
double sum = num1 + num2;
System.out.println(sum);
}
}
RéponseQuestion 1.22 :
public class Test {
public static void main(String[] args) {
double num1 = 18.4;
double num2 = 1.6;
int sum = num1 + num2;
System.out.println(sum);
}
}
RéponseQuestion 1.23 :
public class Test {
public static void main(String[] args) {
double num1 = 13.6;
double num2 = 2.5;
int sum = (int) num1 + (int) num2;
System.out.println(sum);
}
}
RéponseQuestion 1.24 :
public class Test {
public static void main(String[] args) {
double num1 = 13.6;
double num2 = 2.5;
int sum = (int) (num1 + num2);
System.out.println(sum);
}
}
RéponseQuestion 1.25 :
public class Test {
public static void main(String[] args) {
int num = 96;
char a = num;
System.out.println(a);
}
}
RéponseQuestion 1.26 :
public class Test {
public static void main(String[] args) {
System.out.println( (float) (int) 2017.89432 );
}
}
RéponseQuestion 1.27 :
Pour cette question nous allons revisiter la méthode principale main. Comme vous l’avez vu précédemment, la méthode main doit contenir l’argument String args[]. Cet argument permet de fournir des données au programme lors de son lancement. Pour ce faire, il suffit de fournir les donnes via la ligne de commande de la manière suivante :
> java MyProgram one two
Où args contient maintenant les chaînes de caractères « one » et « two ». On peut alors manipuler la variable args comme un tableau régulier.
Voici un exemple :
Maintenant, construisez la classe « Sum » qui fait la conversion de tous les éléments sur la ligne de commande, de String en int, et fait aussi la somme des éléments.
Indice: Utiliser les méthodes de la classe « Integer » fournies dans la documentation Java
> javac Sum.java
> java Sum 1 2 3 4 5
Votre programme doit avoir une sortie similaire à:
The sum is 15
Voici un cours vidéo qui explique le processus étape par étape :