Comme je vous l'ai déjà dit lors de l'introduction, la notion d'héritage est l'un des fondements de la programmation orientée objet. Grâce à elle, nous pourrons créer des classes héritées (appelées aussi
classes dérivées) de nos classes mères (appelées aussi
classes de base).
Nous pourrons créer autant de classes dérivées, par rapport à notre classe de base, que nous le souhaitons. Nous pourrons aussi nous servir d'une classe dérivée comme d'une classe de base pour créer une autre classe dérivée...
Ce que vous devez savoir aussi, c'est que la notion d'héritage est l'un des piliers de la programmation événementielle (autre nom de
programmation graphique). Ceci sera abordé dans la troisième partie de ce tuto.
Pour l'instant, restons dans la programmation procédurale !
Reprenons l'exemple dont je vous parlais dans l'introduction. Nous allons donc créer une nouvelle classe, nommée
Capitale héritée de
Ville.
Vous vous rendrez vite compte que les objets
Capitale auront tous les attributs et méthodes associés des objets
Ville !
Code : Java1
2
3 | class Capitale extends Ville {
}
|
C'est le mot-clé
extends qui informe notre application que la classe
Capitale est héritée de
Ville. Pour vous le prouver, essayez ce morceau de code dans votre
main :
Code : Java1
2 | Capitale cap = new Capitale();
System.out.println("\n\n"+cap.decrisToi());
|
Vous devriez avoir ceci :
Ceci est bien la preuve que notre objet
Capitale possède les avantages de notre objet
Ville. Les objets hérités peuvent accéder à toutes les méthodes
public de leur classe mère, ici la méthode
decrisToi().
Dans ce cas rudimentaire, notre objet
Capitale ne possède que le constructeur par défaut et les méthodes associées.
En fait, lorsque vous déclarez une classe, si vous ne spécifiez pas de constructeur, la
JVM créera au moment de l'interprétation le constructeur par défaut. C'est le cas ici. De plus, notre classe
Capitale hérite de la classe
Ville, ceci a pour effet que le constructeur de notre objet appelle, de façon tacite, le constructeur de la classe mère.
C'est pour cela que les variables d'instances ont pu être initialisées ! Par contre, dans notre classe
Capitale, nous ne pouvons pas utiliser directement les attributs de la classe
Ville.
Essayez ceci dans votre classe :
Code : Java1
2
3
4
5
6
7 | public class Capitale extends Ville{
public Capitale(){
this.nomVille = "toto";
}
}
|
Vous allez avoir une belle erreur de compilation !
Pourquoi ?
Tout simplement parce les variables de la classe
Ville sont déclarés
private.
Comme seules les méthodes et les variables déclarées
public peuvent être utilisées dans une classe héritée, le compilateur rejette votre demande lorsque vous tentez d'accéder à des ressources privées d'une classe mère !
Comment y remédier tout en gardant la protection sur les variables de ma classe mère ?
C'est ici que je vais vous apprendre un nouveau mot clé :
protected.
En remplaçant la déclaration des variables et des méthodes privées de la classe
Ville en
protected, cela aura pour effet de toujours protéger l'accès à celles-ci depuis du code utilisant un objet
Ville ; mais cela permet aux classes qui héritent de cette dernière d'y avoir accès !
Donc, une fois toutes les variables et méthodes privées de la classe mère re-déclarées en
protected, notre objet
Capitale aura accès à celles-ci !
Ainsi, voici votre classe
Ville revue et corrigée :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221 | public class Ville {
/**
* Variable publique compteur d'instances
*/
public static int nbreInstance = 0;
/**
* Variable privée compteur d'instances
*/
protected static int nbreInstanceBis = 0;
/**
* Stocke le nom de notre ville
*/
protected String nomVille;
/**
* Stocke le nom du pays de notre ville
*/
protected String nomPays;
/**
* Stocke le nombre d'habitants de notre ville
*/
protected int nbreHabitant;
/**
* Stocke le type de notre ville
*/
protected char categorie;
/**
* Constructeur par défaut
*/
public Ville(){
//On incrémente nos variables à chaque appel au constructeur
nbreInstance++;
nbreInstanceBis++;
nomVille = "Inconnu";
nomPays = "Inconnu";
nbreHabitant = 0;
this.setCategorie();
}
/**
* Constructeur d'initialisation
* @param pNom
* Nom de la Ville
* @param pNbre
* Nombre d'habitants
* @param pPays
* Nom du pays
*/
public Ville(String pNom, int pNbre, String pPays)
{
nbreInstance++;
nbreInstanceBis++;
nomVille = pNom;
nomPays = pPays;
nbreHabitant = pNbre;
this.setCategorie();
}
//*****************************************************************************************
// ACCESSEURS
//*****************************************************************************************
public static int getNombreInstanceBis()
{
return nbreInstanceBis;
}
/**
* Retourne le nom de la ville
* @return le nom de la ville
*/
public String getNom()
{
return nomVille;
}
/**
* Retourne le nom du pays
* @return le nom du pays
*/
public String getNomPays()
{
return nomPays;
}
/**
* Retourne le nombre d'habitants
* @return nombre d'habitants
*/
public int getNombreHabitant()
{
return nbreHabitant;
}
/**
* Retourne la catégorie de la ville
* @return catégorie de la ville
*/
public char getCategorie()
{
return categorie;
}
//*****************************************************************************************
// MUTATEURS
//*****************************************************************************************
/**
* Définit le nom de la ville
* @param pNom
* nom de la ville
*/
public void setNom(String pNom)
{
nomVille = pNom;
}
/**
* Définit le nom du pays
* @param pPays
* nom du pays
*/
public void setNomPays(String pPays)
{
nomPays = pPays;
}
/**
* Définit le nombre d'habitants
* @param nbre
* nombre d'habitants
*/
public void setNombreHabitant(int nbre)
{
nbreHabitant = nbre;
this.setCategorie();
}
//*****************************************************************************************
// METHODES DE CLASSE
//*****************************************************************************************
/**
* Définit la catégorie de la ville
*/
protected void setCategorie() {
if (this.nbreHabitant < 10000000) {
if (this.nbreHabitant < 5000000) {
if (this.nbreHabitant < 1000000) {
if (this.nbreHabitant < 500000) {
if (this.nbreHabitant < 100000) {
if (this.nbreHabitant < 10000) {
if (this.nbreHabitant < 1000) {
if (this.nbreHabitant > 0)
this.categorie = 'A';
else
this.categorie = '?';
} else
this.categorie = 'B';
} else
this.categorie = 'C';
} else
this.categorie = 'D';
} else
this.categorie = 'E';
} else
this.categorie = 'F';
} else
this.categorie = 'G';
} else
this.categorie = 'H';
}
/**
* Retourne la description de la ville
* @return description ville
*/
public String decrisToi(){
return "\t"+this.nomVille+" est une ville de "+this.nomPays+", elle comporte : "+this.nbreHabitant+
" => elle est donc de catégorie : "+this.categorie;
}
/**
* Retourne une chaîne de caractères selon le résultat de la comparaison
* @param v1
* objet Ville
* @return comparaison de deux ville
*/
public String comparer(Ville v1){
String str = new String();
if (v1.getNombreHabitant() > this.nbreHabitant)
str = v1.getNom()+" est une ville plus peuplée que "+this.nomVille;
else
str = this.nomVille+" est une ville plus peuplée que "+v1.getNom();
return str;
}
}
|
Un point important avant de continuer.
Contrairement au C++, Java ne gère pas les héritages multiples : une classe dérivée (ou encore classe fille) ne peut hériter que d'une seule classe mère !
Vous n'aurez donc
JAMAIS ce genre de classe :
Code : Java1
2 | class Toto extends Titi, Tutu{
}
|
À présent, continuons la construction de notre objet hérité !
Il va de soi que cette opération va se concrétiser avec nos chers constructeurs.
Notre classe
Ville ne changera plus d'un poil, mais nous allons par contre agrémenter notre classe
Capitale.
Comme je vous l'avais dit, ce qui différenciera nos objets
Capitale de nos objets
Ville sera la présence d'un champ nouveau : le nom du président. Ce qui signifie que nous devons créer un constructeur par défaut et un constructeur d'initialisation pour notre objet
Capitale.
Avant de foncer tête baissée, il faut que vous sachiez que nous pouvons faire appel aux variables de la classe mère dans nos constructeurs... Et ceci grâce au mot-clé
super. Ce qui aura pour effet de récupérer les éléments de l'objet de base, et de les envoyer à notre objet hérité.
Démonstration :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13 | class Capitale extends Ville {
private String president;
/**
*Constructeur par défaut
*/
public Capitale(){
//Ce mot clé appelle le constructeur de la classe mère.
super();
president = "aucun";
}
}
|
Si vous testez à nouveau le petit exemple que je vous avais montré un peu plus haut, vous vous apercevrez que le constructeur par défaut fonctionne toujours... Et pour cause, car ici,
super() appelle le constructeur par défaut de l'objet
Ville dans notre constructeur de
Capitale, puis nous avons rajouté le président par défaut.
Mais la méthode
decrisToi() ne prend pas en compte le nom du président...
Eh bien le mot-clé
super() fonctionne aussi pour les méthodes de classe. Ce qui nous donne une méthode
decrisToi() un peu différente... car nous allons rajouter le champ président dans notre description.
Voyez plutôt :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Capitale extends Ville {
private String president;
/**
*Constructeur par défaut
*/
public Capitale(){
//Ce mot clé appel le constructeur de la classe mère.
super();
president = "aucun";
}
/**
*Description d'une capitale
*/
public String decrisToi(){
String str = super.decrisToi() + "\n \t ==>>" + this.president + " est son président";
//Pour bien vous montrer, j'ai ajouté la ligne ci-dessous, mais vous n'êtes pas obligés...
System.out.println("Invocation de super.decrisToi()");
System.out.println(super.decrisToi());
return str;
}
}
|
Si vous relancez les mêmes instructions présentes dans le
main, depuis le début, vous aurez quelque chose comme ça :
Il y a du mieux, non ?
Bon, d'accord, nous n'avons toujours pas fait le constructeur d'initialisation de
Capitale... Eh bien ? Qu'est-ce que nous attendons ?
Code complet de notre classe
Capitale
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | public class Capitale extends Ville {
private String president;
/**
*Constructeur par défaut
*/
public Capitale(){
//Ce mot clé appelle le constructeur de la classe mère.
super();
president = "aucun";
}
/**
* Constructeur d'initialisation de capitale
*/
public Capitale(String nom, int hab, String pays, String president){
super(nom, hab, pays);
this.president = president;
}
/**
*Description d'une capitale
*/
public String decrisToi(){
String str = super.decrisToi() + "\n \t ==>>" + this.president + " est son président";
return str;
}
/**
* @return le nom du président
*/
public String getPresident() {
return president;
}
/**
* Définit le nom du président
* @param president
*/
public void setPresident(String president) {
this.president = president;
}
}
|
Donc : dans le constructeur d'initialisation de notre
Capitale, vous remarquez la présence de
super(nom, hab, pays);. Difficile de ne pas le voir...
Ici, cette ligne de code joue le même rôle que pour le constructeur par défaut. Sauf qu'ici, le constructeur auquel
super fait référence prend trois paramètres... donc
super doit prendre ces paramètres.
Si vous ne lui mettez aucun paramètre, super() renverra le constructeur par défaut de la classe Ville...
Testez ce code :
Code : Java1
2 | Capitale cap = new Capitale("Paris", 654987, "France", "Sarko");
System.out.println("\n"+cap.decrisToi());
|
Vous devriez voir apparaître sous vos yeux ébahis :

:
Je vais encore vous interpeler mais... ce que vous venez de faire sur la méthode
decrisToi() s'appelle :
une méthode polymorphe, ce qui nous conduit tout de suite à la suite

!
Voici encore un des concepts fondamentaux de la programmation orientée objet :
Le polymorphisme.
Ce concept complète parfaitement celui de l'héritage.
Comme vous l'avez vu, le polymorphisme n'est pas si compliqué qu'il pourrait sembler l'être !
Nous pouvons le caractériser en disant qu'il permet de manipuler des objets sans vraiment connaître leur type.
Dans notre exemple, vous avez vu qu'il suffisait d'utiliser la méthode
decrisToi() sur un objet
Ville ou sur un objet
Capitale, et cela sans se soucier de leur type. On pourrait construire un tableau d'objets, et appeler la
decrisToi() sans se soucier de son contenu : villes, capitales, ou les deux.
D'ailleurs nous allons le faire. Essayez ce code :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | //Def d'un tableau de ville null
Ville[] tableau = new Ville[6];
//Définition d'un tableau de noms de Villes et d'un tableau de nombres d'habitants
String[] tab = {"Marseille", "lille", "caen", "lyon", "paris", "nantes"};
int[] tab2 = {123456, 78456, 654987, 75832165, 1594,213};
/* Les 3 premiers éléments du tableau seront des Villes,
et le reste, des capitales
*/
for(int i = 0; i < 6; i++){
if (i <3){
Ville V = new Ville(tab[i], tab2[i], "france" );
tableau[i] = V;
}
else{
Capitale C = new Capitale(tab[i], tab2[i], "france", "Sarko");
tableau[i] = C;
}
}
//il ne nous reste plus qu'à décrire tout notre tableau !
for(Ville v : tableau){
System.out.println(v.decrisToi()+"\n");
}
|
Résultat :
Une petite nouveauté, la création d'un tableau d'un certain nombre d'entrées
vides. Rien de bien compliqué à cela, vous voyez que la syntaxe est toute simple.
Nous créons un tableau de villes, avec des villes et des capitales (nous avons le droit de faire ça, car les objets
Capitale sont aussi des objets
Ville...

), dans notre première boucle
for.
Dans la seconde, nous affichons la description de ces objets... et vous voyez que la méthode
polymorphe decrisToi() fait bien son travail !
Dans ta boucle, tu n'utilises que des objets Ville.
Tout à fait. On appelle ceci la
covariance des variables !
Cela signifie qu'une variable objet peut contenir un objet qui hérite du type de cette variable. Dans notre cas, un objet de type
Ville peut contenir un objet de type
Capitale. Dans ce cas, on dit que
Ville est la
super classe par rapport à
Capitale.
La covariance est efficace dans le cas où la classe héritant redéfinit certaines des méthodes de sa super classe.
Attention à ne pas confondre la surcharge de méthode avec une méthode polymorphe.
Pour dire les choses simplement :
- une méthode surchargée a des paramètres que la méthode de base n'a pas, ou a le même nombre de paramètres, mais de types différents ;
- une méthode polymorphe a un squelette identique à celle de base, mais un traitement différent. Celle-ci fait référence à une autre classe et donc, par extension, à une autre instance de cette classe. On peut dire que les méthodes polymorphes sont typiques des classes héritées !
Vous devez savoir encore une chose sur l'héritage. Lorsque vous créez une classe (
Ville par exemple), celle-ci est une classe héritée de la classe
Object présente dans Java.
Cette écriture est donc tout à fait correcte :
Code : Java1
2
3 | class Ville extends Object{
..........
}
|
Toutes nos classes héritent donc des méthodes de la classe
Object, comme
equals(), qui prend un objet en paramètre, et qui permet de tester l'égalité d'objets. Vous vous en êtes d'ailleurs servis pour tester l'égalité de
String() dans la première partie de ce tuto.
Donc, si nous redéfinissons une méthode de la classe
Object dans la classe
Ville, nous pourrions utiliser la covariance.
La méthode de la classe
Object qui est le plus souvent redéfinie est la méthode
toString(), qui retourne un
String et qui a pour rôle de décrire l'objet en question (tout comme notre méthode
decrisToi()). Nous allons donc faire un copier / coller de notre procédure de la méthode
decrisToi() dans une nouvelle méthode de la classe
Ville :
toString().
Voici :
Code : Java1
2
3
4 | public String toString(){
return "\t"+this.nomVille+" est une ville de "+this.nomPays+", elle comporte : "+this.nbreHabitant+
" => elle est donc de catégorie : "+this.categorie;
}
|
Nous faisons de même dans la classe
Capitale :
Code : Java1
2
3
4 | public String toString(){
String str = super.decrisToi() + "\n \t ==>>" + this.president + " est son président";
return str;
}
|
Maintenant, testez ce code :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | //Def d'un tableau de ville null
Ville[] tableau = new Ville[6];
//Définition d'un tableau de noms de Villes et d'un tableau de nombres d'habitants
String[] tab = {"Marseille", "lille", "caen", "lyon", "paris", "nantes"};
int[] tab2 = {123456, 78456, 654987, 75832165, 1594,213};
/* Les 3 premiers éléments du tableau seront des Villes
et le reste des capitales
*/
for(int i = 0; i < 6; i++){
if (i <3){
Ville V = new Ville(tab[i], tab2[i], "france" );
tableau[i] = V;
}
else{
Capitale C = new Capitale(tab[i], tab2[i], "france", "Sarko");
tableau[i] = C;
}
}
//il ne nous reste plus qu'à décrire tout notre tableau !
for(Object obj : tableau){
System.out.println(obj.toString()+"\n");
}
|
Vous pouvez constater qu'il fait exactement la même chose que le précédent ; nous n'avons pas à nous soucier du type d'objet pour afficher sa description. Je pense que vous commencez à apercevoir la puissance de Java !
ATTENTION : si vous ne redéfinissez pas ou ne polymorphez pas une méthode d'une classe mère dans une classe fille (exemple de toString()), à l'appel de celle-ci avec un objet fille, c'est la méthode de la classe mère qui sera invoquée ! !
Une précision : si vous avez un objet
v de type
Ville par exemple, que vous n'avez pas redéfini la méthode
toString() et que vous testez ce code :
Code : Java
Cette instruction appelle automatiquement la méthode
toString() de la classe
Object ! Mais vu que vous avez redéfini la méthode
toString() dans votre classe
Ville, ces deux instructions sont équivalentes :
Code : Java1
2
3 | System.out.println(v.toString());
//Est équivalent à
System.out.println(v);
|
Pour plus de clarté, je conserverai la première syntaxe ! Mais vous devez savoir ceci !
En clair, vous avez accès aux méthodes
public et
protected de la classe
Object dès que vous créez une classe objet (héritage tacite).
Vous pouvez donc utiliser les dites méthodes ; mais si vous ne les redéfinissez pas... l'invocation se fera sur la classe mère avec les traitements de la classe mère.
Si vous voulez un bel exemple de ce que je viens de vous dire, vous n'avez qu'à retirer la redéfinition de la méthode
toString() dans les classes
Ville et
Capitale : vous verrez que le code de la méthode
main fonctionne toujours, mais le résultat n'est plus du tout le même car, à l'appel de la méthode
toString(), la JVM va regarder si celle-ci existe dans la classe appelante et, si elle ne la trouve pas, elle remonte dans la hiérarchie jusqu'à arriver à la classe
Object...
Attention 2 : ce code fonctionne bien mais, si vous remplacez la méthode
toString() par la méthode
decrisToi(), le programme ne fonctionne plus...

Et cela pour une bonne raison : la méthode
decrisToi() n'existe pas dans la classe
Object.
Vous devez savoir qu'une méthode est invoquable par un objet QUE si celui-ci définit la dite méthode !
Donc, ce code ne fonctionne pas :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | public class Sdz1 {
public static void main(String[] args){
//Def d'un tableau de ville null
Ville[] tableau = new Ville[6];
//Définition d'un tableau de noms de Villes et d'un tableau de nombres d'habitants
String[] tab = {"Marseille", "lille", "caen", "lyon", "paris", "nantes"};
int[] tab2 = {123456, 78456, 654987, 75832165, 1594,213};
/* Les 3 premiers éléments du tableau seront des Villes,
et le reste, des capitales
*/
for(int i = 0; i < 6; i++){
if (i <3){
Ville V = new Ville(tab[i], tab2[i], "france" );
tableau[i] = V;
}
else{
Capitale C = new Capitale(tab[i], tab2[i], "france", "Sarko");
tableau[i] = C;
}
}
//il ne nous reste plus qu'à décrire tout notre tableau !
for(Object v : tableau){
System.out.println(v.decrisToi()+"\n");
}
}
}
|
Pour que cela fonctionne, vous devez dire à la JVM que la référence de type
Object est en fait une référence de type
Ville. Comme ceci :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | public class Sdz1 {
public static void main(String[] args){
//Def d'un tableau de ville null
Ville[] tableau = new Ville[6];
//Définition d'un tableau de noms de Villes et d'un tableau de nombres d'habitants
String[] tab = {"Marseille", "lille", "caen", "lyon", "paris", "nantes"};
int[] tab2 = {123456, 78456, 654987, 75832165, 1594,213};
/* Les 3 premiers éléments du tableau seront des Villes,
et le reste, des capitales
*/
for(int i = 0; i < 6; i++){
if (i <3){
Ville V = new Ville(tab[i], tab2[i], "france" );
tableau[i] = V;
}
else{
Capitale C = new Capitale(tab[i], tab2[i], "france", "Sarko");
tableau[i] = C;
}
}
//il ne nous reste plus qu'à décrire tout notre tableau !
for(Object v : tableau){
System.out.println(((Ville)v).decrisToi()+"\n");
}
}
}
|
Vous transtypez la référence
v en
Ville par cette syntaxe :
Code : Java
Ici, l'ordre des opérations s'effectue comme ceci :
- vous transtypez la référence v en Ville
- vous appliquez la méthode decrisToi() à la référence appelante, ici, une référence Object changée en Ville.
Vous voyez donc l'intérêt des méthodes polymorphes. Avec celles-ci, vous n'avez plus à vous soucier du type de variable appelante ; cependant, n'utilisez le type
Object qu'avec parcimonie.
Il existe encore un type de méthode dont je ne vous ai pas encore parlé. Il s'agit des méthodes dites
final. Ces méthodes sont figées et vous ne pourrez
JAMAIS redéfinir une méthode déclarée
final. Un exemple de ce type de méthode est la méthode
getClass() de la classe
Object : vous ne pourrez pas redéfinir cette méthode et heureusement, car celle-ci retourne un objet
Capitale dans le fonctionnement de Java (nous verrons cela plus tard).
Il existe aussi des classes déclarées final. Vous avez compris que ces classes sont immuables... Et vous ne pouvez donc pas faire hériter un objet d'une classe déclarée final !