1
TP de Java
Classes et Objets
_____________________
Sujets abordés dans ce TP :
Introduction P.O.O en Java
Premières notions d’héritage
Règles d’écriture des données membres et des
méthodes
Communication entre classes
Utilisation de package
Algorithmes de parcours d’arbres binaires
1) Introduction
Dans ce TP nous vous proposons d’approfondir la P.O.O (Programmation Orientée Objets) en
Java. On rappelle que la programmation Java est basée sur la manipulation d’objets composés de
données membres et de méthodes. Les objets sont des instances de classes. La classe correspond à
la généralisation de type, c’est une description d’un ensemble d’objets ayant une structure de
données commune et disposant des mêmes méthodes.
2) Introduction P.O.O en Java
Déclaration de classes, allocation d’objets, encapsulation de données membres
Implémenter la classe suivante :
class Point {
private int x,y;char l;
private void check()
{if(x<0) x=0; if(y<0) y=0;}
void move(int dx, int dy)
{x+=dx; y+=dy; check();}
void set(int xi,int yi)
{x = xi; y= yi; check();}
int getX() {return x;}
int getY() {return y;}
void print()
{System.out.println(x+";"+y+":"+l);}
}
Mettre en oeuvre cette classe de la façon suivante:
Point pt = new Point();
pt.print();
pt.l = 'o';
pt.set(5,2);
pt.print();
pt.move(-6,4);
pt.print();
A travers ce court exemple vous avez vu les principes de base de la P.P.O avec : la classe Point,
ses différentes méthodes, ses données membres x y et l (un label), et l’objet pt instance de la
classe Point, alloué suite à l’appel de l’opérateur new.
Les méthodes et données membres des objets sont accessibles par l’utilisation de l’opérateur point
., comme par exemple pt.print() et pt.l.
Cet exemple met en œuvre un principe important de la P.O.O. : l’encapsulation des données
membres. En effet, les données membres x et y ont été déclarées à l’aide du mot clé private. Ceci
signifie qu’elles sont accessibles uniquement par les méthodes de classe Point. Il en est de même
pour la méthode check(). La donnée membre l est par contre accessible de l’extérieur, comme le
montre l’appel pt.l.
Remarque : Il existe en fait quatre déclarations de droit d’accès : private, pas de déclaration,
protected, et public. Les différences entre ces déclarations seront abordées dans les TP suivants.
Constructeurs
Au cours de l’exemple précédent, vous avez eu recours à l’opérateur new et le constructeur Point()
pour allouer l’objet pt instance de la classe Point. Vous avez utilisé ici le constructeur par défaut.
Rajoutez ce bloc de code à la définition de votre classe Point :
Point() {}
Vous venez ici de redéfinir le constructeur par défaut de la classe Point. En l’absence de
constructeur défini dans la classe Point, ce constructeur est en fait déclaré implicitement. Mettre
en œuvre la classe Point de la façon suivante :
Point pt;
pt = new Point();
pt.print();
System.out.println(new Point());
new Point().print();
2
A travers ce court exemple vous avez vu la mise en œuvre du constructeur Point() via l’utilisation
de l’opérateur new. La commande new Point() alloue un objet de type Point et retourne sa
référence. Cette référence peut être ensuite stockée dans une variable de type Point (ici pt). Le
stockage de cette référence peut également être ignorée, et l’objet utilisé « à usage unique » à un
instant donné du programme comme le montre l’exemple de code : new Point().print();
Redéfinir votre classe de la façon suivante :
class Point
{
private int x,y;
private void check()
{if(x<0) x=0; if(y<0) y=0;}
Point(int v)
{x = y = v; check();}
Point(int xi, int yi)
{x = xi; y= yi; check();}
void move(int dx, int dy)
{x+=dx; y+=dy; check();}
void set(int xi,int yi)
{x = xi; y= yi; check();}
int getX() {return x;}
int getY() {return y;}
void print()
{System.out.println(x+";"+y);}
}
Mettre en oeuvre cette classe de la façon suivante:
Point pt;
pt = new Point(4);
pt.print();
pt = new Point(4,5);
pt.print();
Vous avez ici redéfini deux constructeurs de la classe Point. Que concluez-vous sur l’utilisation
du constructeur par défaut dans ce cas ? pour la suite du TP, re-implémenter le constructeur
Point() avec les constructeur Point(int), et Point(int,int).
Affectation, passage par argument, et comparaison d’objets
Comme nous l’avons expliqué lors de notre partie sur les constructeurs, la manipulation des objets
en Java se fait par l’intermédiaire de références. Implémenter et tester le code suivant basé sur
l’utilisation d’objets de type Point. Que concluez-vous sur l’affectation et le passage par référence
d’objets.
void ex(Point pt) {
pt.set(1,1);
}
void test() {
Point pt1, pt2;
pt1 = new Point();
pt2 = pt1;
pt1.set(2,3);
pt2.print();
ex(pt2);
pt1.print();
}
Faîtes de même pour le code suivant mettant en œuvre la comparaison d’objets :
Point pt1 = new Point();
Point pt2 = new Point();
System.out.println(pt1 == pt2);
System.out.println(pt1 != pt2);
System.out.println(pt1 == pt1);
3) Premières notions d’héritage
Mise en œuvre de l’héritage
Basé sur la classe Point précédente, définir la classe LPoint (labelled Point) de la façon suivante:
class LPoint extends Point {
private char l;
LPoint() {}
LPoint(int v, char li)
{super(v); l = li;}
LPoint(int xi, int yi, char li)
3
{super(xi,yi); l = li;}
void setL(int xi,int yi,char li)
{set(xi,yi); l=li;}
char getL() {return l;}
void printL()
{System.out.println(getX()+";"+getY()+":"+l);}
}
Mettre en oeuvre cette classe de la façon suivante :
LPoint pt = new LPoint();
pt.printL();
pt.setL(5,2,'b'); pt.printL();
pt.move(-6,4); pt.printL();
Vous avez défini ici votre première relation d’héritage entre la classe LPoint et Point à l’aide du
mot clé extends. A travers cette nouvelle classe, vous avez encapsulé et formalisé l’utilisation de la
donnée membre l (le label du point).
Dans la classe LPoint vous n’avez plus accès aux données membres x et y de l’objet Point. En
effet, une classe dérivée n’a pas accès aux données privées de sa classe mère. Vous n’avez
également plus accès à la méthode check() privée de la classe mère. Vous avez accès cependant à
l’ensemble des méthodes non déclarées private, comme la méthode move() par exemple.
En Java, un objet dérivé doit impérativement prendre en charge la construction de l’objet père.
Cette prise en charge est assurée par l’appel des constructeurs de l’objet père via le mot clé super.
Cette instruction est toujours la première exécutée dans le constructeur d’une classe dérivée.
Commenter les appels des constructeurs de l’objet père via le mot clé super. Vous pouvez pour
cela baliser vos constructeurs avec des fonctions d’affichage de la manière suivante :
LPoint() { System.out.println("1");}
Redéfinir la méthode printL() de la classe LPoint de la façon suivante, commenter.
super.print();
System.out.println(":"+l);
La classe racine Object
Mettre en oeuvre la classe Point de la façon suivante:
Point pt = new Point();
System.out.println(pt.toString());
Vous avez fait appel ici à une méthode toString() que vous n’avez pas définie dans votre classe
Point. Cette méthode est en fait une méthode de la classe racine Object. En effet, toute classe en
Java hérite implicitement de cette classe. Vous avez donc accès à toutes les méthodes de la classe
Object quelque soit l’objet que vous créez.
Aller consulter dans l’API specification la documentation de la classe Object afin de commenter
l’action de cette ligne de code.
System.out.println(pt.getClass().getName());
Garbage collector
La notion de destructeur n’existe pas en Java. La démarche employée en Java est un mécanisme de
gestion automatique de la mémoire connu sous le nom de garbage collector (ou ramasse-miettes).
Le principe d’utilisation du garbage collector est basé sur l’analyse des liens entre références et
objets alloués. Dès qu’un objet n’est plus référencé (sortie d’une boucle contenant une référence
locale par exemple) il devient alors candidat au garbage collector. Cette candidature se traduit par
l’appel de la méthode finalize() héritée de la classe racine Object. Le garbage collector est
automatiquement déclenché par la machine virtuelle Java selon l’état de la mémoire du système, il
désalloue alors tous les objets candidats.
Il est cependant possible de forcer le déclenchement du garbage collector. Implémenter, la classe
suivante:
class MyOb {
MyOb() {print();}
String getID()
{return Integer.toHexString(hashCode());}
void print()
{System.out.println(getID());}
protected void finalize()
{System.out.println("finalize: "+getID());}
}
4
Mettre en oeuvre cette classe de la façon suivante:
MyOb my1 = new MyOb();
new MyOb();
MyOb my2 = new MyOb(); my2=null;
System.gc();
System.gc() force le déclenchement du garbage collector, on admettra ici cette écriture (elle est
abordée dans la suite de ce TP). Aller vérifier la déclaration de la méthode finalize() dans la classe
Object, commenter l’exécution de ce programme.
4) Règles d’écriture des données membres et des méthodes
Déclaration et initialisation des données membres
Comme dans de « nombreux » langages de programmation objet, une déclaration d’une donnée
membre (type primitif ou objet) d’une classe en Java se présente sous la forme suivante :
Class NomClasse {
type variable ;
}
Une donnée membre se caractérise donc par la déclaration de son type (type primitif ou objet), et
la déclaration d’un nom de variable l’identifiant. Implémenter la classe suivante et la mettre en
œuvre :
class Point0 {
private int x,y;
private char l;
void print()
{System.out.println(x+";"+y+":"+l);}
}
Vous pouvez constater que les données membres x et y de l’objet type Point0 ont été initialisées
par défaut à 0, et la donnée membre l à . En effet, la création d’un objet entraîne
systématiquement l’initialisation de ses données membres, comme le montre le tableau suivant :
Types de données
Valeur par défaut
boolean
char
byte, short, int, long
float, double
Objet
False
0
0.0
une référence
Dans ce tableau, on voit que les données de types primitifs sont initialisés. Les données de types
objets elles sont initialisées par des références, mais les objets sont non alloués. C’est donc au
programmeur Java de prendre en charge dans son programme les initialisations non nulles des
types primitifs, et l’allocation des objets. Implémenter, et tester les classes suivantes :
class Point1 {
private int x=1,y=1;
private Object o = new Object();
void print()
{System.out.println(x+";"+y+":"+o);}
}
class Point2 {
private int x,y;
private Object o;
Point2()
{x = y = 1; o = new Object();}
void print()
{System.out.println(x+";"+y+":"+o);}
}
Comme vous pouvez le voir, il existe deux possibilités pour un programmeur Java de prise en
charge des initialisations des types primitifs, et des allocations d’objets. Par la suite, habituer vous
à utiliser la deuxième Point2. Elle constitue en effet un mode de programmation implicite en
P.O.O. Il est du rôle naturel du constructeur d’assurer l’initialisation et l’allocation des données
membres.
Il existe enfin en Java une possibilité pour l’initialisation définitive des données membres à l’aide
du mot clé final. Re-implémenter la classe Point0 de la manière suivante, mettre en œuvre la
méthode print(), tenter d’affecter la donnée membre l, et commenter.
class Point0 {
private int x,y;
private final char l = 'a';
void print()
{System.out.println(x+";"+y+":"+l);}
}
Déclaration des thodes
Comme dans de « nombreux » langage de programmation, une déclaration de méthode de classe
en Java se présente sous la forme suivante :
5
type nomMéthode(arguments effectifs)
Implémenter les méthodes suivantes dans la classe Point:
double distance(int xi, int yi)
{return (x-xi)*(x-xi) + (y-yi)*(y-yi);}
Les arguments xi yi de type int figurant dans l’en-tête des la méthode distance() sont qualifiés
d’arguments effectifs de la méthode. En Java, comme dans de « nombreux » langage de
programmation, c’est le couple {nomMéthode, arguments effectifs} qui constitue la « signature »
de la méthode, implémenter de nouvelles méthodes distance() de la façon suivante :
int distance(int xi, int yi)
{return (x-xi)*(x-xi) + (y-yi)*(y-yi);}
double distance(int v)
{return (x-v)*(x-v) + (y-v)*(y-v);}
Vous avez fait ici deux surcharges de la méthode distance(). La première surcharge déclenche une
erreur à la compilation de votre code. En effet cette surcharge est erronée car le couple
{nomMéthode, arguments effectifs} n’a pas été modifié par rapport à la première définition de la
méthode distance(). Par opposition, la deuxième surcharge modifie le couple {nomMéthode,
arguments effectifs}, et constitue donc une surcharge correctement formatée.
En Java, comme dans de « nombreux » langage de programmation, le passage de variables en
argument d’une méthode se fait par recopie de ces variables :
En ce qui concerne les données de types primitifs, elles sont recopiées vers les
arguments effectifs de la méthode (on parle de passage par valeurs).
En ce qui concerne les données de types objets, comme nous l’avons présenté
précédemment, ceux-ci se manipulent par références. Ce sont donc les références qui
sont recopiées vers les arguments effectifs de la méthode (on parle de passage par
références)
Mettre en œuvre cette méthode de la façon suivante, que concluez-vous sur la récupération des
arguments :
Point pt = new Point(1);
double d = pt.distance(5,4);
System.out.println(d);
pt.distance(5);
Mettre en œuvre cette méthode de la façon suivante, commenter les conversions de types liées aux
opérateurs de Cast implicitement mis en œuvre lors du passage des variables à la méthode.
Point pt = new Point(1);
byte b=0;long q=0;
System.out.println(pt.distance(b,0));
System.out.println(pt.distance(b+3,0));
System.out.println(pt.distance(0,(int)q));
Implémenter cette classe et la mettre en oeuvre. Comparer et commenter cet exemple avec
l’exemple d’affectation et de passage par argument des objets présenté précédemment dans ce
TP.
void ex(Point pt)
{pt = null;}
void test() {
Point pt = new Point();
System.out.println(pt);
ex(pt);
System.out.println(pt);
}
Récursivité des méthodes
Java autorise la récursivité des appels des méthodes. Cette récursivité peut prendre deux formes :
directe : une méthode s’appelle elle-même
croisée : une méthode initiale appelle une méthode, qui à son tour appelle la méthode
initiale
On se propose d’étudier ici la récursivité directe. Implémenter la classe suivante et la mettre en
œuvre. Comparer et commenter les trois méthodes de calcul factoriel, en terme de temps de calcul
et d’allocation mémoire.
class Util {
long fac0(long n) {
long r=1;
for(long i=1;i<=n;i++)
r *= i;
return r;
}
long fac1(long n) {
if (n>1) return fac1(n-1)*n;
else return 1;
}
long fac2(long n) {
long r;
if (n<=1) r = 1;
else r = fac2(n-1)*n;
return r;
1 / 11 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !