Liste de  cours java

Support de cours Java : les classes avec exemples


Télécharger



Support de cours Java : les classes avec exemples

...

2.1 LES MODULES, LES CLASSES, L’ENCAPSULATION

La notion de module existe dans la plupart des langages non-objet. La définition générale d’un module est "une unité faisant partie d’un ensemble".

2.1.1 La notion de module et de



type abstrait de données (TAD)

En programmation non-objet (Pascal, C), un module est un ensemble de fonctions traitant des données communes. Les éléments (constantes, variables, types, fonc¬tions) déclarés dans la partie interface sont accessibles de l’extérieur du module, et sont utilisables dans un autre programme (un autre module ou un programme prin¬cipal). Il suffit de référencer le module pour avoir accès aux éléments de sa partie interface. Celle-ci doit être la plus réduite possible, tout en donnant au futur utilisa¬teur un large éventail de possibilités d’utilisation du module. Les déclarations de variables doivent être évitées au maximum. On peut toujours définir une variable locale au module à laquelle on accède ou que l’on modifie par des appels de fonc¬tions de l’interface.

On parle alors d’encapsulation des données qui sont invisibles pour l’utilisateur du module et seulement accessibles à travers un jeu de fonctions. L’utilisateur du module n’a pas besoin de savoir comment sont mémorisées les données ; le module est pour lui un type abstrait de données (TAD). Du reste, cette mémorisation locale peut évoluer, elle n’affectera pas les programmes des utilisateurs du module dès lors que les prototypes des fonctions d’interface restent inchangés.

En langage C, module.h constitue le fichier d’en-tête à inclure dans chaque fichier référençant des fonctions du module. Le corps du module est défini dans module.c.

...

Figure 2.1 — La notion de module séparant ce qui est accessible de l’extérieur du module

de ce qui est local au module, aussi bien pour les données que pour les fonctions.

Un exemple classique est celui de la pile. S’il n’y a qu’une pile à gérer, celle-ci peut être encapsulée dans les données locales (dans pile.c) ; la déclaration du fichier d’en¬tête pourrait être la suivante en langage C.

// pile.h version en langage C gérant une seule pile d’entiers

#ifndef PILE_H #define PILE_H

void initPile (int max); // initialiser la pile

int pileVide (); // la pile est-elle vide ?

void empiler (int valeur); // empiler une valeur

int depiler (int* valeur); // dépiler à l’adresse de valeur

void viderPile (); // vider la pile

void listerPile (); // lister les éléments de la pile

#endif

Si on veut gérer plusieurs piles, les données ne peuvent plus être dans la partie "données locales" du module. Celles-ci doivent être déclarées dans le programme appelant et passées en paramètres des fonctions du module gérant la pile. Le fichier d’en-tête pourrait s’écrire comme indiqué ci-dessous. La déclaration du type Pile doit être faite dans l’interface ; par contre les variables max, sommet et element ne devraient pas être accessibles de l’extérieur du module ce qui n’est pas le cas sur l’exemple suivant où le pointeur de pile donne accès aux composantes de la structure à partir du programme appelant.

// pile.h version en langage C gérant plusieurs piles d’entiers

#ifndef PILE_H #define PILE_H

typedef struct {

int max; // nombre max d’éléments dans la pile

int sommet; // repère le dernier occupé de element

int* element; // tableau d’entiers alloués dynamiquement } Pile;

void initPile (Pile* p, int max);

int pileVide (Pile* p);

void empiler (Pile* p, int valeur);

int depiler (Pile* p, int* valeur);

void viderPile (Pile* p); void listerPile (Pile* p);

#endif

2.1.2 La notion de classe

Une classe est une extension de la notion de module. Les données et les fonctions traitant les données sont réunies ensemble dans une classe qui constitue un nouveau type. On peut déclarer des variables du type de cette classe. Cela revient à avoir la possibilité de dupliquer les modules en leur attribuant des noms différents, chaque module ayant ses propres variables locales visibles ou non de l’extérieur.

L’exemple de la pile peut être schématisé comme indiqué sur la figure 2.2 qui suit les conventions de la modélisation objet UML (Unified Modeling Language). On distingue les données de la classe appelées attributs en PO (Programmation Objet) et les fonctions appelées méthodes. Un signe moins(-) devant un élément indique un élément privé, visible seulement dans la classe ; un signe plus(+) indique un élément public, accessible par tous les utilisateurs de la classe ; l’absence de signe indique une visibilité (voir page 67) seulement pour les classes du paquetage (du répertoire). Les attributs sommet et element, et la méthode erreur() sont privés ; les autres méthodes sont publiques.

...

La classe est un modèle qui s’apparente à la déclaration d’un nouveau type de données. On peut déclarer une variable (on parle d’objet ou d’instance de la classe en PO) du type de cette classe.

Exemple :

Pile p1 = new Pile (5);// p1 et p2 sont des instances de la classe Pile

Pile p2 = new Pile (100); // p1 et p2 sont des objets de type Pile

...

Chaque objet possède ses propres données (attributs). Les données en grisé sur la figure 2.3 sont encapsulées dans leur objet. La déclaration des objets p1 et p2 auto¬rise seulement l’utilisation des méthodes de l’objet. Aucun accès direct (en dehors des méthodes de l’objet) n’est possible à sommet ou element de l’objet. On parle aussi d’envoi de messages à l’objet : chaque méthode s’adresse à l’objet pour qu’il modifie certaines de ses données ou fournisse de l’information sur son état : message p1.empiler(3) ; message p2.viderPile() ; etc.

D’autres structures de données seraient envisageables pour mémoriser la pile. Si l’interface est respectée, un changement de structures de données dans la classe Pile n’affectera pas les programmes utilisateurs qui n’ont pas accès aux données privées. On parle aussi d’un contrat passé entre la classe et ses utilisateurs, les clauses du contrat étant définies par les méthodes publiques.

2.1.3 La classe Pile

Le codage en Java de la classe Pile est indiqué ci-après. Le mot clé "private" indique que les attributs sommet et element, et la méthode erreur() sont privés.

// Pile.java gestion d’une pile d’entiers

public class Pile {

// sommet et element sont des attributs privés

private int sommet; // repère le dernier occupé (le sommet)

private int[] element; // tableau d’entiers alloué dynamiquement

// erreur est une méthode privée utilisable seulement

// dans la classe Pile

private void erreur (String mes) {

System.out.println ("***erreur : " + mes);

}

public Pile (int max) { // voir 2.2.1 Le constructeur d’un objet

sommet = -1;

element = new int [max]; // allocation de max entiers

}

...

2.1.4 La mise en œuvre de la classe Pile

Dans le programme PPPile ci-dessous, on crée l’objet pile1 de type Pile sur lequel on applique, en fonction des réponses au menu de l’utilisateur, des méthodes de la classe Pile. Les données sont encapsulées. L’accès à l’entier sommet de la Pile pile1 serait autorisé si sommet était déclaré public dans la classe Pile ; dans ce cas pile1.sommet serait valide dans PPPile.java pour accéder à la variable sommet de la pile. En vertu du principe d’encapsulation, il est préférable que cet accès soit interdit.

...

2.2 LA NOTION DE CONSTRUCTEUR D’UN OBJET

Un objet est une instance, un exemplaire construit dynamiquement sur le modèle que décrit la classe.

2.2.1 Le constructeur d’un objet

Dans l’exemple précédent de la pile, la méthode suivante est un peu particulière.

public Pile (int max) { sommet = -1;

element = new int Cmax]; // allocation de max entiers

}

Elle porte seulement le nom de la classe en cours de définition. Cette méthode est un constructeur chargé de construire l’objet (allouer de la mémoire et initialiser les attributs de l’objet). Ce constructeur n’est jamais appelé directement ; il est pris en compte lors de la demande de création de l’objet avec new :

Pile pile1 = new Pile (5) ;

L’attribut sommet de l’objet pile1 est initialisé à -1, et un tableau de 5 entiers repéré par la référence element est alloué. pile1 est une référence sur l’objet (son adresse). L’objet contient lui-même une référence element sur un tableau d’entiers.

Figure 2.4 — La construction dynamique d’un objet de type Pile avec Pile pile1 = new Pile (5).

2.2.2 L’initialisation d’un attribut d’un objet

L’initialisation d’un attribut d’un objet est faite :

  • d’abord par défaut lors de la création de l’attribut. Pour les types primitifs, l’attribut est initialisé à 0 pour les entiers, les réels et les caractères et à faux pour les booléens. Un attribut d’un objet peut être une référence sur un autre objet (ou un tableau) comme sur la figure 2.4 ; dans ce cas, la référence est initialisée à null (adresse inconnue) : l’objet devra être créé par la suite, éventuellement dans le constructeur à l’aide de new().
  • en utilisant la valeur indiquée lors de sa déclaration comme attribut :

class Pile {

private int sommet = -1; // repère le dernier occupé (le sommet)

...

initialise l’attribut sommet à -1 lors de sa déclaration.

  • dans le constructeur de l’objet : le constructeur Pile (int max) initialise sommet à -1 ; il initialise la référence element en créant un tableau d’entiers.

Remarque : il n’y a pas à désallouer la mémoire en Java. Cela est fait automa¬tiquement par un processus ramasse-miettes (garbage collector) qui récupère la mémoire des objets qui ne sont plus référencés. En C, on désalloue avec la fonction free() et en C++, on peut définir un destructeur pour une classe.

2.3 LES ATTRIBUTS STATIC

2.3.1 La classe Ecran

On veut simuler un écran graphique comportant un certain nombre de lignes et de colonnes. Chaque point de l’écran contient un caractère correspondant à un pixel. On dessine sur cet écran à l’aide d’un jeu de fonctions définies comme suit :

  • Ecran (int nbLig, int nbCol) : crée un écran de nbLig lignes sur nbCol colonnes.
  • effacerEcran () : efface l’écran pour un nouveau dessin.
  • crayonEn (int numLigCrayon, int numColCrayon) : positionne le crayon sur une ligne et une colonne.
  • changerCouleurCrayon (int couleurCrayon) : change la couleur du crayon.
  • ecrirePixel (int nl, int nc) : écrit un pixel avec la couleur du crayon (en fait ici, un caractère) pour un numéro de ligne et de colonne donnés en (nl, nc).
  • ecrireMessage (int nl, int nc, String mes) : écrit le message mes en (nl, nc).
  • tracerCadre () : trace un cadre autour de l’écran.
  • avancer (int d, int n) : avance le crayon dans une direction d donnée (gauche, haut, droite, bas) d’un nombre de pas n déterminé.
  • afficherEcra () : affiche le contenu de l’écran (les caractères de l’écran).
  • rectangle (int xCSG, int yCSG, int xCID, int yCID) : trace un rectangle à partir des coordonnées CSG (coin supérieur gauche) et CID (coin inférieur droit).
  • spirale1 (int n, int max) : trace une spirale en partant de l’intérieur de la spirale.
  • spirale2 (int n, int d) : trace une spirale en partant de l’extérieur de la spirale.

L’écran graphique est schématisé sur la figure 2.5.

...

2.3.2 L’implémentation de la classe Ecran

Les données étant encapsulées dans la classe Ecran, le choix des structures de données est libre pour le concepteur de la classe Ecran. Cela n’a pas d’incidence pour les utilisateurs de la classe qui n’ont accès qu’aux méthodes (voir figure 2.6).

Les attributs privés suivants sont définis lors de l’implémentation de la classe Ecran :

  • nbLig, nbCol : nombre de lignes et de colonnes de l’écran.
  • numLigCrayon, numColCrayon : numéro de la ligne et de la colonne où se trouve le crayon.
  • couleurCrayon : numéro (de 0 à 15) de la couleur du crayon. On affiche en fait sur l’écran un caractère dépendant de la couleur du crayon.
  • zEcran (zone de l’écran) est une référence sur un tableau de caractères à deux dimensions alloué dynamiquement dans le constructeur de la classe Ecran ; nbLig et nbCol sont initialisés dans ce constructeur.

...

2.3.3 La classe Ecran en Java : le rôle de this

L’absence du mot-clé public devant la déclaration de la classe Ecran et devant chacune des méthodes de cette classe implique en fait une visibilité des méthodes s’étendant au paquetage, soit au répertoire contenant Ecran. Cela indique que le programme principal et la classe Ecran doivent être dans le même répertoire (voir page 67). Une constante symbolique se déclare avec final (voir page 8). Le mot clé "static" est présenté dans le paragraphe suivant.

// Ecran.java simulation d’un écran graphique

class Ecran {

// constantes symboliques pour les couleurs noir et blanc

// les autres couleurs pourraient aussi être définies

static final int NOIR = 0; static final int BLANC = 15;

// constantes symboliques pour les directions static final int GAUCHE = 1;

static final int HAUT = 2;

static final int DROITE = 3;

static final int BAS = 4;

// les attributs (données) de l’objet sont privés

private int nbLig; // nombre de lignes de l’écran

private int nbCol; // nombre de colonnes de l’écran

private int numLigCrayon; // numéro de ligne du crayon

private int numColCrayon; // numéro de colonne du crayon

private int couleurCrayon; // couleur du crayon

private char[][] zEcran; // référence sur le tableau à 2D

Le constructeur Ecran doit allouer et initialiser les attributs (données) privés de l’objet courant. Cette initalisation se fait souvent en donnant au paramètre le même nom que l’attribut qu’il doit initialiser. Ainsi, le constructeur Ecran a deux paramètres nbLig et nbCol (nombre de lignes et de colonnes) qui servent à initialiser les attributs privés nbLig et nbCol. Par convention, this désigne l’objet courant (c’est une réfé¬rence sur l’objet courant) et il permet de lever l’ambiguïté de nom : this.nbLig désigne l’attribut alors que nbLig seul désigne le paramètre. Le principe s’applique également lorsqu’on doit changer la valeur d’un attribut comme dans les méthodes crayonEn() ou changerCouleurCrayon() ci-après.

// tableau de caractères zEcran alloué dynamiquement // dans le constructeur Ecran()

Ecran (int nbLig, int nbCol) { // nbLig désigne le paramètre

this.nbLig = nbLig ; // this.nbLig désigne l’attribut

this.nbCol = nbCol ;

zEcran = new char [nbLig][nbCol]; // référence sur la zone écran

changerCouleurCrayon (NOIR); // par défaut crayon noir

crayonEn (nbLig/2, nbCol/2); // au milieu de l’écran

effacerEcran();

}

// mettre l’écran à blanc

void effacerEcran () { // voir figure 2.5

for (int i=0; i < nbLig; i++) {

for (int j=0; j < nbCol; j++) zEcran [i][j] = ' ';

}

}

// le crayon est mis en numLigCrayon, numColCrayon void crayonEn (int numLigCrayon, int numColCrayon) { this.numLigCrayon = numLigCrayon ;

this.numColCrayon = numColCrayon ;

}

// la couleur du crayon est couleurCrayon de 0 à 15

void changerCouleurCrayon (int couleurCrayon) {

if ( (couleurCrayon >= 0) && (couleurCrayon <= 15) ) {

this.couleurCrayon = couleurCrayon ;

}

}

 Les fonctions suivantes sont les fonctions élémentaires dessinant un pixel (en fait un caractère dans notre simulation), écrivant un message sous forme d’une chaîne de caractères, ou traçant un cadre entourant l’écran. La fonction avancer() avance suivant une direction donnée en laissant une trace de la couleur courante du crayon (en fait écrit des caractères correspondant à la couleur courante). Les dessins n’apparaissent qu’à la demande lors de l’appel de afficherEcran().

// écrire un caractère en (nl, nc) en fonction de couleurCrayon void ecrirePixel (int nl, int nc) {

 String couleurs = "*123456789ABCDE."; // 16 couleurs de 0 à 15

if ( (nl >= 0) && (nl < nbLig) && (nc >= 0) && (nc < nbCol) ) { zEcran [nl][nc] = couleurs.charAt (couleurCrayon);

// écrire le message mes en (nl, nc)

void ecrireMessage (int nl, int nc, String mes) {

for (int i=0; i < mes.length(); i++) {

if ( (nl >= 0) && (nl < nbLig) && (nc >= 0) && (nc < nbCol) ) {

 zEcran [nl][nc] = mes.charAt (i); // voir String page 80

nc++;

// tracer un cadre autour de l’écran

void tracerCadre () {

for (int nc=0; nc < nbCol; nc++) {

zEcran [0][nc] = '-';// le haut de l’écran

zEcran [nbLig-1][nc] = '-'; // le bas de l’écran

1

for (int nl=0; nl < nbLig; nl++) {

zEcran [nl][0] = '|'; // le côté gauche

zEcran [nl][nbCol-1] = '|'; // le côté droit

// avancer dans la direction d de n caractères (pixels)

void avancer (int d, int n) {

switch (d) {

case DROITE :

for (int i=numColCrayon; i < numColCrayon+n; i++) {

ecrirePixel (numLigCrayon, i);

1

numColCrayon += n-1;

break;

case HAUT :

for (int i=numLigCrayon; i > numLigCrayon-n; i--) {

ecrirePixel (i, numColCrayon);

numLigCrayon += -n+1;

break;

case GAUCHE :

for (int i=numColCrayon; i > numColCrayon-n; i--) {

ecrirePixel (numLigCrayon, i);

}

numColCrayon += -n+1;

break;

case BAS :

for (int i=numLigCrayon; i < numLigCrayon+n; i++) {

ecrirePixel (i, numColCrayon);

}

numLigCrayon += n-1;

break;

}

}

void afficherEcran () {

for (int i=0; i < nbLig; i++) {

for (int j=0; j < nbCol; j++) {

System.out.print (zEcran [i][j]);

}

System.out.print ("\n");

}

System.out.print ("\n");

}

Les méthodes suivantes dessinent des figures géométriques sur un objet de la classe Ecran en utilisant les fonctions élémentaires vues ci-dessus. On peut dessiner un rectangle, ou une spirale en partant du plus petit côté intérieur ou en partant du grand côté extérieur.

// tracer un rectangle défini par 2 points CSG et CID

// CSG : coin supérieur gauche; CID : coin inférieur droit void rectangle (int xCSG, int yCSG, int xCID, int yCID) {

int longueur = xCID-xCSG+1;

int largeur = yCID-yCSG+1;

crayonEn (yCSG, xCSG);

avancer (BAS, largeur); avancer (DROITE, longueur); avancer (HAUT, largeur); avancer (GAUCHE, longueur);

}

// tracer une spirale en partant du centre de la spirale // et en se déplacant de n pas dans la direction courante. // augmenter n, et tourner à gauche de 90˚.

// On continue tant que n est < à max

void spirale1 (int n, int max) { // récursif

if (n < max) {

avancer (DROITE, n);

avancer (HAUT , n+1); avancer (GAUCHE, n+2); avancer (BAS, n+3);

spirale1 (n+4, max);

}

}

// tracer une spirale en partant du plus grand côté // extérieur de la spirale, et en décrémentant n // tant que n est supérieur à 0.

void spirale2 (int n, int d) { // récursif

if (n >= 1) {

avancer (d, n); // avancer dans la direction d de n pixels

spirale2 (n-1, 1 + d%4 ); // d modulo 4 soit 0, 1, 2 ou 3

}

}

} // classe Ecran

2.3.4 Les attributs static (les constantes static final)

Le programme suivant utilise la classe Ecran définie précédemment pour effectuer des dessins sur deux objets différents ecran1 et ecran2. Chaque objet dispose de ses propres données (attributs) privées.

...

Certains attributs peuvent être communs à tous les objets de la classe et exister indépendamment de tout objet de la classe. Ces attributs sont déclarés static. On peut trouver des variables static et des constantes static. Les constantes (NOIR, BLANC, GAUCHE, HAUT, DROITE, BAS) n’ont pas besoin d’être dupliquées dans chaque objet de la classe. Sur la figure 2.9, les constantes (car déclarées final) sont static donc existent indépendamment de tout objet. Ces constantes ne sont pas déclarées public ; elles sont visibles dans le paquetage (voir page 67). Elles pour¬raient être déclarées private, et visibles seulement dans les méthodes de l’objet.

...

La déclaration static indique que ces constantes sont spécifiques de la classe Ecran mais non spécifiques d’un objet particulier (comme ecran1 ou ecran2). On appelle ces variables des attributs de classe. Dans les méthodes de la classe, elles sont directe¬ment référencées en indiquant leur nom (voir l’utilisation de GAUCHE, DROITE, etc., dans la méthode avancer() de la classe Ecran). A l’extérieur de la classe, elles sont référencées en les préfixant du nom de la classe (voir ci-dessous dans la classe PPEcran : Ecran.HAUT par exemple signifie la variable (constante) static HAUT de la classe Ecran).

2.3.5 La mise en œuvre de la classe Ecran

La mise en œuvre de la classe consiste simplement en une suite d’appels des méthodes disponibles. L’utilisateur de la classe ne connaît pas les structures de données utilisées pour l’implémentation.

8