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 : Java | 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 : Java | 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 : Java | public String toString(){
String str = super.toString() + "\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 : Java | 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 ladite 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 !