0% ont trouvé ce document utile (0 vote)
13 vues46 pages

Java Structure de Controle

Ce document présente les structures de contrôle en Java, notamment la séquence, la boucle et la décision. Il explique comment utiliser des blocs d'énoncés et l'énoncé while pour gérer les répétitions dans les programmes. De plus, il aborde l'importance des tests et du débogage pour assurer la qualité du logiciel.

Transféré par

Abdesslam Nfissi
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
13 vues46 pages

Java Structure de Controle

Ce document présente les structures de contrôle en Java, notamment la séquence, la boucle et la décision. Il explique comment utiliser des blocs d'énoncés et l'énoncé while pour gérer les répétitions dans les programmes. De plus, il aborde l'importance des tests et du débogage pour assurer la qualité du logiciel.

Transféré par

Abdesslam Nfissi
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

3.

Structures de contrôle
Dans le corps d’une méthode, il y a trois manières fondamentales
d’enchaîner les actions : la séquence, la boucle et la décision. Ce chapitre introduit
les énoncés de base Java qui permettent d’exprimer ces trois types
d’enchaînement.

3.1 La séquence
Les exemples vus jusqu’à présent ont utilisé une séquence. Une séquence
d’énoncés est de la forme générale suivante :

énoncé1 énoncé2 … énoncén

Les énoncés sont placés en séquence les uns après les autres et sont exécutés
dans cet ordre. La Figure 1 montre une représentation graphique d’une
séquence avec un diagramme d’activité UML. Un rectangle aux coins arrondis
représente une activité. Dans notre cas, une activité correspond à un énoncé
Java. Les flèches indiquent l’ordre d’exécution des activités. Le point noir
représente le début et le point noir encerclé la fin de l’exécution.

énoncé 1

énoncé 2

...

énoncé n

Figure 12. Diagramme d’activité UML pour une séquence

Dans Exemple1 du chapitre 2, il n’y a pas seulement des énoncés qui


correspondent à des actions mais aussi des déclarations. On peut ainsi
mélanger les énoncés de déclaration et d’actions dans la séquence. Une
séquence d’énoncés (action ou déclaration) placés entre accolades est appelé
un bloc d’énoncés Java (ou simplement bloc). La syntaxe d’un bloc est illustrée à
la figure suivante :
62
bloc d'énoncés :
{ }
énoncé

Le corps d’une méthode est essentiellement un bloc Java. Il est à noter qu’il
est permis d’avoir un seul énoncé dans le bloc. Les différents types d’énoncés
seront étudiés en détails. Jusqu’à présent, nous avons rencontrés trois sortes
d’énoncés : énoncé de déclaration de variable, d’affectation et d’appel de
méthode. Un bloc Java est lui-même considéré comme un énoncé. On
obtient ainsi le diagramme syntaxique suivant :

énoncé :
déclaration de variable

affectation

appel de méthode

bloc d'énoncés

On peut donc imbriquer un bloc Java dans un autre bloc Java.

Exemple. JavaPasAPas/chapitre_3/[Link]

Dans l’exemple suivant, les deux énoncés de saisie de chaîne de Exemple1 ont
été regroupés en un bloc qui est imbriqué dans le bloc de la méthode main().
/**
* [Link]
* Modification de Exemple1 avec un bloc imbriqué
*/
import [Link]; // Importe la classe [Link]
public class ExempleBloc{

public static void main (String args[]) {

// Déclaration de variables
String chaine1, chaine2; // Les entiers sous forme de chaînes
int entier1, entier2, somme; // Les entiers à additionner

// Saisir les deux chaînes de caractères qui représentent des nombres entiers
{
chaine1 = [Link]("Entrez un premier nombre entier");
chaine2 = [Link]("Entrez un second nombre entier");
}

63
// Convertir les chaînes en entiers
entier1 = [Link](chaine1);
entier2 = [Link](chaine2);

// Calculer la somme des deux entiers


somme = entier1 + entier2;

// Afficher la somme avec [Link]


[Link](null,"La somme des deux entiers est " + somme);

// Appel de [Link](0) nécessaire à cause des appels à


// [Link] et [Link]
[Link](0);
}
}

Ce programme produit le même effet que Exemple1. Dans cet exemple,


l’utilisation du bloc imbriqué n’a aucune utilité. Nous verrons par la suite
l’importance de cette notion.

3.2 La boucle avec l’énoncé while


Imaginons que l’on veuille afficher les entiers de 1 à 5. On pourrait produire
ce résultat avec le programme suivant :

Exemple. JavaPasAPas/chapitre_3/[Link]

Affichage des entiers de 1 à 5.


/**
* [Link]
* Afficher les entiers de 1 à 5
*/
import [Link];
public class Afficher12345{

public static void main (String args[]) {

[Link](null,"Valeur du compteur: "+1);


[Link](null,"Valeur du compteur: "+2);
[Link](null,"Valeur du compteur: "+3);
[Link](null,"Valeur du compteur: "+4);
[Link](null,"Valeur du compteur: "+5);
[Link](0);
}
}

S’il fallait afficher les entiers de 1 à 1 000 000, le programme serait long à
écrire… Et même si vous aviez la patience de le faire, ce serait sans aucun
doute inefficace parce que le processeur devrait charger et interpréter un

64
grand nombre d’instructions redondantes. Par ailleurs, si le nombre de
répétition n’est pas connu au moment d’écrire le code, par exmple si c’est
l’utisateur ou la taille du fichier qui détermine le nombre de répétition, il peut
être tout simplement impossible de procéder avec une répétition explicite
dans le code. Pour éviter de répéter les énoncés dans le programme, on peut
employer une répétition (aussi appelée boucle ou itération). L’énoncé while Java
est un des énoncés Java qui permet d’effectuer une répétition.

Exemple. JavaPasAPas/chapitre_3/[Link]

ExempleWhile illustre la notion de répétition avec compteur en employant


un énoncé while. Ce programme a le même effet que le précédent.
/**
* [Link]
* Exemple d'utilisation d'une boucle while avec un compteur
*/
import [Link];
public class ExempleWhile{
public static void main (String args[]) {
int compteur = 1;
while(compteur <= 5){
[Link](null,"Valeur du compteur: "+compteur);
compteur = compteur + 1;
}
[Link](0);
}
}

Voici la syntaxe d’un énoncé while :

énoncé while :

while ( expression ) énoncé

L’expression entre parenthèses doit être une expression booléenne, aussi appelée
condition , dont la valeur est vraie (true) ou faux (false). Si cette condition est
respectée (i.e. la valeur retournée par l’expression est true), l’énoncé après le
while est répété en boucle jusqu’à ce que la condition ne soit plus respectée
(i.e. la valeur retournée par l’expression est false).

Il faut prendre soin, lorsque l’on conçoit une boucle, de s’assurer que celle-
ci puisse se terminer : une boucle peut être infinie et ne jamais se terminer. Il
s’agit généralement d’une erreur.
65
La figure suivante illustre l’enchaînement des énoncés du programme par un
diagramme d’activité UML. Un losange représente une condition. Les
flèches qui partent de la condition montrent les deux enchaînements
possibles selon le résultat de la condition. Le diagramme montre bien le
concept de répétition dans l’enchaînement des énoncés.

int compteur = 1;

[ compteur > 5 ]

[ compteur <= 5 ]

[Link](null,"Va
leur du compteur: "+compteur);

compteur =
compteur + 1;

[Link](0);

Figure 13. Diagramme d’activité pour le programme.

S’il y a plus d’un énoncé à répéter, comme c’est le cas de notre exemple, il
faut les regrouper en un bloc, donc mettre ces énoncés entre accolades. Il est
à noter que s’il y a un seul énoncé, les accolades sont facultatives. Dans notre
exemple, la condition est
(compteur <= 5){
66
Donc, tant que cette condition s’avère vraie (tant que le compteur sera plus
petit ou égal à 5), les énoncés
[Link](null,"Valeur du compteur:
"+compteur);
compteur = compteur + 1;
s’exécuteront en boucle. Dès que le compteur dépasse la valeur de cinq, ces
énoncés arrêtent de s’exécuter, et le programme passe à l’énoncé suivant, qui
est [Link](0). Ce dernier énoncé met fin au programme.

Expression booléenne

Le nombre de répétition est contrôlé par une expression booléenne. Une


expression booléenne peut être formée en comparant des valeurs à l’aide des
opérateurs de comparaison du tableau suivant. Il y a quelques différences
avec la notation mathématique usuelle.

Opérateur de comparaison Signification


< Plus petit que
<= Plus petit ou égal à
> Plus grand que
>= Plus grand ou égal à
== Égal à
!= N’est pas égal à
Figure 14. Opérateurs de comparaison.

Exercice. Modifiez l’exemple précédent afin qu’il affiche les entiers 0, 2, 4,


6, 8, 10.

67
Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Afficher les valeurs 0,2,4,6,8,10
*/
import [Link];
public class ExerciceWhile1{
public static void main (String args[]) {
int compteur = 0;
while(compteur <= 10){
[Link](null,"Valeur du compteur: "+compteur);
compteur = compteur + 2;
}
[Link](0);
}
}

Exercice. Modifiez l’exemple afin d’afficher 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5.

Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Afficher les valeurs de compteur 5,4,3,2,1,0,-1,-2,-3,-4,-5
*/
import [Link];
public class ExerciceWhile2{
public static void main (String args[]) {
int compteur;
compteur = 5;
while(compteur >= -5){
[Link](null,"Valeur du compteur:"+compteur);
compteur = compteur - 1;
}
[Link](0);
}
}

Exercice. Reprenons l’exemple de lecture d’entiers afin d’en afficher la


somme. Nous avons vu le cas de deux et de trois entiers. Maintenant
cherchons à extrapoler jusqu’à 10 entiers ! Il serait très long de répéter dix
fois la lecture, la conversion et l’accumulation dans somme. Une solution
naturelle est d’utiliser un énoncé while. Dans cet exercice, utilisez une boucle
while pour lire dix entiers et en afficher la somme.

68
Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire dix entiers et en afficher la somme avec un while
*/
import [Link];
public class ExerciceWhile3 extends Object {
public static void main (String args[]) {
String serie;
int entier;
int compteur = 1;
int somme = 0;

while (compteur <=10){


serie = [Link]("Entrez un entier");
entier = [Link] (serie);
somme = somme + entier;
compteur = compteur + 1;
}
[Link](null,"La somme des dix entiers est " + somme);
[Link](0);
}
}

Exercice. Supposons que le nombre d’entiers à lire est inconnu à l’avance.


Une technique souvent employée pour arrêter la répétition est l’utilisation
d’une valeur spéciale appelée sentinelle qui provoque l’arrêt de la répétition.
Par exemple, supposons que le nombre 0 représente la sentinelle. Vous
devez donc écrire un programme qui lit une série d’entier jusqu’à ce que
l’entier 0 soit entré et produit la somme de ces entiers.

Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la somme
* des entiers lus.
*/
import [Link];
public class ExerciceWhileSentinelle {
public static void main (String args[]) {
String serie;
int somme = 0;
int entier = 1; // N'importe quelle valeur différente de 0 ferait l'affaire
while (entier != 0) {
serie = [Link]("Entrez un nombre");
entier = [Link] (serie);
somme = somme + entier;
}
[Link](null,"La somme de tous les nombres est de " +somme+ ".");
[Link](0);
}
}

69
3.3 Qualité du logiciel, tests et débogage
Un aspect fondamental de la qualité d’un programme est la validité des
résultats produits par rapport à sa spécification. Une pratique courante en
développement de logiciel est de vérifier que les résultats corrects sont
produits pour un ensemble de cas de tests.

Exemple. Pour tester le programme précédent, on pourrait employer


plusieurs combinaisons de données en entrée et vérifier que pour chacun des
cas de tests, le bon résultat est produit.
Numéro de Input Output
test
1 15 165
120
30
0
2 10 30
-5
20
0
3 0 0
4 2a Exception

Ce genre de test est dit fonctionnel étant donné qu’il vérifie que le
fonctionnement du programme est valide par rapport à ce qu’il doit faire
(spécification fonctionnelle). Dans des programmes plus complexes, d’autres
aspects peuvent aussi être mesurés tel que le temps de calcul, la mémoire
consommée ou d’autres aspects dits non fonctionnels.

Il existe des méthodes et logiciels qui visent à automatiser le processus de


vérification par des tests. Généralement, même si tous les tests produisent le
résultat correct, ceci ne garantit pas que le programme fonctionne
correctement dans tous les cas possibles. Il est habituellement trop complexe
en pratique de tester tous les cas. Cependant en choisissant les cas de tests
d’une manière judicieuse, on peut obtenir un grand niveau de confiance au
sujet du fonctionnement du programme. Différentes stratégies peuvent être
employées à cet effet.

Dans l’approche de test par boîte noire ou opaque (black box testing), les tests
sont choisis sans examiner le code lui-même. On cherche à choisir les cas de
tests de manière à produire différentes combinaisons d’input qui couvrent
les différentes possibilités prévues dans la spécification du programme. Dans
70
l’approche de test par boîte blanche ou transparente (white box testing, glass box
testing), les tests sont conçus en tenant compte du code. En particulier, il faut
tenter de parcourir toutes les parties du code par l’ensemble des tests.

Lorsqu’un test ne produit pas le résultat voulu, on dit qu’il y a un ou plusieurs


bogues (bug) dans le programme. Le débogage est le processus d’élimination
des bogues. Le débogage peut ressembler à un travail de fin limier qui
consiste à trouver le code coupable en examinant les indices produits par
l’exécution du programme. Il existe des outils débogueurs qui facilitent le
dépistage des bogues par exemple en permettant d’exécuter les énoncés pas
à pas et en inspectant les valeurs des variables. En l’absence d’un tel outil, on
peut ajouter des énoncés qui affichent l’état de variables à différents endroits
du programme pour en suivre le déroulement d’une manière plus détaillée.

Exercice. Vérifiez si les bons résultats sont produits avec les tests précédents
pour la solution du dernier exercice.

3.4 La boucle avec l’énoncé for


L’utilisation d’une répétition avec compteur est très fréquente. La boucle for
simplifie l’écriture de telles boucles.

Exemple. JavaPasAPas/chapitre_3/[Link]

Le programme suivant produit le même effet que ExempleWhile en affichant


les entiers de 1 à 5. Dans un énoncé for, l’initialisation du compteur,
l’expression de fin de répétition et la mise-à-jour du compteur sont regroupés
entre parenthèses après l’identificateur réservé for.
/**
* [Link]
* Exemple d'utilisation d'un énoncé for qui affiche les entiers de 1 à 5
*/
import [Link];
public class ExempleForSimple {
public static void main (String args[]) {
for (int compteur = 1; compteur <=5; compteur = compteur + 1)
[Link](null,"Valeur du compteur: "+compteur);
[Link](0);
}
}

Une abréviation syntaxique souvent employée est l’emploi de l’opérateur de


post-incrémentation (++) qui a l’effet d’incrémenter de 1.
71
Exemple. Le for suivant est équivalent au précédent.
for (int compteur = 1; compteur <=5; compteur++)

La syntaxe du for est :


énoncé for :

for ( initialisation ; expression ; mise-à-jour ) énoncé

A noter qu’il n’y a qu’un énoncé à répéter dans notre exemple, et qu’il n’est
pas nécessaire de mettre un bloc pour l’énoncé à répéter. En effet, dans
l’exemple, il n’y a pas d’accolades. Cependant, s’il y avait plusieurs énoncés,
il aurait été nécessaire de mettre des accolades avant et après les énoncés à
répéter.

L’énoncé for fonctionne de façon semblable au while. La figure suivante


montre comment transformer un énoncé for en un while (il suffit tout
simplement de changer les emplacements de certaines composantes). On
peut donc se passer du for et toujours utiliser le while. Le for vise tout
simplement à simplifier l’écriture des programmes. D’autre part, le for n’est
pas limité au cas d’un compteur. N’importe quelle expression peut être
employée pour décider de la poursuite de la répétition.
énoncé for :

for ( initialisation ; expression ; mise-à-jour ) énoncé

initialisation while ( expression ) { énoncé mise-à-jour }

Exemple. Les deux bouts de code suivants font la même chose :


for (int compteur = 1; compteur <=5; compteur = compteur + 1)
[Link](null,"Valeur du compteur:
"+compteur);

int compteur = 1;
while(compteur <= 5){
[Link](null,"Valeur du
compteur: "+compteur);
compteur = compteur + 1;

72
}

Il est possible d’omettre l’initialisation, l’expression ou la mise-à-jour du for


mais en laissant les « ; ».

Exemple. JavaPasAPas/chapitre_3/[Link]

L’exemple suivant reprend l’exercice de lecture d’une série d’entiers avec


sentinelle vu précédemment mais en employant un for plutôt qu’un while. La
partie mise-à-jour du for est vide mais le dernier « ; » à l’intérieur des
parenthèses du for doit être présent.
/**
* [Link]
* Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la somme
* des entiers lus. Exemple illustrant un for sans la partie mise-à-jour.
*/
import [Link];
public class ExempleForSentinelle {
public static void main (String args[]) {
String serie;
int somme = 0;
for (int entier = 1; entier != 0;){
serie = [Link]("Entrez un nombre");
entier = [Link] (serie);
somme = somme + entier;
}
[Link](null,"La somme de tous les nombres est de " +somme+ ".");
[Link](0);
}
}

Exercice. Trouver une justification au besoin de conserver le dernier ; du


for précédent malgré l’omission de la partie mise-à-jour (indice : essayez de
penser comme un ordinateur…).

Exercice. Affichez les entiers 0, 2, 4, 6, 8, 10 avec un for.

Exercice. Affichez 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5 avec un for.

Exercice. Utilisez un for pour lire dix entiers et en afficher la somme.

Exercice. Utilisez un for pour lire une série d’entier jusqu’à ce que l’entier 0
soit entré (cas de sentinelle) et afficher la somme de ces entiers.

73
Exercice. Affichez le résultat suivant sur la sortie standard (avec
[Link]() et [Link]())
1
12
123
1234
12345
123456
1234567
12345678
123456789

Solution. JavaPasAPas/chapitre_3/[Link]

Avec for imbriqués :


/**
* [Link]
*/
import [Link];
public class ExerciceForFor {
public static void main (String args[]) {
for (int compteur1 = 1; compteur1 <=9; compteur1 = compteur1 + 1){
for (int compteur2 = 1; compteur2 <=compteur1; compteur2 = compteur2 + 1)
[Link](compteur2);
[Link]();
}
}
}

3.5 La décision avec if


L’énoncé if permet au programme de prendre une décision au sujet des
actions à exécuter en fonction d’une condition à évaluer.

Exemple. JavaPasAPas/chapitre_3/[Link]

Le programme suivant lit un entier (unInt) et indique s’il est plus grand que
10 ou non. Pour déterminer le message à afficher, une décision est prise en
comparant l’entier lu à 10 dans une condition (unInt > 10). Selon le résultat
de la condition, le if permet de choisir entre les deux énoncés alternatifs à
exécuter. La première alternative suit la condition et elle est exécutée si la
condition est évaluée à vrai (true). Sinon (i.e. la valeur de l’expression est false),
l’énoncé qui suit l’identificateur else est exécuté.
74
/**
* [Link]
* Petit exemple illustrant l'énoncé if.
*/
import [Link];
public class ExempleIf{
public static void main (String args[]) {
String unString = [Link]("Entrez un premier nombre entier");
int unInt = [Link](unString);

// Exemple d'énoncé if
if (unInt > 10)
[Link](null,unInt + " est plus grand que 10");
else
[Link](null,unInt + " n'est pas plus grand que 10");

[Link](0);
}
}

La syntaxe du if est :

énoncé if :

if ( expression ) énoncé1 else énoncé2

Lors de l’utilisation d’un if, l’énoncé1 suivant l’expression ne sera exécutée que
si l’expression (la condition) se révèle vraie. L’énoncé ne s’exécute qu’une
seule fois. Si l’expression se révèle fausse, l’énoncé2 suivant le mot else sera
exécuté (une seule fois). Dans notre exemple, si la condition
(unInt > 10)
se révèle vraie, l’énoncé
[Link](null,unInt + " est plus
grand que 10");
sera exécuté. Si l’expression se révèle fausse, l’énoncé qui sera exécuté est
[Link](null,unInt + " n'est
pas plus grand que 10");

Le diagramme d’activité suivant illustre cet enchaînement.

75
[ unInt > 10 ] if [ unInt <= 10 ]

[Link](null, [Link](null,
unInt + " est plus grand que 10"); unInt + " n'est pas plus grand que 10");

[Link](0);

Il est à noter que s’il y a plusieurs énoncés à exécuter dans la partie énoncé1(cas
vrai) ou énoncé2 (cas faux), il faut les regrouper en un bloc, se débutant par
l’accolade ouvrante et se terminant par l’accolade fermante.22 Aussi, la partie
else est optionnelle. En son absence, lorsque l’expression de condition est
fausse, rien n’est exécuté. Ceci peut causer une ambigüité potentielle illustrée
par l’exemple suivant.

Exemple. JavaPasAPas/chapitre_3/[Link]

Illustration du else ambigu.


/**
* [Link]
* Petit exemple illustrant l'ambiguïté du else.
*/
import [Link];
public class ExempleElseAmbigu{
public static void main (String args[]) {
String chaine1 = [Link]("Entrez un premier nombre entier");
String chaine2 = [Link]("Entrez un second nombre entier");
int entier1 = [Link](chaine1);
int entier2 = [Link](chaine2);

// If ambigu
if (entier1 > 10)
if (entier2 > 10)
[Link](null,entier1 + " et "+ entier2 + " sont plus grands que 10");
else
[Link](null,entier1 + " est inférieur ou égal à 10");

[Link](0);
}
}

Voici un scénario possible avec ce programme ! Voyez-vous le problème ?

Le problème vient du fait que la disposition du texte donne l’impression que


le else est associé au premier if. Ce n’est pas le cas ! Java associe toujours le else
22Il est parfois recommandé de toujours mettre des accolades autour des énoncés découlant
d’une clause if afin d’éviter les ambiguités.
76
au if le plus rapproché ! La disposition suivante suggère la bonne
interprétation :
if (entier1 > 10)
if (entier2 > 10)
[Link](null,entier1 + " et "+
entier2 + " sont plus grands que 10");
else
[Link](null,entier1 + " est
inférieur ou égal à 10");

Pour forcer le else à être associé au premier if, il faut utiliser un bloc Java afin
de forcer la terminaison du second if.

Exemple. JavaPasAPas/chapitre_3/[Link]

Forcer l’association du else avec un if éloigné par l’introduction d’un bloc.


/**
* [Link]
* Introduction d'un bloc pour terminer un if sans else
*/
import [Link];
public class ExempleIfIfElse{
public static void main (String args[]) {
String chaine1 = [Link]("Entrez un premier nombre entier");
String chaine2 = [Link]("Entrez un second nombre entier");
int entier1 = [Link](chaine1);
int entier2 = [Link](chaine2);

// If ambigu
if (entier1 > 10) {
if (entier2 > 10)
[Link](null,entier1 + " et "+ entier2 + " sont plus grands que 10");
}
else
[Link](null,entier1 + " est inférieur ou égal à 10");

[Link](0);
}
}

Voici un scénario avec cette nouvelle version :

77
Nous avons maintenant vu les trois manières d’enchaîner les énoncés :
séquence, boucle et choix. Il est possible de combiner ces trois types
d’énoncés de manière quelconque. Les diagrammes syntaxiques suivants
résument les différents cas d’énoncés vus jusqu’à présent :
énoncé :
déclaration de variable

affectation

appel de méthode

bloc d'énoncés

énoncé while

énoncé for

énoncé if
etc.

bloc d'énoncés :
{ }
énoncé

78
Dans un bloc d’énoncé, il peut y avoir un while, dans le while, un if et dans
le if, un bloc, etc.

Exercice. Lire deux entiers et afficher la division du premier par le


deuxième. Si le diviseur est 0, afficher un message à cet effet.

Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire deux entiers et afficher la division entière.
* Si le diviseur est 0 afficher un message à cet effet.
*/
import [Link];
public class ExerciceIf1 {

public static void main (String args[]) {

String chaine1 = [Link]("Entrez un premier nombre entier");


String chaine2 = [Link]("Entrez un second nombre entier");
int entier1 = [Link](chaine1);
int entier2 = [Link](chaine2);

if (entier2 == 0)
[Link](null,"La division est impossible");
else
[Link](null,entier1 + "/" + entier2 + "=" + entier1 / entier2);
[Link](0);
}
}

Exercice. Lire deux entiers et afficher le maximum des deux. S’ils sont
égaux, afficher n’importe lequel des deux.

Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire trois entiers et afficher le maximum des trois
*/
import [Link];
public class ExerciceIfMax2{

public static void main (String args[]) {

String chaine1 = [Link]("Entrez un premier nombre entier");


String chaine2 = [Link]("Entrez un second nombre entier");

int entier1 = [Link](chaine1);


int entier2 = [Link](chaine2);

if (entier1 > entier2)


[Link](null,"Le maximum des deux entiers est " + entier1);
else

79
[Link](null,"Le maximum des deux entiers est " + entier2);

[Link](0);
}
}

Exercice. Lire deux entiers et afficher le plus grand des deux s’il y en a un
qui est le plus grand, sinon afficher qu’ils sont égaux.

Exercice. Lire trois entiers et afficher le maximum des trois.

Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire trois entiers et afficher le maximum des trois
*/
import [Link];
public class ExerciceIfMax3{

public static void main (String args[]) {

String chaine1 = [Link]("Entrez un premier nombre entier");


String chaine2 = [Link]("Entrez un second nombre entier");
String chaine3 = [Link]("Entrez un troisième nombre entier");

int entier1 = [Link](chaine1);


int entier2 = [Link](chaine2);
int entier3 = [Link](chaine3);

if (entier1 > entier2)


if (entier1 > entier3)
[Link](null,"Le maximum des trois entiers est " + entier1);
else
[Link](null,"Le maximum des trois entiers est " + entier3);
else
if (entier2 > entier3)
[Link](null,"Le maximum des trois entiers est " +entier2);
else
[Link](null,"Le maximum des trois entiers est " +entier3);
[Link](0);
}
}

On peut imaginer la complexité de cette méthode si on accroît le nombre


d’entiers à lire. Dans l’exercice suivant, cherchez à utiliser une boucle pour
simplifier le code.

Exercice. Lire 5 entiers et afficher l'entier maximal (le plus grand des cinq
entiers).

80
Solution. JavaPasAPas/chapitre_3/[Link]
/**
* [Link]
* Lire 5 entiers et afficher l'entier maximal
*/
import [Link];
public class ExerciceWhileIf{
public static void main (String args[]) {
String chaine = [Link]("Entrez un nombre");
int plusGrand = [Link] (chaine);
for (int compteur = 1; compteur < 5; compteur=compteur+1){
chaine = [Link]("Entrez un nombre entier");
int entier = [Link] (chaine);
if (entier > plusGrand) {
plusGrand = entier;
}
}
[Link](null,"L'entier le plus grand est " + plusGrand);
[Link](0);
}
}

Cette solution montre un exemple de if imbriqué dans une boucle.

Exercice *. Afficher les nombres premiers plus petits que 100.

Exercice. Lire une note entre 0 et 100 inclusivement et afficher la lettre


correspondante selon le barème suivant :

0<=note<60 E
60<=note<70 D
70<=note<80 C
80<=note<90 B
90<=note<=100 A

81
Types et expressions Java
Dans les premiers chapitres, nous avons rencontré les types int et String et
quelques expressions simples. Ce chapitre approfondit les notions de type et
d’expression Java.

3.6 Type primitif et littéral


Rappelons que Java inclut les types primitifs du tableau suivant.
Type Valeurs Exemples Signification
primitif de
littéraux
boolean true ou false true Vrai
false faux
char Caractère selon 'a' La lettre a (minuscule)
le code 'A' Lettre A (majuscule)
standard '5' Le chiffre 5
UNICODE (16 ' ' Un espace
bits) '\'' Le caractère '
'\u0061' Le caractère a dont le code
'\u000a' Unicode est 0061 (en
hexadécimal)
Le caractère non imprimable
qui correspond à un saut de
ligne
byte Octet en (byte)15 Conversion explicite d’un
binaire (8 littéral int
bits) entre -
128 (-27 ) et
127 (27-1)
short Entier (short)325 Conversion explicite d’un
(précision de littéral int
16 bits) entre
-32 768 (-215)
et 32 767 (215 -
1)
int Entier 3015 Par défaut, un littéral entier
(précision de -3015 est de type int
32 bits) entre 0X2A Le moins unaire est employé
-2 147 483 648 017 pour un nombre négatif
(-231) et Représentation hexadécimale du
2 147 483 647 int 42 (2*16+10)
(231-1) Représentation octale du int
15 (1*8 + 7)
long Entier 3015L Le L ou l à la fin du littéral
(précision de 3015l entier indique que le nombre
64 bits) entre entier est de type long
-
9 223 372 036 8
54 775 808 (-
263) et
9 223 372 036 8

82
54 775 807 (263-
1)
float Nombre réel 45.032F Le F ou f à la fin du littéral
(précision de 45.032f numérique indique que le
32 bits selon 32F nombre doit être de type float
le code 32f
standard IEEE 12.453E5F
754-1985) entre 12.053E-5F 1245300 (La notation En
-3.4*1038 et représente 10n)
3.4*1038 (7 0.0001205
chiffres
significatifs)
double Nombre réel 45.032 Par défaut, un littéral
(précision de 45.032D numérique avec partie décimale
64 bits selon 45.032d est de type double
le code 32D Le D ou d à la fin indique que
standard IEEE 32d le nombre doit être de type
754-1985) entre 12.453E5 double
-1.7*10308 et 12.053E-5
1.7*10308 (15 124. 1245300 (La notation En
chiffres représente 10n)
significatifs) 0.0001205
Figure 15. Types primitifs de Java.

Chacun des types primitifs correspond à un ensemble de valeurs décrit dans


le tableau de la Figure 15. Une variable d’un type primitif contient une valeur
parmi l’ensemble des valeurs du type.

Littéral (litteral)

Une valeur particulière d’un type primitif exprimée par une chaîne de
caractères dans le code source d’un programme est appelée un littéral. Le
tableau précédent montre des exemples de littéraux pour chacun des types
primitifs.

3.7 Types et expressions numériques


Les types primitifs numériques byte, short, int, long correspondent à des entiers
de différentes tailles : ils utilisent une quantité variable de mémoire, et ils
peuvent représenter des nombres plus ou moins volumineux. À l’exception
du type « char » qui peut être considéré comme un type représentant des
entiers (de 0 à 65536), les types entiers en Java sont toujours signés dans le
sens où ils permettent de représenter à la fois les nombres positifs et négatifs.
En contraste, dans des langages comme Go, Swift, C#, C++, etc., il existe
des types d’entiers signés et non-signés (unsigned). Notez qu’il y a toujours une
valeur négative de plus grande amplitude que n’importe quelle valeur positive
(par exemple, -128, -32768, -2147483648, etc.) ce qui signifie qu’on ne peut
83
toujours prendre la valeur absolue : par exemple, la valeur 2147483648 ne
peut pas être représentée par un « int » alors que -2147483648 est
parfaitement légitime.

Les types float et double sont des nombres réels avec partie fractionnaire. En
Java, on ne peut pas représenter les valeurs réelles (comme π). On utilise
plutôt des nombres à virgule flottante. Ainsi donc, on peut utiliser des «
double » pour consacrer 64 bits afin de représenter des nombres. On utilise
alors la norme « binary64 » qui accorde 53 bits à la mantisse d’une
représentation binaire. En d’autres mots, on peut pratiquement représenter
n’importe quel nombre de la forme m 2p tant que m n’excède pas 253. En
particulier, le type « double » en Java peut représenter tous les entiers (positifs
et négatifs) qui n’excèdent pas une magnitude de 253. Quand un nombre ne
peut pas être pas être représenté, Java va trouver le nombre à virgule flottante
le plus proche. Si nous arrivons exactement entre deux nombres à virgule
flottante, comme c’est le cas avec le nombre 9000000000000002.5, Java va
choisir le nombre le plus proche qui a une mantisse paire (ici
9000000000000002). Tous les nombres décimaux à 6 chiffres significatifs
peuvent être représentés avec le type float. Par contre, certains nombres
comprenant 6 chiffres significatifs correspondent à plus d’un nombre de
type float. On peut distinguer deux nombres de type float en utilisant 9 chiffres
significatifs. Tous les nombres décimaux à 15 chiffres significatifs peuvent
être représentés avec le type double; on peut distinguer deux nombres de type
double en utilisant 17 chiffres significatifs. Dans la pratique, nous utilisons
donc souvent soit 9 chiffres significatifs, soit 17 chiffres significatifs afin de
stocker en format décimal les nombres. Par exemple, si on sait que le résultat
sera stocké sous la forme d’un double, il est inutile de saisir le nombre π avec
plus de précision que 3.1415926535897932. Les types float et double peuvent
aussi avoir pour valeur –infini, +infini ainsi qu’une valeur spéciale NaN (not-
a-number) que nous pouvons générer en divisant zéro par zéro (par
exemple).

Exemple. JavaPasAPas/chapitre_3/[Link]

L’exemple suivant illustre le comportement du zéro positif, du zéro négatif


et du NaN et Java. Il affiche ceci à l’écran :
true
-Infinity
Infinity

84
false
NaN
False

public class ExempleZero {


public static void main(String[] s) {
double minus_zero = -0.0;
double plus_zero = +0.0;
[Link](minus_zero == plus_zero);
[Link](1/minus_zero);
[Link](1/plus_zero);
[Link](1/minus_zero == 1/plus_zero);
double n = 0.0 / 0.0;
[Link](n);
[Link](n == n);
}
}

La Figure 15 montre les conventions Java pour exprimer les littéraux des
types numériques. Java permet de former des expressions numériques
complexes de manière analogue aux expressions mathématiques. Le tableau
suivant montre les opérations numériques binaires de Java.
Opération binaire Signification
+ Addition
Exemples : 4 + 5 donne 9, 4.2 + 16.3 donne 20.5
- Soustraction
Exemples : 5 - 2 donne 3, 20.5 – 16.3 donne 4.2
* Multiplication
Exemple : 3 * 4 donne 12
/ Division
Exemples : 16 / 5 donne 3, 16.0 / 5.0 donne 3.2
% Reste après division entière
Exemples : 16 % 4 donne 0, 16 % 5 donne 1

La division des nombres entiers positifs se fait comme à la petite école : le


résultat de la division est un entier qui représente le nombre de fois que le
diviseur peut être soustrait au numérateur sans obtenir un résultat négatif.
Par exemple, 4/4, 5/4 et 7/4 donnent toutes comme réponse le quotient 1.
Le reste de la division est obtenu avec le symbole %. On peut vérifier que
l’entier x divise l’entier y en comparant y % x avec la valeur zéro.

La division par zéro (avec / ou %) dans le cas des entiers génère une erreur
et peut terminer un programme. La division par zéro dans le cas des nombres

85
à virgule flottante est permise, mais elle génère une valeur infinie ou la valeur
NaN.

Dans le cas d’une operation arithmétique dont le résultat ne peut pas être
représenter par le type choisi, un résultat incorrect peut être obtenu. Il est de
votre responsabilité de vérifier que le résultat du calcul peut être représenté.

Exemple. Le int 2000000000 ajouté au int 2000000000 donne la valeur -


294967296 :
int x = 2000000000;
int y = 2000000000;
int z = x + y; // = -294967296

Il est permis de former des expressions arithmétiques complexes en


combinant les opérations au besoin. Comme pour les conventions
mathématiques usuelles, lorsqu’il y a plusieurs opérations, les opérations à
plus grande priorité sont effectuées en premier. À priorité égale, les
opérations sont effectuées de gauche à droite. Le tableau suivant montre la
priorité des opérateurs en ordre décroissant de priorité. Le + et - unaires
servent à préciser le signe d’une valeur numérique comme dans +3 ou -15,
le + étant toujours facultatif.
Opération Priorité
(,) 0
+, - unaires 1
*, /, % 2
+, - binaires 3

Exemple illustrant les priorités. L’expression suivante


3 + 2 * 6 - 3 - 2 * 4

est équivalente à
(((3 + (2 * 6)) - 3) - (2*4))

dont le résultat est 4. L’évaluation procède donc selon les étapes suivantes :
(((3 + (2 * 6)) - 3) - (2*4))
(((3 + 12) - 3) - (2*4))

86
(((3 + 12) - 3) - 8)
((15 - 3) - 8)
(12 - 8)
4
Les parenthèses permettent de modifier cet ordre d’évaluation au besoin.

Exercice. Quel est le résultat de l’expression suivante :


2+4*2*5+10/2
Réécrire l’expression avec des parenthèses qui reflètent la priorité
d’évaluation des opérations.

Conseil

Ne vous fiez pas à la priorité, et mettez des parenthèses en cas de doute !

Lorsque des opérandes de types différents sont combinés, Java effectue des
conversions de type automatiques en convertissant à un type unique tous les
opérandes de l’expression.

Exemple. L’expression suivante


3.4 + 7

fait intervenir le double 3.4 et le int 7. Le int sera converti automatiquement en


un double avant d’effectuer l’opération.

La conversion cherche à éviter la perte d’information en faisant une promotion


à un type plus général. Le tableau suivant montre les promotions valides en
Java pour les types numériques. Les conversions sont appliquées non
seulement à l’évaluation d’expressions mais aussi lors de l’affectation du
résultat de l’expression à une variable et lors du passage d’un paramètre.
Type Promotions automatiques valides
double aucune
float double
long float ou double
int long, float ou double
short int, long, float ou double
byte short, int, long, float ou double

87
Lorsqu’une conversion non valide est voulue par le programmeur, il est
possible de forcer une conversion par une opération de conversion (cast)
dont la syntaxe est :
(nomDuType)valeur
La valeur sera alors convertie dans le type entre parenthèses. La conversion
peut entraîner une perte de précision comme l’illustre l’exemple suivant.

Exemple. Le double 15.2 est converti en int 15 et affecté à la variable unInt :


int unInt = (int)15.2

Sans conversion explicite, une erreur serait levée car cette promotion
automatique n’est pas valide en Java. La conversion explicite est
potentiellement dangereuse. En tant que programmeur, vous avez la
responsabilité de vérifier que le nouveau type peut représenter la valeur.
Considérons l’exemple suivant.

Exemple. Le int 131072 est converti en short 0 et affecté à la variable unShort :


short unShort = (short)131072

Conseil de génie logiciel

Il est préférable de spécifier explicitement les conversions dans les


expressions pour clarifier le comportement désiré.

Exemple. L’expression suivante


3.4 + 7
est équivalente à
3.4 + (double)7
La deuxième formulation est préférable car elle clarifie la conversion désirée.

3.8 Expressions booléennes


Le type boolean comprend deux littéraux, true et false. Ce type est souvent
utilisé dans les conditions des énoncés if et des énoncés de boucle (while, for,
do). Nous avons vu comment formuler des expressions booléennes simples
88
avec les opérateurs de comparaison (<, ==, >, etc.) dans le chapitre 3. Il est
aussi possible de formuler des expressions booléennes complexes avec les
opérateurs logiques (et, ou et négation) du tableau suivant :
Opérateur logique Signification
& et
| ou
! négation

Exemple. JavaPasAPas/chapitre_3/[Link]

Le programme suivant illustre les opérateurs logiques &, | et !.

/**
* [Link]
* Petit exemple illustrant l'énoncé if.
*/
import [Link];
public class ExempleLogique{
public static void main (String args[]) {
String unString = [Link]("Entrez un premier
nombre entier");
int unInt = [Link](unString);
if (unInt > 10 & unInt < 20) { [Link](null,unInt
+ " est entre 10 et 20"); }
if (unInt == 100 | unInt == 200) {
[Link](null,unInt + " est 100 ou 200"); }
if (!(unInt > 30)) { [Link](null,unInt + " n'est pas
plus grand que 30"); }
[Link](0);
}
}
La condition
(unInt > 10 & unInt < 20)
est évaluée à vrai si unInt est à la fois plus grand que 10 et plus petit que 20.
Lorsque l’on utilise un &, il faut que les deux parties de la condition soient
vraies pour que le résultat soit vrai.

La condition
89
if (unInt == 100 | unInt == 200)
est évaluée à vrai si unInt est égal à 10 ou unInt est égal à 20. Une seule des
deux parties doit être vraie pour que le résultat soit vrai. Le ou n’est pas
exclusif, c’est-à-dire que si les deux parties sont vraies, le résultat est vrai.

La condition
!(unInt > 30)
est évaluée à vrai si inInt > 30 est faux, donc si inInt <= 30.

Java inclut aussi le && et le ||. Le && est une variante du & qui court-
circuite l’évaluation lorsque possible. Le & évalue toujours les deux parties
de la condition alors que le && n’évalue pas la deuxième partie si la première
est fausse. On dit que l’évaluation est court-circuitée. En effet, si la première
partie est fausse, le résultat de la condition sera nécessairement faux. Il n’est
donc pas nécessaire d’évaluer la deuxième partie.

Opérateur logique Signification


&& et (évaluation court-circuité)
|| ou (évaluation court-circuité)

Exemple. L’expression

2 > 3 && 2 < 10

donne false. Comme 2 > 3 donne false, il n’est pas nécessaire d’évaluer la
deuxième partie 2 < 10. Le && évite d’évaluer la seconde partie, 2 < 10.
Dans la plupart des circonstances, le résultat de & et && est identique.
Cependant, si l’évaluation de la partie droite peut avoir un effet autre que
l’évaluation de la condition elle-même, le résultat ne sera pas toujours le
même.

Exemple. JavaPasAPas/chapitre_3/[Link]

Le programme suivant vérifie si l’entier saisi est un diviseur de 10. Il vérifie


si l’entier est non nul et si le reste après division entière est nul. Dans le cas
où l’entier est nul, il ne faudrait pas effectuer la division qui provoquerait une
exception en Java et l’arrêt du programme. En utilisant le &&, la division ne
sera pas effectuée.

90
/**
* [Link]
* Petit exemple illustrant l'énoncé if.
*/
import [Link];
public class ExempleEtCourtcircuite{
public static void main (String args[]) {
String unString = [Link]("Entrez un premier nombre entier");
int unInt = [Link](unString);

// Exemple de l'opérateur &


if (unInt != 0 && 10 % unInt == 0)
[Link](null,unInt + " est un diviseur de 10");
else
[Link](null,unInt + " n'est pas un diviseur de 10");
[Link](0);
}
}

Dans le cas du ||, si la première partie est vraie, la deuxième partie de la


condition n’est pas évaluée, car le résultat doit être vrai. L’évaluation se fait
toujours de la gauche vers la droite. La première condition à gauche est
toujours évaluée en premier.

3.9 Traitement de caractères


Le type char correspond souvent en pratique à l’ensemble des caractères. Cet
ensemble est défini par le standard Unicode ([Link]). Les codes
sont énumérés dans

[Link]

Plus précisément, un char en Java permet de représenter n’importe quel


caractère Unicode pouvant être représenté avec un mot de 16 bits dans la
norme UTF-16. Certains caractères spécialisés, comme les émojis, requièrent
32 bits et donc deux mots de 16 bits (deux char). La norme Unicode permet
aussi de combiner plusieurs caractères pour former un seul caractère visible
(par exemple, les émojis « famille »).

On peut aussi voir le type char comme une valeur entière entre 0 et 65536.
Le type char occupe autant d’espace en mémoire que le type short, mais le
type short est utilisé pour représenter les valeurs entières entre -32768 et
327678.

91
La Figure 15 montre des exemples de littéraux du type char. Un caractère
imprimable est représenté entre apostrophes. On peut aussi employer le
code Unicode du caractère selon le format :
'\uyyyy'

où yyyy est une suite de quatre chiffres hexadécimaux qui correspond au code
Unicode hexadécimal du caractère. Le \u est une séquence dite d’échappement
qui altère l’interprétation normale par le compilateur Java. La séquence
indique au compilateur que ce qui suit est le code Unicode du caractère et
non pas le caractère lui-même. Le tableau suivant montre d’autres séquences
d’échappement prévues en Java.

Séquence Code Unicode Description


d’échappement
\b \u0008 backspace BS
\t \u0009 tabulation HT
\n \u000a saut de ligne LF
\f \u000c saut de page FF
\r \u000d retour de chariot CR
\" \u0022 guillemets "
\' \u0027 apostrophe '
\\ \u005c backslash \

3.9.1 Type String, objets et classes


Le type char ne permet que de manipuler un caractère à la fois. Il est souvent
nécessaire de manipuler une chaîne de caractères, par exemple pour afficher
un message ou pour saisir une donnée. Java inclut le type String à cet effet.
Le type String est aussi un type prédéfini mais n’est pas un type primitif.
En fait, String, dont le nom complet est [Link], est une classe Java
qui fait partie du package [Link]. Le type d’une variable peut aussi être une
classe. Dans ce cas, une valeur de la variable est une référence à un objet de la
classe. Par analogie à un type primitif qui correspond à un ensemble de
valeurs, une classe correspond à un ensemble d’objets. La notion d’objet est
fondamentale en Java ainsi que dans les autres langages objet. Par analogie
avec les types primitifs, on peut dire qu’un objet est analogue à une valeur
possible pour une classe. La différence entre une valeur d’un type primitif et
une référence à un objet d’une classe n’est pas apparente dans les exemples
que nous avons rencontrés jusqu’à présent. Nous allons maintenant faire

92
ressortir certains aspects fondamentaux qui distinguent les objets (de classes)
et les valeurs (de types primitifs).

• Constructeur d’objet

Pour créer un nouvel objet, il faut normalement employer un constructeur


d’objet.

Exemple.
JavaPasAPas/chapitre_3/[Link]

L’exemple de programme suivant permet d’illustrer concrètement la notion


d’objet et de constructeur d’objet pour la classe String.
import [Link]; // Importe la classe [Link]
public class ExempleCreationObjetString{
public static void main (String args[]) {
String string1 = new String("abcdef");
String string2 = string1;
String string3 = new String("abcdef");

[Link](string1 == string2); //true


// string1 et string3 sont deux objets différents
[Link](string1 == string3); //false
// par contre, string1 et string3 ont le même contenu
[Link]([Link](string3)); //true
}
}

Après les trois affectations


String string1 = new String("abcdef");
String string2 = string1;
String string3 = new String("abcdef");
le résultat suivant est produit :

objet de la classe String (OID=4000)


string1 4000
"abcdef"
string2 4000

string3 4050 objet de la classe String (OID=4050)

"abcdef"

93
L’appel new String("abcdef") dans la ligne suivante
String string1 = new String("abcdef");
crée un objet de la classe String dont le contenu est "abcdef". L’objet
contient la chaîne de caractères "abcdef". Il contient la chaîne mais n’est pas la
chaîne ! Lorsqu’un objet est créé, un identifiant d’objet (OID) lui est assigné
automatiquement. Dans notre exemple, l’objet crée a l’OID = 4000. Cette
valeur n’est donnée qu’à titre d’exemple et n’a pas d’importance en soi. On
ne doit pas se préoccuper de la manière dont les OID sont générés. En fait,
l’OID n’est pas visible dans le programme Java et n’est pas manipulable
directement. Un OID est en quelque sorte une adresse pour retrouver un
objet. Il est analogue à un numéro d’assurance sociale pour un citoyen. Le
numéro en soi n’a pas d’importance. Ce qui compte, c’est qu’il permet
d’identifier un citoyen sans ambiguïté. L’adresse en mémoire centrale est une
manière de réaliser un OID, mais il y a aussi d’autres implémentations
possibles.

Dans l’énoncé
String string1 = new String("abcdef");
l’objet de la classe String créé dans la partie droite est affecté à la variable
string1 de la partie gauche. Le type de string1 doit être le même que celui de
l’objet créé23. C’est pourquoi, le type de string1 est String. Il est fréquent de
rencontrer en Java ce genre d’énoncé où une variable de type ClasseX est
déclarée et on lui affecte un objet de type ClasseX créé par new ClasseX().

Lorsqu’un objet est affecté à une variable, c’est l’OID de l’objet qui est placé
dans la variable. On dit alors que la variable contient une référence à l’objet.
Souvent les références sont représentées graphiquement par des flèches tel
qu’illustré dans la figure suivante car les valeurs exactes des OID sont sans
importance. Ce qui compte, c’est que la variable fasse référence au bon objet.

23 Nous verrons plus loin que ce n’est pas nécessairement le même type dans certaines
circonstances
94
objet de la classe String (OID=4000)
string1
"abcdef"
string2

string3 objet de la classe String (OID=4050)

"abcdef"

Un objet est créé avec un constructeur d’objet. Un constructeur d’objet est


une méthode spéciale dont le rôle est de créer un objet d’une classe. Il est
appelé en utilisant la syntaxe suivante :

appel de constructeur d'objet :

new nomDeClasse ( liste des paramètres )

Un constructeur porte le même nom que la classe. De ce point de vue, une


classe est comme un moule à objet. La classe String est donc un moule
pour construire des objets de type String.

Une valeur de type String ne peut pas être modifiée. Une fois que la valeur
« abcdef » a été assignée à la variable, on ne peut plus changer la chaîne. C’est
un choix spécifique à cette classe puisqu’il aurait été possible pour les
créateurs du Java de faire en sorte que la classe String puisse modifier sont
contenu.

La ligne
String string2 = string1;
affecte le contenu de string1 à string2. Ceci ne copie pas l’objet mais
plutôt l’OID de l’objet. En conséquence, les deux variables font maintenant
référence au même objet !

La ligne
String string3 = new String("abcdef");
crée un autre objet de la classe String, dont l’OID = 4050. Cet autre objet
contient aussi "abcdef" mais c’est un objet différent du premier !

95
Par opposition aux objets, il n’y a pas de distinction entre une valeur et son
contenant pour les types primitifs. Comment fait-on la différence entre la
référence à l’objet et le contenu de l’objet dans un programme ? La réponse
à cette question est illustrée par le reste du code du programme.

Dans le cas d’objets, le « == » Java compare les références aux objets et non
pas le contenu des objets. Ainsi le test string1 == string2 dans
[Link](string1 == string2); //true
produit la valeur true car les deux variables font référence au même objet mais
string1 == string3 dans
[Link](string1 == string3); //false
est false car les deux variables string1 et string3 font référence à des objets
différents ! Pour comparer le contenu des objets String, on peut utiliser la
méthode equals() de la classe String. Ainsi le test [Link](string3) dans
[Link]([Link](string3)); //true
produit la valeur true parce que le contenu des deux objets est le même.

Attention !

Une erreur fréquente en Java est de confondre == et equals().

La méthode equals() de la classe String est une méthode d’objet. La


possibilité d’appeler des méthodes sur les objets est un autre aspect qui les
distingue des valeurs des types primitifs. Nous avons déjà dit qu’une classe
regroupe un ensemble de méthodes. Parmi ces méthodes, il y a des méthodes
de classe et des méthodes d’objets. On ne peut appeler une méthode de
classe sur un objet ou une méthode d’objet sur une classe.

• Documentation des classes et méthodes

Un aspect important de la programmation Java est le fait qu’un grand


nombre de classes et de méthodes sont déjà définies et mises à la disposition
du programmeur. Le programmeur doit pouvoir facilement retrouver les
méthodes et les classes. À cet effet, le programmeur peut consulter la
documentation des classes et méthodes prédéfinies. Cette documentation est
accessible sur le site de Oracle. Pour la version 8, vous pouvez y accéder par :

96
[Link]

Cette documentation est sous forme HTML, et elle peut être consultée à
partir d’un fureteur Web. Notez que même si les versions de Java se succède
rapidement, il est souvent possible de tout faire avec les classes de la version
8. Il y a d’ailleurs des bénéfices à ne pas trop rapidement adopter des
fonctions et des classes qui ne sont disponibles qu’avec des versions récentes
du Java.

Exercice. À ce point-ci, vous devriez vous familiariser un peu avec cette


documentation en cherchant la classe String. Vous pouvez la retrouver
dans la liste All Classes du panneau inférieur gauche. Cliquez sur METHOD
de la rubrique SUMMARY NESTED dans le panneau de droite en haut et
vous obtenez la liste des méthodes de la classe String.

• La documentation montre pour chacune des méthodes, le type de


ce qui est retourné sous la première colonne du tableau Method
summary.

o L’identificateur réservé void signifie que la méthode ne


retourne rien.

• Les méthodes de classe sont distinguées des méthodes d’objet par


l’identificateur réservé static qui apparaît avant le type de ce qui est
retourné. Par exemple, la méthode copyValueOf(char[] data)
est une méthode de classe alors que charAt(int index) est une
méthode d’objet.

• Dans la deuxième colonne du tableau Method Summary, la liste des


paramètres apparaît après le nom de la méthode. Les paramètres
sont séparés par des virgules.

o Pour chacun des paramètres, il y a son type suivi d’un nom


de paramètre. Le nom n’a pas d’importance comme tel.
Lorsqu’on appelle la méthode, les paramètres doivent
apparaître dans le même ordre et doivent être du bon type.

La classe String inclut plusieurs autres méthodes d’objet visant la


manipulation de chaînes de caractères.

97
Exemple. Le programme suivant illustre quelques méthodes de la classe
String.

import [Link]; // Importe la classe [Link]


public class ExempleMethodesDeString{

public static void main (String args[]) {

String string1 = new String("abcdef");


String string2 = new String("cd");
[Link]("String string1 = new String(\"abcdef\")");
[Link]("String string2 = new String(\"cd\")");
[Link]("La longueur de string1 est :" + [Link]());
[Link]("Le caractère en position 2 de string1 est :" + [Link](2));
[Link]("La sous-chaine en position 2 de string1 est :" + [Link](2));
[Link]("La sous-chaine qui débute en position 2 et fini en 4 est :" + [Link](2,5));
[Link]("La première occurrence de string2 dans string1 est à la position :" + [Link](string2));
[Link]("La concaténation de string2 à string1 donne :" + [Link](string2));
}
}

Résultat affiché :
String string1 = new String("abcdef")
String string2 = new String("cd")
La longueur de string1 est :6
Le caractère en position 2 de string1 est :c
La sous-chaine en position 2 de string1 est :cdef
La sous-chaine qui débute en position 2 et fini en 4 est :cde
La première occurrence de string2 dans string1 est à la position
:2
La concaténation de string2 à string1 donne :abcdefcd

Notez dans la ligne suivante l’utilisation de la séquence d’échappement \".


Ceci est nécessaire pour inclure un guillemet dans une chaîne de caractères
étant donné que le guillemet est aussi le délimiteur de fin de chaîne.

La méthode length() retourne la taille d’un String. Elle compte le


nombre de mots de 16 bits contenu dans la chaîne de caractères. Dans
plusieurs cas, le résultat de la méthode donne le nombre de caractères UTF-
16 : tant qu’au caractère n’appartient à un plan supplémentaire. Les plans
supplémentaires comprenent les emojis, les symboles mathématiques et
musicaux et autres symboles spécialisés.
[Link]("La longueur de string1 est :" +
[Link]());

98
L’appel [Link](2) retourne le caractère (ou plutôt le mot de 16 bits) en
position 2 de string1, ce qui correspond au troisième caractère de "abcdef",
c’est-à-dire le caractère « c » car les positions des caractères sont numérotées
à partir de la position 0 !

position 0 1 2 3 4 5
caractère a b c d e f

Figure 16. Numérotation des positions des caractères à partir de 0 !

Le fait d’inclure 0 comme indice peut sembler étrange à un non-initié …

L’appel [Link](2) de la ligne suivante retourne un objet de la classe


String qui contient la sous-chaîne qui débute en position 2 (troisième
caractère).
[Link]("La sous-chaine en position 2 de string1 est
:" + [Link](2));

L’appel [Link](2,5) de la ligne suivante retourne un objet de la classe


String qui contient la sous-chaîne qui débute en position 2 (troisième
caractère) et se termine en position 5 (excluant la position 5, ce qui
correspond au cinquième caractère).
[Link]("La sous-chaine qui débute en position 2 et
fini en 4 est :" + [Link](2,5));

A noter que les deux appels utilisent le même nom de méthode mais avec
des paramètres différents ! En réalité, ces deux appels invoquent deux
méthodes différentes. D’ailleurs, dans l’extrait suivant de la documentation,
la méthode substring() apparaît deux fois.

String substring(int beginIndex)


Returns a string that is a substring of
this string.

String substring(int beginIndex,


int endIndex)

99
Returns a string that is a substring of
this string.
Ces deux méthodes de même nom désignent en réalité deux méthodes
différentes. Le compilateur peut distinguer les méthodes de même nom par
le fait que la nature des paramètres est différente. Dans
substring(int beginIndex), il n’y a qu’un paramètre de type int. Cette
méthode retourne un objet String dont la chaîne est la sous-chaîne qui
débute à la position beginIndex et termine au dernier caractère du String.
Dans substring(int beginIndex, int endIndex), il y a deux
paramètres et la méthode retourne un objet String qui débute à la position
beginIndex et se termine à la position endIndex. Ce sont bien deux
méthodes différentes mais apparentées.

Surcharge d’un nom de méthode

Le fait d’employer le même nom pour désigner plusieurs méthodes


différentes distinguées par la nature des paramètres est appelé la surcharge des
noms de méthodes.

Ensuite, l’appel [Link](string2) retourne la position de la première


occurrence de la chaîne de string1 dans string2.
[Link]("La première occurrence de string2 dans
string1 est à la position :" + [Link](string2));
Enfin, l’appel [Link](string2) retourne un objet String formé de la
concaténation de la chaîne de string2 à la fin de celle de string1.
[Link]("La concaténation de string2 à string1 donne
:" + [Link](string2));

Exercice. Lire un String, en afficher la taille, et compter le nombre


d’occurrences de la lettre «a» dans le String.

• Littéral String

À cause de l’importance de la manipulation de chaînes de caractères, Java


prévoit un raccourci pour la création d’objets de la classe String. Plutôt que
d’utiliser un constructeur pour créer un objet de la classe String, Java

100
permet d’utiliser un littéral String. Un littéral String est une séquence de
caractères entre guillemets.

Le littéral est en fait un raccourci pour désigner un objet de la classe String.


Par exemple, il est permis d’écrire le code suivant :
String string1 = "abc";
Cette utilisation de littéraux donne l’illusion que String est un type primitif.
Mais cette vue n’est qu’une partie du portrait car un littéral String désigne
un objet. Cet énoncé assigne un objet de la classe String qui contient la
suite de caractères "abc" à la variable string1. Ceci produit presque le même
effet que :
String string1 = new String("abc");
L’effet de ces deux énoncés n’est cependant pas exactement le même mais
dans la majorité des cas, la différence n’est pas importante. L’exemple suivant
illustre la différence.

Exemple. JavaPasAPas/chapitre_3/[Link]

L’exemple de programme suivant illustre quelques subtilités concernant


l’usage de littéraux String. Le principe important à saisir est le suivant :
l’utilisation d’un littéral qui est connu à la compilation du programme est
toujours remplacé par une référence à un objet de la classe String qui
contient la chaîne de caractère du littéral. S’il y a plusieurs occurrences du
même littéral, disons "abc", c’est toujours le même objet qui est utilisé. Ceci
permet une certaine économie de mémoire car il n’est pas nécessaire de créer
un objet différent à chaque utilisation du même littéral. Cependant, si un
objet est créé à l’exécution (par new par exemple), ce ne sera pas le même
objet !

101
public class ExemplesString{

public static void main (String args[]) {

String string1 = "abc";


String string2 = "def";
String string3 = "abcdef";
String string4 = new String("abcdef");

// Tous les littéraux identiques (à la compilation) sont traduits


// par une référence au même objet
[Link](string3 == "abcdef"); // true
[Link]("abc"+"def" == "abcdef"); //true

// Cependant, si le littéral est calculé à l'exécution, ce n'est pas le cas


[Link](string1 + string2 == "abcdef"); //false

// Le constructeur String produit toujours un objet différent de l'objet


// correspondant au littéral
[Link](string4 == "abcdef"); //false

// La méthode intern() de la classe String permet de convertir


// la référence à l'objet correspondant au littéral
[Link]((string1 + string2).intern() == "abcdef"); //true
[Link]([Link]() == "abcdef"); //true

// La méthode equals() permet de comparer le contenu de l'objet plutôt que la référence


[Link]((string1 + string2).equals("abcdef")); //true
[Link]([Link]("abcdef")); //true
}
}

La condition dans
[Link](string3 == "abcdef"); // true
retourne true car string3 fait référence au même objet que "abcdef".

La condition dans
[Link]("abc"+"def" == "abcdef"); //true
retourne aussi true car la concaténation "abc"+"def" est calculée au moment
de la compilation, ce qui produit le littéral "abcdef".

La condition dans
[Link](string1 + string2 == "abcdef"); //false
retourne false car la concaténation string1 + string2 ne peut être calculée à la
compilation. Donc, l’objet créé ne sera pas le même que l’objet
correspondant au littéral "abcdef".

La condition

102
[Link](string4 == "abcdef"); //false
retourne false car le constructeur String() produit toujours un objet distinct et
donc différent de l’objet correspondant au littéral.

Les deux conditions dans


[Link]((string1 + string2).intern() ==
"abcdef"); //true
[Link]([Link]() == "abcdef"); //true
retournent true car la méthode intern() convertit la référence à une référence
à l’objet correspondant au littéral "abcdef".

Les deux conditions dans


[Link]((string1 + string2).equals("abcdef"));
//true
[Link]([Link]("abcdef")); //true
retournent true car ce n’est pas la référence à l’objet qui est comparée mais
bien le contenu, c’est-à-dire la chaîne de caractère elle-même. La morale de
cet exemple : il est habituellement préférable de comparer les String avec
equals().

Un autre aspect qui porte souvent à confusion pour un novice est la


distinction entre

• le littéral qui représente la chaîne vide ""

• le littéral null

Exemple. JavaPasAPas/chapitre_3/[Link]

L’exemple suivant illustre la différence entre ces concepts.


public class ExemplesStringVide{

public static void main (String args[]) {

String string1 = "";


String string2 = new String("");
String string3 = new String();
String string4 = null;

[Link](string1 == ""); // true


[Link]([Link]("")); //true
[Link](string1 == null); //false

[Link](string2 == ""); // false

103
[Link]([Link]("")); //true
[Link](string2 == null); //false

[Link](string3 == ""); // false


[Link]([Link]("")); //true
[Link](string3 == null); //false

[Link](string4 == ""); //false


// [Link]([Link]("")); provoquerait une exception à l'exécution
[Link](string4 == null); //true
}
}
La figure suivante montre l’effet du programme.

objet de la classe String correspondant au litéral ""


string1
""
string2

string3 objet de la classe String

string4 null ""

objet de la classe String

""

String1 fait référence à l’objet String qui correspond au littéral de la chaîne


vide "". String2 fait référence à un autre objet qui contient aussi la chaîne vide.
Il en est de même pour string3. Ceci signifie que le constructeur de String sans
paramètre String() initialise automatiquement son contenu à la chaîne vide.
Enfin, string4 contient la référence null. Le littéral spécial null signifie que la
variable ne fait référence à aucun objet. L’expression string4==null permet de
détecter cette situation. Lorsque l’objet fait référence à null, l’accès à son
contenu provoque une exception Java à l’exécution. Ce serait le cas de
l’énoncé suivant car la méthode equals() doit extraire le contenu de l’objet
string4 mais cet objet n’existe pas !
// [Link]([Link]("")); provoquerait une
exception à l'exécution

Enfin, notons que l’accès à une variable non initialisée provoque une erreur
de compilation. Java vous protège contre l’accès aux variables non initialisée
parce que de tels accès sont souvent la source de problèmes et de bogues.

104
Exemple. JavaPasAPas/chapitre_3/[Link]

En Java, nous distinguons la déclaration d’une variable (sans initialisation)


et son initialisation (ou attribution de valeur). Avant de pouvoir utiliser une
variable, celle-ci doit avoir été initialisée. Au sein d’une fonction, la simple
déclaration d’une variable ne suffit par à l’initialiser et Java refusera l’accès à
une variable non-initialisée.
public class ExempleStringNonInitialise{
public static void main (String args[]) {
String unString;
[Link](unString == null); // erreur de compilation car non initialisé
}
}

Exemple. JavaPasAPas/chapitre_3/[Link]

Java représente ses caractères sous le format UTF-16. La notion de caractère


au sein d’une chaîne de caractères en Java présume que tous les caractères
occupent 16 bits. Or les caractères des plans supplémentaires (comme les
emojis) occupent 32 bits. Le code suivant va donc afficher 8 pour la longueur
de la chaîne et va retourner un caractère invalide après l’appel à charAt. Le
traitement des chaînes de caractères dans de tels cas peut se faire avec les
méthodes codePointAt et offsetByCodePoints. Il s’agit d’un sujet avancé.

public class ExempleEmoji {


public static void main(String[] args) {
String s = "😂😍🎉👍";
[Link]([Link]());
[Link]([Link](1));
}
}

3.10 Fonctions mathématiques : [Link]


Au-delà des opérations de base permises dans les expressions arithmétiques,
le package [Link] contient plusieurs méthodes pour le calcul de
fonctions mathématiques courantes.

Exemple. JavaPasAPas/chapitre_3/[Link]

105
L’exemple suivant montre quelques exemples de fonctions mathématiques
usuelles.
public class ExemplesMath {

public static void main (String args[]) {


[Link]("[Link](1.0)="+[Link](1.0));
[Link]("[Link](1.0)="+[Link](1.0));
[Link]("[Link](0)="+[Link](0));
[Link]("[Link](0)="+[Link](0));
[Link]("[Link](4)="+[Link](4));
}
}

Résultat affiché :
[Link](1.0)=0.0
[Link](1.0)=2.718281828459045
[Link](0)=1.0
[Link](0)=0.0
[Link](4)=2.0

Les classes [Link] (pour les entiers) et [Link] (pour


les nombres décimaux) permettent de traiter des nombres d’une précision
plus grande que ce qui est permis avec les types primitifs.

3.11 Sommaire des opérations et priorités


Le tableau suivant montre la liste des opérations pour les expressions Java
en ordre décroissant de priorité.
Opérateur
++ Post-incrémentation
-- Post-décrémentation
++ Pré-incrémentation
-- Pré-décrémentation
+ + unaire
- - unaire
! négation logique
~ complément (niveau bit)
(type) conversion de type
* Multiplication binaire
/ Division
% Reste après division entière
+ Addition binaire
- Soustraction binaire
<< Décalage à gauche (niveau bit)
>> Décalage à droite (niveau bit)

106
>>> Décalage à droite sans signe (niveau bit)
< Plus petit que
> Plus grand que
<= Plus petit ou égal à
>= Plus grand ou égal à
instanceof Est une instance de
== Est égal à
!= Est différent de
& Et (niveau bit / logique)
^ Ou exclusif (niveau bit / logique)
| Ou inclusif (niveau bit / logique)
&& Et logique
|| Ou logique
?: Expression conditionnelle
= Affectation
+= Auto-addition
-= Auto-soustraction
/= Auto-division
%= Auto-reste
^= Auto-ou-exclusif (niveau bit)
|= Auto-ou (niveau bit)
<<= Auto-décalage à gauche (niveau bit)
>>= Auto-décalage à droite (niveau bit)
>>>= Auto-décalage à droite sans signe (niveau bit)

107

Vous aimerez peut-être aussi