inseadima

Bienvenue Invité sur INSEA DIMA.

Actualité de l’ Institut National de Statistique et d'Economie Appliquée {INSEA}.



Rejoignez le forum, c’est rapide et facile

inseadima

Bienvenue Invité sur INSEA DIMA.

Actualité de l’ Institut National de Statistique et d'Economie Appliquée {INSEA}.

inseadima
Vous souhaitez réagir à ce message ? Créez un compte en quelques clics ou connectez-vous pour continuer.
Le Deal du moment :
Funko POP! Jumbo One Piece Kaido Dragon Form : ...
Voir le deal

Dérivation & Polymorphisme en Java (ou comment réussir s

inseadima :: I N S E A :: Options :: Informatique :: Etude :: JAVA & J2EE :: Java :: JAVA

Aller en bas

Dérivation & Polymorphisme en Java (ou comment réussir s Empty Dérivation & Polymorphisme en Java (ou comment réussir s

Message par s.souhail Mar 30 Jan 2007 - 7:03

Dérivation & Polymorphisme en Java (ou comment réussir son partiel)

Ce petit document est fait pour que vous ne vous plantiez pas durant
le prochain examen de Java. Il vise à vous faire comprendre les
mécanismes parfois un peu flous de la dérivation et du polymorphisme.

Si vous n'êtes pas à l'aise avec ces notions, aucun problème, ce document est fait pour vous !

Si par contre, vous pensez maitriser ces concepts, nous allons tout
de même commencer par le petit problème sur lequel tout le monde se
plante (même moi la première fois).

Le test

Soit deux classes Java A et B, B dérivant de A, avec pleins de fonctions compliquées :
class A
{
int f(A a)
{
return 1;
}
}
class B extends A
{
int f(A a)
{
return 2;
}
int f(B b)
{
return 3;
}
}

Il faut maintenant calculer le retour de chacun de ces appels :

public static void main(String[] astrArgs)
{
A a = new A();
A ab = new B();
B b = new B();

// Partie a
System.out.println( a.f(a) );
System.out.println( a.f(ab) );
System.out.println( a.f(b) );
// Partie ab
System.out.println( ab.f(a) );
System.out.println( ab.f(ab) );
System.out.println( ab.f(b) );
// Partie b
System.out.println( b.f(a) );
System.out.println( b.f(ab) );
System.out.println( b.f(b) );
}

Vous pouvez ensuite comparez vos résultats aves les résultats suivants :

-- Partie a --
1
1
1
-- Partie ab --
2
2
2
-- Partie b --
2
2
3

Si vous ne trouvez pas la même chose pour les parties a et b ? C'est
qu'il faut que vous révisiez votre cours de Java ! Ou mieux... Regardez
ce document jusqu'au bout !

Si vous avez seulement fait une erreur sur le troisième calcul de la
partie ab, alors vous êtes comme 90% des autres, vous avez compris la
dérivation et le polymorphisme, mais pas ses subtilités.

Une classe, comment ça marche ?

Une classe est un type. C'est un peu simpliste mais il faut
commencer par le début. Le fait que ce soit un type signifie donc
qu'elle n'a pas réellement d'existence en dehors de la compilation, ce
n'est qu'une notion utilisée par le programmeur pour se simplifier la
tâche (et ne pas sombrer dans la folie au premier projet).

Donc pour résoudre notre petit problème, nous allons nous tourner
vers le compilateur et essayer de comprendre comment il fonctionne pour
nous faire ces fameuses classes qui au final, n'existent plus.

Compilation des classes

Commençons par le début, le compilateur commence toujours par compiler les classes avant de compiler le code à exécuter.

class A
{
int f(A a)
{
return 1;
}
}

En voyant cela, le compilateur va se créer un objet classe dans
lequel il va enregistrer toutes les informations de cette classes
(attributs, classes internes, méthodes). Ici ce sont les méthodes qui
nous intéressent.

Pour stocker les méthodes, le compilateur va créer une table afin
d'associer au nom de la fonction un pointeur sur son code. Rappelons
qu'un nom de fonction est composé du nom qu'on lui donne plus les types
de ces arguments. Dans notre cas, le compilateur crée donc une table
associant à f(A) le pointeur vers le code de la méthode définie.

Table des méthodes de A
f(A) { return 1; }

class B extends A
{
int f(A a)
{
return 2;
}
int f(B b)
{
return 3;
}
}

Ensuite pour créer la table de B, rien de plus simple, il suffit de
prendre la table de A (puisque B dérive de A) et de la recopier. Il
suffit ensuite de modifier cette table en fonction des méthodes qui
seraient définies ou redéfinies dans cette classe.

Table des méthodes de B
f(A) { return 2; } // Redéfinition, on modifie donc le pointeur
f(B) { return 3; } // Surcharge, c'est donc une nouvelle méthode

Une fois les tables de nos classes connues, on peut passer à l'analyse du code à exécuter.

Compilation des appels de méthodes

Pour compiler un appel de méthode, il suffit de regarder le type déclarés des objets entrant en jeu dans cet appel.

ab.f(a)

Les types mis en œuvre ici nous renseignent sur le fait que nous allons faire un appel à la méthode A.f(A) (ab et a étant tous les deux de type A).

Le compilateur recherche alors la méthode de la classe A la plus à même de répondre à ce besoin : A.f(A) ! Gagné, même pas besoin de s'embéter. f(A) est donc sélectionné pour l'exécution :

ab.Table["f(A)"](a)

On utilise la même technique pour ab.f(ab) que l'on replacera par ab.Table["f(A)"](ab).

Analysons maintenant le code qui pose problème...

ab.f(b)

D'après les types, le compilateur recherche la méthode A.f(B). Cette méthode n'est pas disponible dans la classe A, mais il existe une méthode capable de répondre à nos besoins, la méthode A.f(A).
Cette méthode peut être appelée puisque notre classe B dérive de la
classe A, elle est donc tout à fait utilisable s'il n'y a pas de
méthode A.f(B) (et en l'occurrence il n'y en a pas).

On pourrait se demander pourquoi le compilateur ne va pas chercher
dans les méthodes de B ? Parce que c'est un compilateur... Et un
compilateur ne peut pas deviner plus que les informations que vous lui
donnez, à savoir les déclarations de types. Donc pour lui, ab est un A,
et restera toujours un A.

Le code est donc transformé ainsi :

ab.Table["f(A)"](b)

Voilà, notre programme est maintenant dans la moulinette, près à être exécuté.

Exécution

Reprenons ce que le compilateur nous a demandé d'exécuter pour la partie ab :

ab.Table["f(A)"](a)
ab.Table["f(A)"](ab)
ab.Table["f(A)"](b)

Nous allons donc demander à chacun des objets d'aller fouiller dans
sa table pour nous trouver la fonction qu'il faut... « Eh oh ! Objet ab ! Veux-tu bien m'exécuter la méthode f(A) situé dans ta table ? »

ab regarde alors dans sa table... Mais ab est
un objet de classe B à cet instant, n'oubliez pas, son type ne compte
plus, maintenant c'est un objet, seulement un objet... Il recherche
donc dans sa table et voici ce qu'il voit :

Table des méthodes de l'objet ab
f(A) { return 2; }
f(B) { return 3; }

Eh oui, étant finalement un objet de type B, il contient en interne
ce fameux attribut « table des méthodes » dont il va se servir pour
trouver le code de ses méthodes. En lui demandant d'exécuter le code de
f(A), il va donc exécuter { return 2; }.

Il fera de même pour la deuxième instruction, et fera encore de même pour la troisième...

Ce qu'il faut se rappeler

* Un compilateur ne peut connaitre que les types déclarés.
* Une classe définit toujours une table de méthodes associant noms et pointeurs.
* Un objet contient toujours un pointeur sur cette table comme un
attribut, visible comme tous les autres attributs sur certains
debuggers.
s.souhail
s.souhail
Administrateur
Administrateur

Masculin
Nombre de messages : 1053
Localisation : Île-de-France (Nanterre)
Date d'inscription : 23/10/2006

http://www.inseadima.com

Revenir en haut Aller en bas

Revenir en haut

- Sujets similaires

inseadima :: I N S E A :: Options :: Informatique :: Etude :: JAVA & J2EE :: Java :: JAVA

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum