1. Introduction

publicité
TP Traitement d’images
RAPPEL DE JAVA
Sujets abordés: Java et Programmation Orientée Objets
1. Introduction
Le but de ce TP est de rappeler différentes notions de Java avant d’aborder les TP de traitements
d’images « applications industrielles ». Les différentes questions de ce TP sont optionnelles et
dépendent surtout de votre niveau dans ce langage, aborder donc celles qui vous paraissent nécessaires.
Une lecture attentive du TP est au moins demandée.
2. Introduction à Java
Java est un langage développé dans le milieu des années 1990. La particularité de Java réside dans son
indépendance au matériel grâce à l’utilisation d’une machine dite virtuelle. Java est un langage O.O
(Orienté Objets). La programmation Java est donc 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, une classe est une description d’un ensemble d’objets ayant une structure de
données commune et disposant des mêmes méthodes. Différentes ouvrages Java disponibles en ligne
peuvent trouvés en [1] [2] et [3] (voir bibliographie).
Premier programme
Vous pouvez écrire le programme suivant, l’enregistrer le dans un fichier (App.java) et le compiler et
l’exécuter à l’aide de votre environnement de programmation. Vous pouvez également utiliser la
commande « javac App.java » pour la compilation du fichier suivie de la commande « java –cp .\ App
» pour son exécution.
class App {
public static void main(String args[])
{ System.out.println("Hello World"); }
}
Remarque : le « .\ » est optionnel selon la configuration de l’exécutable java sur votre système.
Remarque : Des informations détaillées sur l’utilisation de javac et java sont fournies par la
documentation de la plate-forme SDK : j2sdkx.x.x/docs/index.html
App correspond ici à votre classe, et main la fonction d’exécution principale de cette classe. L’appel de
la fonction System.out.println correspond au printf en C et cout en C++. La déclaration static et public
sont des obligations indispensables pour l’appel de la fonction main. Cette déclaration vous oblige
également de déclarer votre classe App contenant le main dans un fichier de même nom (App.java). On
admettra dans cette première partie ces différentes notions, elles seront abordées plus en détails dans la
suite du TP. Dans votre répertoire source, le compilateur a créé un fichier (App.class). Ce fichier
correspond à votre exécutable Java, c’est le « binaire » (ou byte code) utilisé par la machine virtuelle
Java pour exécuter votre programme.
Premières notions .O.O
Vous pouvez reprendre le programme précédent de la façon suivante :
/** HelloWorld */
class HelloWorld {
//hello member
String hello = "Hello World";
/** sayHello */
void sayHello()
{ System.out.println(hello); }
}
/** App class */
class App {
/ ** main function */
public static void main(String args[]) {
HelloWorld my = new HelloWorld();
my.sayHello();
}
}
Vous avez défini ici votre première classe HelloWorld, et sa méthode sayHello(). my correspond à
votre premier objet instance de la classe HelloWorld, alloué suite à l’appel de l’opérateur new. La
notion d’opérateur est abordée plus tard dans ce TP. La méthode sayHello() est appelé via l’opérateur
point ., tel que my.sayHello(). Vous avez également défini votre première donnée membre hello de
votre classe HelloWorld de type String. Sur cet exemple, des commentaires ont été rajoutés de type
ligne // et bloc /** */.
A travers ce court exemple, vous avez vu les principes de base de la P.O.O, la classe HelloWorld, sa
méthode sayHello et sa donnée membre hello, et l’objet my instance de la classe HelloWorld alloué
suite à l’appel de l’opérateur new.
Remarque : La classe HelloWorld a été définie de façon Majuscule-Majuscule, et la méthode sayHello
de façon minuscule-Majuscule. C’est une convention de programmation implicite en Java.
Remarque : Prenez l’habitude de programmer en « anglais », et de commenter vos programmes à la
manière de cet exemple. En effet, le code et les commentaires formatés ainsi peuvent être traités par la
suite par l’outil de documentation automatique de code javadoc.
API specification
Avant d’aborder plus en détails la suite de ce TP, il nécessaire d’introduire l’API specification de la
plate forme Java 2 SDK. Ouvrir avec votre explorateur le fichier j2sdk1.x.x.x\docs\api\index.html, et
placer ce fichier en raccourci sur votre système (vous aurez à l’utiliser de nombreuses fois).
Cette documentation html correspond à la documentation de toutes les classes standards distribuées
avec la plate forme Java 2 SDK. Ces classes sont regroupées par package. Le package correspond à un
ensemble de classes (ou une librairie de classes spécifiques). La notion de package sera abordée plus en
détails dans la suite de ce TP.
class Test1 {
void testa() {…}
void testb() {…}
}
Si vous faîtes défiler la barre de déroulement de la fenêtre bas gauche, vous avez un aperçu du nombre
de classe à disposition. Ajoutez à ça le volume « important » de projet Java à disposition sur Internet,
nous venons d’illustrer que la puissance du langage Java ne réside pas seulement dans le langage en lui
même, mais aussi par sa popularité.
class App {
public static void main(String args[]) {
Test1 t = new Test();
t.testa();
}
}
Remarque : La documentation API specification est obtenu par utilisation de l’outil javadoc à partir du
code source Java des packages standrards Java, en respectant les règles de commentaire présentées
précédemment.
3. Types primitifs de données, opérateurs et instructions de contrôle
Introduction
Comme « tout » langage de programmation, Java est basé sur l’utilisation de types primitifs de
données, d’opérateurs, et d’instructions de contrôle. Le tableau suivant résume ces différents éléments.
Types primitifs
Opérateurs
Instructions de contrôles
Entier
Flottant
Texte
Booléen
Pas de type
Arithmétiques
Relationnels
Logiques
Affectation
Cast
(in,de)crémentation
Conditionnel
Allocation
Point
byte, short, int, long
float, double
char
bool
void
+, -, *, /, %
<, <=, >, >=, ==, !=
!, &, ^, |, &&, ||
=
(type)
++, -?:
new
.
if, else, elseif
switch case
do while
for
break, continue
Ces diffèrents éléments sont similaires entre les langages de programmation, on les retrouve dans les
langages C et C++ par exemple. Nous détaillons ici quelques points particuliers.
Question : Commentez les 12 exemples suivants, vous pourrez implémenter ceux que vous ne
comprenez pas. Pour cela, vous pouvez créer une nouvelle classe Test1 comme le montre l’exemple
suivant. Dans cette classe, implémenter de nouvelles méthodes test() pour chacun de vos tests. Mettre
en œuvre cette classe depuis votre classe principale App.
Types primitifs de données
1.
Déclaration
int a = 1;
{int b=2;}
int b=3;
System.out.println(a+b);
Opérateurs
2.
L’opérateur modulo
int a = 11%4;
System.out.println(a);
3.
Opérateurs d’affectation élargie
int a=2,b=2;
System.out.println(a = a+3);
System.out.println(b += 3);
4.
Priorités entre opérateurs
int i=0;
System.out.println((i++) == 0);
int j=0;
System.out.println((j+=1) == 0);
5.
Opérateurs de court-circuit
int i=0;
if(false && (i++==0)){}
System.out.println(i);
int j=0;
if(false & (j++==0)){}
System.out.println(j);
6.
Opérateur de Cast
System.out.println(1.2/0.7);
System.out.println((int)(1.2/0.7));
7.
Opérateur conditionnel
int a=1,b=2,m1,m2;
if(a>b) m1=a;
else m1=b;
System.out.println(m1);
m2 = a>b ? a : b;
System.out.println(m2);
8.
Opérateur new
int a=0;
HelloWorld my1 = null;
HelloWorld my2 = new HelloWorld();
System.out.println(a+" "+" "+my1+" "+my2);
my2 = new HelloWorld();
System.out.println(a+" "+" "+my1+" "+my2);
int i=0;
for(i=0;i<5;i++)
System.out.println(i);
i=0;
for(;i<5;i++)
System.out.println(i);
i=0;
for(;(i+=1)<5;)
System.out.println(i);
12. Instruction for (2)
for(int i=0, j=10;j>i;i++,j--)
System.out.println(i+";"+j);
for(int i=0, j=10; ;i++,j--) {
System.out.println(i+";"+j);
if(j<i) break;
}
4. Tableaux
Remarque : L’opérateur delete n’existe pas en Java, en effet la désallocation des objets est assurée par
le garbage collector « ramasse-miettes ».
9.
Opérateur point
HelloWorld my = new HelloWorld();
my.sayHello();
System.out.println(my.hello);
Instruction de contrôle
10. Conditions imbriquées
int b;
for(int a=0;a<10;a++)
{
if(a<5) b=1;
else if((a>=5)&(a<8)) b=2;
else if((a>=8)&(a<9)) b=3;
else b=4;
System.out.println(b);
}
11. Instruction for (1)
Introduction
Comme « tout » langage de programmation, Java permet la manipulation de tableaux. Cependant, Java
fait preuve d’originalité sur ce point en considérant les tableaux comme des objets, et en permettant la
composition de tableaux multi-dimensionnels à partir de tableaux de dimensions inférieures. Nous
illustrons ici l’utilisation des tableaux en Java à travers quelques exemples.
Manipulation
Nous illustrons ici l’utilisation des tableaux en Java à travers quelques exemples.
Question : Commenter les 5 exemples suivants. Vous pourrez implémenter ceux que vous ne
comprenez pas. Pour cela vous pourrez créer une nouvelle classe Test2 :
1.
Déclaration, allocation, initialisation, et accès
int [] t1 = new int[3];
int t2[] = {1,2,3};
for(int i=0;i<3;i++)
System.out.println(t1[i] + ";" + t2[i]);
2.
Taille et affectation
int [] t1 = new int[3];
int t2[] = {1,2,3};
for(int i=0;i<t1.length;i++)
{
t1[i] = t2[i];
System.out.println(t1[i] + ";" + t2[i]);
}
3.
Affectation et passage par argument
Java permet la manipulation de chaînes de caractères. Ces chaînes de caractères sont en fait des objets,
instance de la classe standard Java String. Leur utilisation est assez proche des tableaux. Java fait
preuve d’originalité sur la gestion des chaînes de caractères, en imposant que les objets de type String
ne soient pas modifiables. Ils restent cependant utilisables pour la création de nouveaux objets.
Manipulation
Nous illustrons ici l’utilisation des chaînes de caractères en Java à travers quelques exemples.
void update(int t[]) {
if(t.length>=2)
t[1] = 5;
}
Question : Commentez les 6 exemples suivants, vous pourrez implémenter ceux que vous ne
comprenez pas. Pour cela, vous pouvez créer une nouvelle classe Test3
void test() {
int t1[], t2[] = {1,2,3};
t1 = t2; t1[0] = 4;
System.out.println(t2[0]+";"+t1[0]);
String s1 = new String("hello");
String s2 = "hello";
String s3 = new String(s1);
update(t2);
System.out.println(t2[1]+";"+t1[1]);
}
4.
Tableaux à plusieurs dimensions (1)
int t1[][] = new int[2][3];
int [][]t2 = {{1,2,3},{4,5,6}};
for(int i=0;i<t1.length;i++)
for(int j=0;j<t1[i].length;j++) {
System.out.println(t1[i][j]);
System.out.println(t2[i][j]);
}
5.
Tableaux à plusieurs dimensions (2)
int t[][] = new int[2][];
int t1[] = {1,2,3}, t2[] = {4,5,6,7,8};
t[0] = t1;t[1] = t2;
t1[1] = 0; t2[2] = 0;
for(int i=0;i<t.length;i++)
for(int j=0;j<t[i].length;j++)
System.out.println(t[i][j]);
5. Chaînes de caractères
Introduction
1.
Déclaration, allocation, et initialisation
System.out.println(s1 + " " + s2 + " " + s3);
2.
Affectation
String s1 = "hello1", s2 = s1;
System.out.println(Integer.toHexString(s1.hashCode()));
s1 = "hello2";
System.out.println(s1 + " " + s2);
System.out.println(Integer.toHexString(s1.hashCode()));
On admettra ici l’écriture de Integer.toHexString(o.hashCode()) pour la récupération sous forme de
chaîne de caractères de la référence de l’objet o. Elle sera abordée dans la suite de ce TP.
3.
Opérateurs arithmétiques, d’affectation, et d’affectation élargie
String s1 = "Java", s2 = "2";
String s = s1 + s2;
System.out.println(s);
s1 += s2;
System.out.println(s1);
4.
Opérateurs relationnels
String s1 = "Java", s2 =
System.out.println(s1 ==
System.out.println(s1 !=
System.out.println(s1 ==
5.
"Java";
s2);
s2);
s1);
Longueur, accès aux caractères, recherche, et sous chaînes
String s = "JavaTM 2 Platform"; char c;
for(int i=0;i<s.length();i++)
{c = s.charAt(i);System.out.println(c);}
int b = s.indexOf('T');
int e = s.indexOf('a',b);
String r = s.substring(b+1,e);
System.out.println(r);
6.
Et sa mise en oeuvre :
Point pt = new Point();
pt.print();
pt.l = 'o';
pt.set(5,2);
pt.print();
pt.move(-6,4);
pt.print();
Comparaison, modification, et conversion
String s1 = "Java"; String s2 = "Jovo";
System.out.println(s1.equals(s2));
s2 = s2.replace('o','a');
System.out.println(s1.equals(s2));
String cvt = new String();
for(int i=1;i<=2;i++)
{System.out.println(s2+cvt.valueOf(i));}
Il illustre les principes de base de la programmation objet 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 la suite de ce TP.
6. Objets
Dans cette partie nous vous proposons d’aborder l’utilisation des objets en Java. On rappelle que les
objets sont composés de données membres et de méthodes, et 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.
Déclaration de classes et encapsulation de données membres
Considérons l’exemple suivant
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);}
}
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. Si
vous rajoutez ce bloc de code à la définition de votre classe Point :
Point() {}
Vous redéfinissez alors 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. Considérons l’exemple
suivant :
Point pt;
pt = new Point();
pt.print();
System.out.println(new Point());
new Point().print();
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();
Question : Expliquer l’erreur de compilation générée par le code suivant. Que concluez-vous sur
l’utilisation du constructeur par défaut ?
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();}
void test1() {
Point pt1, pt2;
pt1 = new Point();
pt2 = pt1;
pt1.set(2,3);
pt2.print();
ex(pt2);
pt1.print();
}
void test1() {
Point pt1 = new Point();
Point pt2 = new Point();
System.out.println(pt1 == pt2);
System.out.println(pt1 != pt2);
System.out.println(pt1 == pt1);
}
7. Règles d’écriture des données membres et des méthodes
int getX() {return x;}
int getY() {return y;}
void print()
{System.out.println(x+";"+y);}
}
public class Test {
public static void main(String[] args) {
Point pt;
pt = new Point(4);
pt.print();
pt = new Point(4,5);
pt.print();
}
}
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.
Question : Au travers de l’exemple suivant, que concluez-vous sur l’affectation et le passage par
référence d’objets, et sur la comparaison d’objets.
void ex(Point pt) {
pt.set(1,1);
}
La déclaration des données membres et méthodes des objets fait intervenir différentes règles en Java.
Cette section a pour but d’introduire les principales.
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. Si vous mettez en oeuvre la classe suivante :
class Point0 {
private int x,y;
private char l;
void print() {System.out.println(x+";"+y+":"+l);}
}
Vous pourrez 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
boolean
char
Valeur par défaut
False

byte, short, int, long
float, double
Objet
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.
Question : Commenter les classes suivantes, pour la classe Point3 que se passe t-il si on tente
d’affecter la donnée membre l.
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);}
}
class Point3 {
private int x,y;
private final char l = 'a';
void print()
{System.out.println(x+";"+y+":"+l);}
}
Remarque : 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.
Déclaration des mé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 :
type nomMéthode(arguments effectifs)
La déclaration suivante présente une méthode distance implémentable 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,
Question : Si on tente d’implémenter les 3 méthodes suivantes au sein de la classe Point, commenter
l’erreur de compilation générée :
double distance(int xi, int yi)
{return (x-xi)*(x-xi) + (y-yi)*(y-yi);}
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);}
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)
Question : Comparer et commenter les deux exemples suivants.
void ex1(Point pt) {
pt.set(1,1);
}
void test1() {
Point pt1, pt2;
pt1 = new Point();
pt2 = pt1;
pt1.set(2,3);
pt2.print();
ex1(pt2);
pt1.print();
}
void ex2(Point pt)
{pt = null;}
void test2() {
Point pt = new Point();
System.out.println(pt);
Ex2(pt);
System.out.println(pt);
}
this();
if(v!=1) x = y = v;
}
Point4(int x, int y) {
this(x);
Récursivité des méthodes
if(x != y) {
this.x = x;
this.y= y;
}
}
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
Question : 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;
}
}
Auto référence à l’aide du mot clé this
Le mot clé this en Java permet de faire référence à l’objet dans sa globalité. this correspond donc à la
référence de l’objet dans lequel il est utilisé. La classe suivante donne un exemple d’utilisation du mot
clé this.
class Point4 {
private int x,y;
Point4()
{x = y = 1;}
Point4(int v) {
void print()
{System.out.println(x+";"+y);}
}
Déclaration static des données membres et des méthodes
Il est possible en Java de définir des données membres et des méthodes de classe existantes en un seul
exemplaire quelque soit le nombre d’objets instances d’une même classe. De même, ces données
membres et ces méthodes peuvent être appelées indépendamment de toute allocation. Considérons
l’exemple suivant :
class MyClass2
{
static int n;
static void print()
{System.out.println(n);}
}
Et sa mise en oeuvre :
MyClass2 my = new MyClass2();
my.print();
MyClass2.n = 10;
MyClass2.print();
my.print();
A travers cet exemple, vous pouvez constater qu’une donnée membre ou une méthode déclarée statique
peut être utilisée sans allocation d’objet préalable. Cette déclaration statique peut être vue comme une
déclaration globale (un peu à la manière du C et du C++). Cependant, la déclaration de données
membres et/ou de méthodes statiques dans une classe restreint l’utilisation de celles-ci.
Question : Implémenter et mettre en oeuvre la classe suivante. Commenter les restrictions d’utilisation
des méthodes et données statique en expliquant les erreurs de compilation.
class MyClass3 {
int n1;
static int n2;
void f1()
{n1++; n1 *= 2;}
static void f2() {
n2++; n2 *= 2;
f1(); n2 += n1;
}
}
L’importation de toutes les classes d’un package n’augmente ni la taille du byte code Java, ni le temps
d’exécution du programme. Le filtrage des classes lors de l’importation d’un package n’a d’utilité que
de limiter les ambiguïtés en cas de classes de noms similaires entre packages différents importés.
L’instruction . est un séparateur entre les noms de package, de sous packages, et l’instruction *. En
effet, dans notre exemple le package lang est un sous-package du package java. Dans l’API
specification, Vous verrez qu’il existe trois packages racines : java, javax, et org. L’exemple suivant
donne une hiérarchie de package à trois étages.
import java.util.zip.*;
import java.util.jar.*;
Au cours de vos différents programmes Java vous avez utilisé à plusieurs reprises des données
membres et/ou des méthodes déclarées statiques, comme la méthode main() par exemple.
Le code suivant donne un premier exemple complet d’utilisation de package pour la mise en œuvre de
la classe Random du package util.
Question : Maintenant que vous avez appréhendé la notion de déclaration statique, aidez vous de l’API
specification afin d’expliquer les déclarations suivantes :
import java.util.*;
1.
2.
3.
System.out.println()
Integer.toHexString()
System.gc()
8. Utilisation de package
La notion de package en Java correspond à un regroupement de classes, sous un identificateur commun
(correspondant au nom du package). Cette notion facilite le développement et la cohabitation
d’application « conséquente », en isolant l’ensemble des classes existantes. Dans l’API specification, la
fenêtre haute droite donne la liste des packages standards de la plate-forme Java 2 SDK. Pour importer
un package dans un fichier Java, il suffit de déclarer ce package à l’aide de l’instruction import en entête de votre fichier de la façon suivante :
import java.lang.*;
Sans le savoir, vous avez utilisé ce package java.lang à plusieurs reprises dans vos programmes Java.
En effet, ce package standard java.lang est en fait implicitement déclaré dans les fichiers Java (Nous
l’utilisons ici à titre d’exemple). Si vous sélectionnez ce package dans l’API specification, vous y
trouverez l’ensemble des classes que vous avez utilisées au cours de vos programmes : String, System,
Integer, etc.
De même, vous avez déjà défini des packages à plusieurs reprises dans vos programmes Java. En effet,
lorsque vous compilez différentes classes stockées en (.class) dans un même répertoire, vous constituez
un package local (sans nom).
Dans notre déclaration exemple, l’instruction * définit que toutes les classes du package java.lang sont
importées. Il est en effet possible de filtrer les classes que l’on souhaite importer en les sélectionnant
individuellement, comme par exemple :
import java.lang.String;
public class PackageUse1
{
public static void main(String args[])
{
Random r = new Random();
System.out.println(r.nextInt());
}
}
Comme nous l’avons présenté en introduction, l’utilisation des packages permet de lever les ambiguïtés
entre classes de noms similaires entre packages différents. On se propose d’illustrer cette propriété ici
en confrontant une classe Vector locale avec la classe Vector du package java.util.
Question : Implémenter le code suivant et le mettre en œuvre. Que concluez vous sur la déclaration de
v1 en java.util.Vector, commenter. Recommencez une première fois une compilation/exécution de ce
code en mettant la classe Stack en commentaire. Re-faîtes une seconde fois une compilation/exécution
mais en supprimant au préalable le fichier Stack.class dans votre répertoire local. Que concluez vous
sur une déclaration éventuelle de la variable st en java.util.Stack ?
import java.util.*;
class Vector
{
private Object t[];
private int p;
Vector()
{t = new Object[100];}
void add(Object o) {
if(p<100) {t[p] = o; p++;}
System.out.println("add");
}
Object elementAt(int i)
{if (i<=p) return t[i]; else return null;}
}
class Stack {
private java.util.Vector v1;
private Vector v2;
int p;
Stack() {
v1 = new java.util.Vector();
v2 = new Vector();
}
void push(Object o) {
v1.add(o);
v2.add(o);
System.out.println("push");
p++;
}
Object peek() {
return v2.elementAt(p-1);
}
}
public class PackageUse2 {
public static void main(String args[])
{
Stack st = new Stack();
st.push(new Object());
System.out.println(st.peek());
}
}
membres et des méthodes de la classe mère dans la classe dérivée. Cette re-exploitation est le plus
souvent contrôlée par des droits d’accès entre les classes mères et les classes dérivées.
En Java, il existe en fait quatre déclarations de droit d’accès définissables dans une classe pour les
données membres et les méthodes : private, pas de déclaration, protected, et public. Les déclarations
protected et public concernent « majoritairement » la définition de package, elles seront abordées plus
en détails dans les TP suivants. En ce qui concerne les relations d’héritage, seules les déclarations
private et pas de déclaration sont utilisées. Si on considère le code suivant :
class A1{
private int u; int v;
void set1(int u)
{setB(); this.u = u;}
void set1(int u, int v)
{setB(); this.u = u; this.v = v;}
int get1() {return u;}
private int back;
private void setB() {back = u;}
void undo() {u = back;}
void print1()
{System.out.println(u+";"+v);}
}
class A2 extends A1 {
private int w;
void set2(int u, int v, int w)
{set1(u); this.v = v; this.w = w;}
int get2() {return w;}
void print2()
{System.out.println(get1()+";"+v+";"+w);}
}
9. Héritage
On se propose ici d’aborder quelques premières notions sur l’héritage en Java
Introduction
Une relation d’héritage entre classes se définit en Java à l’aide du mot clé extends. Le code suivant
donne un exemple d’héritage entre deux classes « vides » :
class I1 {}
class I2 extends I1 {}
En Java, l’héritage multiple n’est pas permis : une classe ne peut hériter que d’une seule autre classe
tout au plus. Evidemment, l’intérêt de l’héritage en P.O.O réside dans la re-exploitation des données
class A3 extends A2{
void print3()
{System.out.println(get1()+";"+v+";"+get2());}
}
public class Test {
public static void main(String[] args) {
A2 my2 = new A2();
my2.set2(1,2,3);my2.set2(4,5,6);
my2.v = 8; my2.undo();
my2.print2();
A3 my3 = new A3();
my3.set1(7); my3.v = 8;
my3.print3();
}
TP). Aller vérifier la déclaration de la méthode finalize() dans la classe Object, commenter l’exécution
de ce programme.
}
Cet exemple met en œuvre les droits d’accès aux données membres et méthodes via des relations
d’héritage.

private : Dans cet exemple, la donnée membre u a été déclarée à l’aide du mot clé private
dans la classe A1. Ceci signifie qu’elle est accessible uniquement par les méthodes de la
classe A1. La relation d’héritage entre les classes A1 et A2 ne lève en rien l’encapsulation de
la donnée membre u dans la classe A2. Il en est de même pour la méthode setB() de la classe
A1, elle est uniquement accessible par les méthodes de la classe A1. Ce constat est
également vrai en ce qui concerne les classes A1 et A3, et A2 et A3.

pas de déclaration : En l’absence de déclaration les méthodes et données membres de la
classe A1 sont directement accessibles par la classe dérivée A2 : v, get1(). De même, cette
propriété se reconduit entre les classes A1 et A3 (v, get1()), et A2 et A3(v, get1(),get2()).
Ces données membres et méthodes sont également accessibles à l’extérieure des classes. On
peut en effet les invoquer à partir d’un objet instance et de l’opérateur ., comme par
exemple : my.v, my.print().
La classe racine Object
Si on considère l’exemple suivant:
Point pt = new Point();
System.out.println(pt.toString());
Vous faites 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.
Question : Aller consulter dans l’API specification la documentation de la classe Object afin de
commenter l’action de ces lignes de code.
System.out.println(new Point().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.
Question : Implémenter la classe suivante et la mettre en oeuvre comme indiquée. System.gc() force le
déclenchement du garbage collector, on admettra ici cette écriture (elle est abordée dans la suite de ce
class MyOb {
MyOb() {print();}
String getID()
{return Integer.toHexString(hashCode());}
void print()
{System.out.println(getID());}
protected void finalize()
{System.out.println("finalize: "+getID());}
}
public class Test {
public static void main(String[] args) {
MyOb my1 = new MyOb();
new MyOb();
MyOb my2 = new MyOb(); my2=null;
System.gc();
}
}
Construction d’objets dérivés
Un objet donné, instance d’une classe dérivée peut donc exploiter les données membres et les méthodes
définies dans la classe mère de cette classe dérivée. Ceci induit que cet objet instance de la classe
dérivée est lié à un objet instance de la classe mère. Durant la construction d’un objet dérivé, il y a
donc construction des objets pères associés à cet objet dérivé par les différentes relations d’héritage
entre classes. On se propose d’étudier ici la construction des objets dérivés. On considère les
différentes classes suivantes dans lesquelles nous avons redéfinis les constructeurs par défaut.
class A {
A() {System.out.println("A");}
}
class B extends A {
B() {System.out.println("B");}
}
class C extends A {
C() {System.out.println("C");}
}
class D extends C {
D() {System.out.println("D");}
}
class E extends C {
E()
{System.out.println("E");}
}
Question : Indiquer le diagramme d’héritage de cet exemple, que pouvez vous conclure sur le
processus de construction des objets dérivés en ce qui concerne l’ordre de la construction.
S3()
{super(true);print();}
}
On se propose de vérifier cet ordre en ce qui concerne la construction et l’initialisation des données
membres d’une classe.
Question : Implémenter les classes suivantes, les mettre en œuvre, commenter :
class V1 {
int v;
int v1=1;
Dans cet exemple, vous avez redéfini dans la classe S1 le constructeur par défaut. Vous pouvez voir
dans la mise en œuvre de la classe S2 que ce constructeur par défaut est également appelé par défaut
dans une relation d’héritage.
Question : Que pouvez vous conclure sur la prise en charge de la construction de l’objet père via le
mot clé super.
Redéfinition des données membres et des méthodes, surcharge de méthodes
V1() {print1();v=v1;print1();}
void print1()
{System.out.println(v+";"+v1+";?");}
}
class V2 extends V1 {
int v2=2;
Lorsque qu’une classe dérivée déclare des méthodes et des données membres de même signature que
celles d’une de ses classes mères, on dit que la classe dérivée redéfinit les données membres et les
méthodes. Cette signature correspond en ce qui concerne les données membres à {nomVariable}, et en
ce qui concerne les méthodes {nomMéthode, arguments effectifs}. Si on considère le code suivant :
class R1{
int u; char v='b';
V2() {print2();v=v2;print2();}
void set(int u)
{this.u = u;}
void print2()
{System.out.println(v+";"+v1+";"+v2);}
}
void print()
{System.out.println(v);}
}
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. Si on
considère le code suivant :
class S1 {
int v;
S1() {v=1;}
S1(boolean t) {v=2;}
void print()
{System.out.println(v);}
}
class S2 extends S1 {
S2()
{print();}
}
class S3 extends S1 {
class R2 extends R1{
char u='a';
void set(int u)
{System.out.println("R2 class");}
void print(char w)
{System.out.println(u+","+v+","+w);}
}
public class Test {
public static void main(String[] args) {
R1 my1 = new R1();
my1.set(2);
System.out.println(my1.u);
R2 my2 = new R2();
my2.set(2);
System.out.println(my2.u);
my2.print();
my2.print('c');
}
}
A travers cet exemple, vous pouvez voir que la donnée membre u et la méthode set(int) ont été
redéfinies de la classe R1 à la classe R2. De même, vu que la classe dérivée hérite des méthodes de la
classe mère la notion de surcharge de méthodes reste vraie dans une relation d’héritage. Par exemple la
méthode print(char) de la classe R2 correspond à la méthode surchargée print() de la classe R1.
La redéfinition des données membres et des méthodes dans la classe dérivée est possible de par leur
autorisation d’accès par la classe mère. Dans le cas où ces données membres et ces méthodes sont
déclarées privées, leur déclaration est ignorée par la classe dérivée.
Question : Implémenter le code suivant et commenter
class RP1{
private int i=0;
class RF1 {
final int i=0;
final void ic() {}
}
class RF2 extends RF1{
char i='a';
void ic() {i++;}
}
10. Polymorphisme
Introduction, compatibilité ascendante, instanceof
Le polymorphisme est un des concepts important de la P.O.O, fortement lié au concept d’héritage. On
peut caractériser le polymorphisme en disant qu’il permet de manipuler des objets sans en connaître
(tout à fait) le type. Si on considère le code suivant :
private void ic() {i++;}
void update1()
{System.out.println(i);ic();}
}
class RP2 extends RP1{
char i='a';
void ic() {i++;}
void update2()
{System.out.println(i);ic();}
}
public class Test {
public static void main(String[] args) {
RP2 my = new RP2();
my.update1();
my.update2();
my.ic();
my.update1();
my.update2();
}
}
Java permet d’interdire la redéfinition de méthodes et de données membres dans les classes dérivées à
l’aide du mot clé final.
Question : Implémenter les classes suivantes, relever et commenter les erreurs de la compilation de ce
code.
class P1
{
void pp1()
{System.out.println("P1 class");}
}
class P2 extends P1
{
void pp2()
{System.out.println("P2 class");}
}
public class Test {
public static void main(String[] args) {
P1 my = new P2();
my.pp1();
((P2)my).pp2();
}
}
A travers ce court exemple vous avez vu le premier concept du polymorphisme : la compatibilité
ascendante. En effet, vous avez manipulé un objet de type P2 via une référence de type P1
correspondant à la classe mère de P2. La règle suivante résume le concept de compatibilité ascendante :
Comptabilité ascendante : Il existe une conversion implicite d’une référence à
un objet de classe T en une référence d’une classe ascendante de T.
Tout objet en Java hérite implicitement de classe racine Object, vous pouvez donc reproduire l’exemple
précédent à partir de cette classe racine. Le code suivant en donne un exemple :
class MyOb {
void pp() {System.out.println("MyOb class");}
}
DS2 myc = new DS3(); myc.print();
Object my2 = new MyOb();
((MyOb)my2).pp();
Cet exemple est similaire au précédent, malgré l’allocation d’un objet de type DS3 affecté à une
référence de type DS2, c’est bien la méthode print() de DS3 qui est appelée.
Java permet l’utilisation de opérateur instanceof pour la vérification de type,
Question : Mettre en œuvre votre classe MyOb de la façon suivante, commenter.
Object my = new MyOb();
if(my instanceof MyOb)
System.out.println("Ok");
Ligature dynamique
Dans la partie héritage de ce TP nous avons présentés les concepts de redéfinition de méthodes. La
prise en compte du polymorphisme va compliquer la redéfinition. Si on considère le code suivant :
class DL1{
void print()
{System.out.println("DL1 class");}
}
class DL2 extends DL1{
void print()
{System.out.println("DL3 class");}
}
Et sa mise en œuvre :
DL1 my1 = new DL1(); my1.print();
DL1 my2 = new DL2(); my2.print();
A travers cet exemple vous voyez que malgré l’allocation d’un objet de type DL2 affecté à une
référence de type DL1, c’est bien la méthode print() de DL2 qui est appelée. Nous vous proposons de
développer cet aspect, considérons le code suivant :
Si on reprend cet exemple en plaçant en commentaire la méthode print() de la classe DS1 et en
recompilant l’ensemble. Le compilateur vous indique une erreur de résolution concernant la méthode
print(). Ceci signifie que la méthode print() initialement choisie à la compilation est celle de la classe
DS1, à l’exécution le choix se reporte sur la méthode print() de la classe DS3. Cet exemple illustre ce
que l’on nomme la ligature dynamique, la règle suivante en résume les concepts:
Ligature dynamique : Dans un appel de la forme x.f() x est déclaré et
supposé de classe Tn. Cette déclaration est telle que {T0 …Tn …Tv} avec pour
classes mères ascendantes de Tn les classes {T0 …Tn-1 }, et pour classes filles
descendantes de Tn les classes { Tn+1 …Tv }. Dans une telle déclaration le choix
de f() est déterminé ainsi :

à la compilation : on détermine dans {T0 …Tn } la meilleure signature
de f()

à l’exécution : on détermine dans {Tn …Tv } la meilleure signature de
f()
Classes abstraites
En P.O.O une classe abstraite est une classe qui ne permet pas d’instancier d’objets, elle ne peut servir
que de classe de base pour une dérivation. En Java une classe abstraite se déclare à l’aide du mot clé
abstract. Si on considère les classes suivantes
abstract class Ab {
int a = 1;
void print(int b)
{System.out.println(a+b);}
}
class DS1{
void print()
{System.out.println("DS1");}
}
class AbImpl1 extends Ab {}
class AbImpl2 extends Ab {}
class DS2 extends DS1{}
Ab a1,a2;
a1 = new AbImpl1(); a1.print(2);
a2 = new AbImpl2(); a1.print(3);
class DS3 extends DS2{
void print()
{System.out.println("DS3");}
}
Et sa mise en œuvre :
Et leur mise en œuvre :
A travers cet exemple, vous venez de définir votre première classe abstraite Ab. Vous pouvez définir
dans les classes abstraites différentes données membres et méthodes au même titre qu’une classe non
abstraite.
Vous pouvez déclarer des objets du type de votre classe abstraite dans votre programme principal (ici
a1 et a2). Vous ne pouvez cependant pas directement instancier ces objets, il vous faut utiliser un
constructeur d’une classe non abstraite (ici AbImpl1 et AbImpl2) dérivant de votre classe abstraite.
abstract class Ha2 extends Ha1 {
abstract void g();
}
L’exemple précédent ne présente que peut d’intérêt sur l’utilisation de classes mères abstraites par
rapport aux classes mères non abstraites. L’intérêt des classes abstraites réside dans la possibilité de
définir des méthodes abstraites. Si on considère les classes suivantes
abstract class Ha3 extends Ha2{
abstract void h();
}
abstract class Co {
int a = 1;
abstract void print(int b);
}
class Ha4 extends Ha3{
void g() {}
void h() {}
}
class CoImpl1 extends Co {
void print(int b)
{System.out.println(a+b);}
}
Interfaces
class CoImpl2 extends Co {
void print(int b)
{System.out.println(a-b);}
}
Une interface peut être considérée comme une classe abstraite n’implémentant que des méthodes
abstraites. Cependant, la notion d’interface est plus riche qu’un simple cas particulier de classe
abstraite, en effet :
1. Une classe peut implémenter plusieurs interfaces
2. Une classe peut étendre une autre classe tout en implémentant des interfaces
3. Les interfaces peuvent se dériver
4. Les variables peuvent être de type interface
Et leur mise en œuvre :
Si on considère les classes suivantes :
Co c1,c2,c3;
c1 = new CoImpl1();c1.print(1);
c2 = new CoImpl2();c2.print(1);
interface
/*
}
interface
/*
}
Dans cet exemple, vous venez de définir votre première méthode abstraite print(int a). Dans la classe
abstraite, cette méthode est définie à l’aide du mot abstract sans corps de fonction. Elle est redéfinie
par la suite dans les classes dérivées CoImpl1 et CoImpl2.
La définition de ces méthodes abstraites dans la classe mère correspond à un contrat d’implémentation
des méthodes pour les classes dérivées. Ce contrat doit être respecté par les classes dérivées, elles se
doivent obligatoirement de redéfinir les méthodes abstraites.
C’est cette certitude de la présence des méthodes abstraites redéfinies qui permet d’exploiter le
polymorphisme. Dans l’exemple précédent vous manipulez des objets de type Co, en faisant appel à la
méthode print(int a). Vous êtes en effet sûr que cette méthode est redéfinie dans vos objets dérivés,
avec un comportement propre à chacune de ces objets (ici addition pour CoImpl1 et soustraction pour
CoImpl2).
Vous pouvez également définir des relations d’héritage entre classes
abstraites et non abstraites. Le code suivant en donne un exemple.
class Ha1 {
void f() {}
}
abstraites, et entre classes
In1 {
public abstract*/ void f();
In2 {
public abstract*/ void g();
class Impl1 implements In1 {
public void f() {
System.out.println("In1");
}
}
class Impl2 implements In1,In2 {
public void f() {
System.out.println("In1");
}
public void g() {
System.out.println("In2");
}
}
class In3 {
public void h() {
System.out.println("In3");
}
}
class Impl3 extends In3 implements In1,In2 {
public void f() {
System.out.println("In1");
}
public void g() {
System.out.println("In2");
}
}
Et leur mise en œuvre :
Impl1 mya = new Impl1(); mya.f();
Impl2 myb = new Impl2(); myb.f(); myb.g();
Impl3 myc = new Impl3(); myc.f(); myc.g(); myc.h();
Dans cet exemple, vous avez mis en oeuvre vos premières interfaces In1 et In2. Il est possible
d’implémenter ces interfaces individuellement (Impl1), ou simultanément (Impl2). De même, ces
interfaces peuvent être utilisées en complément d’un héritage (Impl3).
interface Ii3 extends Ii2{
void h();
}
1.
class Ig1 implements Ii1 {
public void f() {System.out.println("Ig1");}
}
class Ig2 extends Ig1 {}
2.
implémente A, héritage, implémente B
class Ig3 implements Ii1 {
public void f() {System.out.println("Ig3");}
}
class Ig4 extends Ig3 implements Ii2 {
public void g() {System.out.println("Ig4");}
}
3.
Dans cet exemple nous avons placé en commentaire les instructions public abstract. Dans une
interface, toutes les méthodes sont implicitement déclarées de cette manière, il n’est donc pas
nécessaire de re-préciser cette déclaration. La partie public de cette déclaration implicite impose la
déclaration public des redéfinitions des méthodes des interfaces dans les classes les implémentant
(public f(), public g()). Nous admettrons cette déclaration public dans ce TP, elle sera abordée plus en
détails dans les TP suivants.
implémente A, héritage
implémente A, héritage, implémente A
class Ig5 implements Ii1 {
public void f() {System.out.println("Ig5");}
}
class Ig6 extends Ig5 implements Ii1 {
public void f() {System.out.println("Ig6");}
}
Si on complète la mise en oeuvre de la façon suivante :
4.
In1 myd = new Impl1(); myd.f();
In1 mye = new Impl2(); mye.f();
In2 myf = new Impl2(); myf.g();
Cet exemple illustre la manipulation de variables de type interface pour le polymorphisme. Vous
pouvez manipuler n’importe quelle instance de classes différentes (Impl1, Impl2) via des variables d’un
type interface (In1), si cette interface est implémentée par les classes (Impl1, Impl2). Egalement,
différentes instances d’une même classe (Impl2) peuvent être manipulées via des variables de type
interface différentes (In1, In2), si ces interfaces sont implémentées par la classe (Impl2). Vous pouvez
également définir des relations d’héritage entre interfaces. Les exemples suivants illustrent ces
différentes possibilités d’utilisation des interfaces.
interface Ii1 {
void f();
}
interface Ii2 {
void g();
}
implémente A, héritage, implémente AB
class Ig7 implements Ii2 {
public void g() {System.out.println("Ig7");}
}
class Ig8 extends Ig7 implements Ii3 {
public void g() {System.out.println("Ig8");}
public void h() {System.out.println("Ig8");}
}
5.
implémente AB, héritage, implémente A
class Ig9 implements Ii3 {
public void g() {System.out.println("Ig9");}
public void h() {System.out.println("Ig9");}
}
class Ig10 extends Ig9 implements Ii2 {
public void g() {System.out.println("Ig10");}
}
11. Algorithmes de parcours d’arbres binaires
Dans cette partie, on se propose de mettre en œuvre les notions abordées dans ce TP pour
l’implémentation d’algorithmes de parcours d’arbres binaires. L’arbre est une structure de données
informatique classique. Par exemple, la structuration des fichiers d’un disque de stockage se fait sous
forme d’arbre. L’arbre binaire est une spécialisation de la structure de données arbre, dans lequel
chaque noeud de l’arbre est connecté à 0-2 nœuds fils. La figure ci-après donne deux exemples d’arbres
binaires composés respectivement de 7 et 10 nœuds. L’arbre à 10 nœuds à pour racine l’arbre à 7
nœuds, plus 3 nœuds supplémentaires : h, i, et j.
Définir une classe Search de la façon suivante. Dans une première étape, initialiser le constructeur de
façon à construire les arbres binaires de 7 et 10 nœuds présentés sur la figure précédente avec pour
nœud racine respectivement r1 et r2. Entre les constructions des deux arbres, utiliser la méthode
clearID() pour la remise à zéro du label statique.
class Search
{
Node r1, r2;
Search() {…}
void traverse1(Node n) {…}
void traverse2(Node n) {…}
void traverse3(Node n) {…}
}
Pour les arbres binaires, il existe 2 liens et donc 3 parcours élémentaires pour passer par les nœuds :

parcours préfixe : nœud père, nœud fils gauche, nœud fils droit

parcours infixe : nœud fils gauche, nœud père, nœud fils droit

parcours postfixe : nœud fils gauche, nœud fils droit, nœud père
On se propose d’implémenter 3 fonctions récursives pour la mise en œuvre de ces parcours :
traverse1(), traverse2(), et traverse3(). Dans ces fonctions, le passage par un nœud se traduit par
l’appel de sa fonction getID() pour l’affichage de son label. Implémentez ces trois fonctions, pour
vérifier vos fonctions, les listes suivantes donnent les résultats pour les 3 types de parcours concernant
l’arbre à 7 nœuds de la figure précédente. Indiquer les résultats concernant l’arbre à 10 nœuds.



préfixe : a, b, c, d, e, f, g
infixe : c, b, d, a, f, e, g
postfixe : c, d, b, f, g, e, a
12. Bibliographie
On se propose dans une première étape d’implémenter la structure de données arbre binaire. Celle-ci se
limite à la conception d’une classe Node constituée de deux références internes pour les nœuds fils
gauche (l) et droit (r). Implémenter la classe suivante en la complétant, la mettre en œuvre.
class Node {
private Node l,r;
Node(){…}
Node getL(){…}
Node getR(){…}
void set(Node l, Node r) {…}
}
On se propose de gérer automatiquement à la création de chaque nœud un label incrémental de type
caractère. Reprenez votre classe Node, déclarez un label statique et un label dynamique. Votre
constructeur devra affecter votre label dynamique avec le label statique, et incrémenter le label statique.
Ainsi, l’état du label statique sera transmis entre tous les objets instances de la classe Node. Définissez
dans votre classe Node une méthode getID() permettant d’afficher le label dynamique. Prévoir
également une méthode statique clearID() pour la remise à « zéro » du label statique.
Quelques références bibliographiques disponibles sur Internet :
[1] B. Eckel. Thinking in Java. MindView Inc, 2 edition, 2000.
[2] D. Eck. Introduction to Programming Using Java. D.J. Eck, 4.1 edition, 2004.
[3] P. Sestoft. Java Precisely. P. Sestoft, 2000.
Téléchargement