Vous êtes sur la page 1sur 32

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

Traducteur : Jrome DANNOVILLE 21.06.01 - Version 1.1 : - Correction du code html, guillemets "texte" -> texte , texte: -> texte :, texte? -> texte ? [Armel]. 14.06.01 - Version 1.0 : - Relecture en cours [JeromeDanno]. Texte original : -Thinking in Java, 2nd edition, Revision 10 2000 by Bruce Eckel

7: Polymorphisme
Le polymorphisme est la troisime caractristique essentielle d'un langage de programmation orient objet, aprs l'abstraction et l'hritage.
Le polymorphisme fournit une autre dimension sparant la partie interface de l'implmentation qui permet de dcoupler le quoi du comment. Le polymorphisme amliore l'organisation du code et sa lisibilit de mme qu'il permet la cration de programmes extensible qui peuvent voluer non seulement pendant la cration initiale du projet mais galement quand des fonctions nouvelles sont dsires. L'encapsulation cre de nouveaux types de donnes en combinant les caractristiques et les comportements. Cacher la mise en œuvre permet de sparer l'interface de l'implmentation en mettant les dtails privs [private]. Cette sorte d'organisation mcanique est bien comprise par ceux viennent de la programmation procdurale. Mais le polymorphisme s'occupe de dcoupler au niveau des types. Dans le chapitre prcdant, nous avons vu comment l'hritage permet le traitement d'un objet comme son propre type ou son type de base. Cette capacit est critique car elle permet beaucoup de types (driv d'un mme type de base) d'tre traits comme s'ils n'taient qu'un type, et permet a un seul morceau de code de traiter sans distinction tous ces types diffrents. L'appel de mthode polymorphe permet un type d'exprimer sa distinction par rapport un autre, un type semblable, tant qu'ils drivent tous les deux d'un mme type de base. Cette distinction est exprime travers des diffrences de comportement des mthodes que vous pouvez appeler par la classe de base. Dans ce chapitre, vous allez comprendre le polymorphisme [galement appel en anglais dynamic binding ou late binding ou encore run-time binding] en commenant par les notions de base, avec des exemples simples qui vacuent tout ce qui ne concerne pas le comportement polymorphe du programme.

Upcasting
Dans le chapitre 6 nous avons vu qu'un objet peut tre manipul avec son propre type ou bien comme un objet de son type de base. Prendre la rfrence d'un objet et l'utiliser comme une rfrence sur le type de base est appel upcasting (transtypage en franais) , en raison du mode de reprsentation des arbres d'hritages avec la classe de base en haut.

1 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

On avait vu le problme reprit ci-dessous apparatre:


//: c07:music:Music.java // Hritage & upcasting. class Note { private int value; private Note(int val) { value = val; } public static final Note MIDDLE_C = new Note(0), C_SHARP B_FLAT } // Etc. class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } } = new Note(1), = new Note(2);

// Les objets Wind sont des instruments // car ils ont la mme interface: class Wind extends Instrument { // Redfinition de la mthode de l'interface: public void play(Note n) { System.out.println("Wind.play()"); } }

public class Music { public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind();

2 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

tune(flute); // Upcasting } } ///:~

La mthode Music.tune() accepte une rfrence sur un Instrument, mais galement sur tout ce qui drive de Instrument. Dans le main(), ceci se matrialise par une rfrence sur un objet Wind qui est passe tune(), sans qu'un changement de type (un cast) soit ncessaire. Ceci est correct; l'interface dans Instrument doit exister dans Wind, car Wind hrite de la classe Instrument. Utiliser l'upcast de Wind vers Instrument peut rtrcir cette interface, mais au pire elle est rduite l'interface complte d'Instrument.

Pourquoi utiliser l'upcast?


Ce programme pourrait vous sembler trange. Pourquoi donc oublier intentionnellement le type d'un objet? C'est ce qui arrive quand on fait un upcast, et il semble beaucoup plus naturel que tune() prenne tout simplement une rfrence sur Wind comme argument. Ceci introduit un point essentiel: en faisant a, il faudrait crire une nouvelle mthode tune() pour chaque type de Instrument du systme. Supposons que l'on suive ce raisonnement et que l'on ajoute les instruments cordes [Stringed] et les cuivres [Brass]:
//: c07:music2:Music2.java // Surcharger plutt que d'utiliser l'upcast.

class Note { private int value; private Note(int val) { value = val; } public static final Note MIDDLE_C = new Note(0), C_SHARP = new Note(1), B_FLAT = new Note(2); } // Etc.

class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } }

class Wind extends Instrument { public void play(Note n) {

3 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

System.out.println("Wind.play()"); } }

class Stringed extends Instrument { public void play(Note n) { System.out.println("Stringed.play()"); } }

class Brass extends Instrument { public void play(Note n) { System.out.println("Brass.play()"); } }

public class Music2 { public static void tune(Wind i) { i.play(Note.MIDDLE_C); } public static void tune(Stringed i) { i.play(Note.MIDDLE_C); } public static void tune(Brass i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); Stringed violin = new Stringed(); Brass frenchHorn = new Brass(); tune(flute); // Pas d' upcast tune(violin); tune(frenchHorn); }

4 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

} ///:~

Ceci fonctionne, mais avec un inconvnient majeur: il vous faut crire des classes spcifique chaque ajout d'une classe Instrument. Ceci implique davantage de programmation dans un premier temps, mais galement beaucoup de travail venir si l'on dsire ajouter une nouvelle mthode comme tune() ou un nouveau type d' Instrument. Sans parler du compilateur qui est incapable de signaler l'oubli de surcharge de l'une de vos mthodes qui fait que toute cette construction utilisant les types devient assez complique. Ne serait-il pas plus commode d'crire une seule mthode qui prenne la classe de base en argument plutt que toutes les classes drives spcifiques? Ou encore, ne serait-il pas agrable d'oublier qu'il y a des classes drives et d'crire votre code en ne s'adressant qu' la classe de base? C'est exactement ce que le polymorphisme vous permet de faire. Souvent, ceux qui viennent de la programmation procdurale sont drouts par le mode de fonctionnement du polymorphisme.

The twist
L'ennui avec Music.java peut tre visualis en excutant le programme. L'output est Wind.play(). C'est bien sr le rsultat attendu, mais il n'est pas vident de comprendre le fonctionnement.. Examinons la mthode tune():
public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); }

Elle prend une rfrence sur un Instrument en argument. Comment le compilateur peut-il donc deviner que cette rfrence sur un Instrument pointe dans le cas prsent sur un objet Wind et non pas un objet Brass ou un objet Stringed? H bien il ne peut pas. Mieux vaut examiner le mcanisme d'association [binding] pour bien comprendre la question souleve.

Liaison de l'appel de mthode


Raccorder un appel de mthode avec le corps de cette mthode est appel association. Quand cette association est ralise avant l'excution du programme (par le compilateur et l'diteur de lien, s'il y en a un), c’est de l’association prdfinie. Vous ne devriez pas avoir dj entendu ce terme auparavant car avec les langages procduraux, c'est impos . Les compilateurs C n'ont qu'une sorte d'appel de mthode, l'association prdfinie. Ce qui droute dans le programme ci-dessus tourne autour de l'association prdfinie car le compilateur ne peut pas connatre la bonne mthode a appeler lorsqu'il ne dispose que d'une rfrence sur Instrument. La solution s'appelle l' association tardive, ce qui signifie que l'association est effectue l'excution en se basant sur le type de l'objet. L'association tardive est galement appele association dynamique [dynamic binding ou run-time binding] . Quand un langage implmente l'association dynamique, un mcanisme doit tre prvu pour dterminer le type de l'objet lors de l'excution et pour appeler ainsi la mthode approprie. Ce qui veut dire que le compilateur ne connat toujours pas le type de l'objet, mais le mcanisme d'appel de mthode trouve et effectue l'appel vers le bon corps de mthode. Les mcanismes d'association tardive varient selon les

5 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

langages, mais vous pouvez deviner que des informations relatives au type doivent tre implantes dans les objets. Toutes les associations de mthode en Java utilisent l'association tardive moins que l'on ait dclar une mthode final. Cela signifie que d'habitude vous n'avez pas vous proccuper du dclenchement de l'association tardive, cela arrive automatiquement. Pourquoi dclarer une mthode avec final? On a vu dans le chapitre prcdant que cela empche quelqu'un de redfinir cette mthode. Peut-tre plus important, cela coupe effectivement l'association dynamique, ou plutt cela indique au compilateur que l'association dynamique n'est pas ncessaire. Le compilateur gnre du code lgrement plus efficace pour les appels de mthodes spcifis final. Cependant, dans la plupart des cas cela ne changera pas la performance globale de votre programme; mieux vaut utiliser final la suite d'une dcision de conception, et non pas comme tentative d'amlioration des performances.

Produire le bon comportement


Quand vous savez qu'en Java toute association de mthode se fait de manire polymorphe par l'association tardive, vous pouvez crire votre code en vous adressant la classe de base en sachant que tous les cas des classes drives fonctionneront correctement avec le mme code. Dit autrement, vous envoyez un message un objet et laissez l'objet trouver le comportement adquat. L'exemple classique utilise en POO est celui de la forme [shape]. Cet exemple est gnralement utilis car il est facile visualiser, mais peut malheureusement sous-entendre que la POO est cantonne au domaine graphique, ce qui n'est bien sr pas le cas. Dans cet exemple il y a une classe de base appele Shape et plusieurs types drivs: Circle, Square, Triangle, etc. Cet exemple marche trs bien car il est naturel de dire qu'un cercle est une sorte de forme. Le diagramme d'hritage montre les relations : Image

l'upcast pourrait se produire dans une instruction aussi simple que :


Shape s = new Circle();

On cr un objet Circle et la nouvelle rfrence est assigne un Shape, ce qui semblerait tre une erreur (assigner un type un autre), mais qui est valide ici car un Cercle [Circle] est par hritage une sorte de forme [Shape]. Le compilateur vrifie la lgalit de cette instruction et n'met pas de message d'erreur. Supposons que vous appeliez une des mthode de la classe de base qui a t redfinie dans les classes drives :
s.draw();

De nouveau, vous pourriez vous attendre ce que la mthode draw() de Shape soit appele parce que c'est aprs tout une rfrence sur Shape. Alors comment le compilateur peut-il faire une autre liaison? Et malgr tout

6 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

le bon Circle.draw() est appel grce la liaison tardive (polymorphisme). L'exemple suivant le montre de manire lgrement diffrente :
//: c07:Shapes.java // Polymorphisme en Java.

class Shape { void draw() {} void erase() {} }

class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } }

class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } }

class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() {

7 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

System.out.println("Triangle.erase()"); } }

public class Shapes { public static Shape randShape() { switch((int)(Math.random() * 3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } public static void main(String[] args) { Shape[] s = new Shape[9]; // Remplissage du tableau avec des formes [shapes]: forint i = 0; i < s.length; i++) s[i] = randShape(); // Appel polymorphe des mthodes: forint i = 0; i < s.length; i++) s[i].draw(); } } ///:~

La classe de base Shape tablit l'interface commune pour tout ce qui hrite de Shape &mdash; C'est dire, toutes les formes (shapes en anglais) peuvent tre dessines [draw] et effaces [erase]. Les classes drives redfinissent ces mthodes pour fournir un comportement unique pour chaque type de forme spcifique. La classe principale Shapes contient une mthode statique randShape () qui rend une rfrence sur un objet slectionn de manire alatoire chaque appel. Remarquez que la gnralisation se produit sur chaque instruction return, qui prend une rfrence sur un cercle [circle], un carr [square], ou un triangle et la retourne comme le type de retour de la mthode, en l'occurrence Shape. Ainsi chaque appel de cette mthode vous ne pouvez pas voir quel type spcifique vous obtenez, puisque vous rcuprez toujours une simple rfrence sur Shape. Le main() a un tableau de rfrences sur Shape remplies par des appels a randShape(). Tout ce que l'on sait dans la premire boucle c'est que l'on a des objets formes [Shapes], mais on ne sait rien de plus (pareil pour le compilateur). Cependant, quand vous parcourez ce tableau en appelant draw() pour chaque rfrence dans la

8 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

seconde boucle, le bon comportement correspondant au type spcifique se produit comme par magie, comme vous pouvez le constater sur l'output de l'exemple :
Circle.draw() Triangle.draw() Circle.draw() Circle.draw() Circle.draw() Square.draw() Triangle.draw() Square.draw() Square.draw()

Comme toutes les formes sont choisies alatoirement chaque fois, vous obtiendrez bien sr des rsultats diffrents. L'intrt de choisir les formes alatoirement est d'illustrer le fait que le compilateur ne peut avoir aucune connaissance spciale lui permettant de gnrer les appels corrects au moment de la compilation. Tous les appels draw() sont raliss par liaison dynamique.

Extensibilit
Revenons maintenant l'exemple sur l'instrument de musique. En raison du polymorphisme, vous pouvez ajouter autant de nouveaux types que vous voulez dans le systme sans changer la mthode tune(). Dans un programme orient objet bien conu, la plupart ou mme toutes vos mthodes suivront le modle de tune() et communiqueront seulement avec l'interface de la classe de base. Un tel programme est extensible parce que vous pouvez ajouter de nouvelles fonctionnalits en hritant de nouveaux types de donnes de la classe de base commune. Les mthodes qui utilisent l'interface de la classe de base n'auront pas besoin d'tre retouches pour intgrer de nouvelles classes. Regardez ce qui se produit dans l'exemple de l'instrument si vous ajoutez des mthodes dans la classe de base et un certain nombre de nouvelles classes. Voici le schma : Image

Toutes ces nouvelles classes fonctionnent correctement avec la vieille mthode tune(), sans modification. Mme si tune() est dans un fichier spar et que de nouvelles mthodes sont ajoutes l'interface de Instrument, tune() fonctionne correctement sans recompilation. Voici l'implmentation du diagramme prsent ci-dessus :
//: c07:music3:Music3.java // Un programme extensible.

9 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

import java.util.*;

class Instrument { public void play() { System.out.println("Instrument.play()"); } public String what() { return

"Instrument";

}
public void adjust() {} }

class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return public void adjust() {} }

"Wind"; }

class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return public void adjust() {} }

"Percussion"; }

class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return

"Stringed"; }

10 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

public void adjust() {} }

class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } }

class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return }

"Woodwind"; }

public class Music3 { // Indpendants des types, ainsi les nouveaux types // ajouts au systme marchent toujours bien: static void tune(Instrument i) { // ... i.play(); } static void tuneAll(Instrument[] e) { forint i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Upcasting pendant l'ajout au tableau:

11 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); } } ///:~

Les nouvelles mthodes sont what(), qui renvoie une rfrence sur une String dcrivant la classe, et adjust(), qui fournit un moyen d'ajuster chaque instrument. Dans le main(), quand on met quelque chose dans le tableau d' Instrument, on upcast automatiquement en Instrument. Vous pouvez constater que la mthode tune() ignore fort heureusement tous les changements qui sont intervenus autour d'elle, et pourtant cela marche correctement. C'est exactement ce que le polymorphisme est cens fournir. Vos modifications ne peuvent abmer les parties du programme qui ne devraient pas tre affectes. Dit autrement, le polymorphisme est une des techniques majeures permettant au programmeur de sparer les choses qui changent des choses qui restent les mmes.

Redfinition et Surcharge
Regardons sous un angle diffrent le premier exemple de ce chapitre. Dans le programme suivant, l'interface de la mthode play() est change dans le but de la redfinir, ce qui signifie que vous n'avez pas redfinie la mthode, mais plutt surcharge. Le compilateur vous permet de surcharger des mthodes, il ne proteste donc pas. Mais le comportement n'est probablement pas celui que vous vouliez. Voici l'exemple :
//: c07:WindError.java // Changement accidentel de l'interface. class NoteX { public static final int MIDDLE_C = 0, C_SHARP = 1, C_FLAT = 2; }

class InstrumentX { public void play(int NoteX) { System.out.println("InstrumentX.play()"); } }

12 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

class WindX extends InstrumentX { // OUPS! L'interface de la mthode change: public void play(NoteX n) { System.out.println("WindX.play(NoteX n)"); } }

public class WindError { public static void tune(InstrumentX i) { // ... i.play(NoteX.MIDDLE_C); } public static void main(String[] args) { WindX flute = new WindX(); tune(flute); // Ce n'est pas le comportement souhait! } } ///:~

Il y a un autre aspect droutant ici. Dans InstrumentX, la mthode play() a pour argument un int identifi par NoteX. Bien que NoteX soit un nom de classe, il peut galement tre utilis comme identificateur sans erreur. Mais dans WindX, play() prend une rfrence de NoteX qui a pour identificateur n (bien que vous puissiez mme crire play(NoteX NoteX) sans erreur). En fait, il s'avre que le programmeur a dsir redfinir play() mais s'est tromp de type. Du coup le compilateur a suppos qu'une surcharge tait souhaite et non pas une redfinition. Remarquez que si vous respectez la convention standard de nommage Java, l'identificateur d'argument serait noteX ('n' minuscule), ce qui le distinguerait du nom de la classe. Dans tune, le message play() est envoy l'InstrumentX i, avec comme argument un de membres de NoteX (MIDDLE_C). Puisque NoteX contient des dfinitions d'int, ceci signifie que c'est la version avec int de la mthode play(), dornavant surcharge, qui est appele. Comme elle n'a pas t redfinie, c'est donc la mthode de la classe de base qui est utilise. L'output est le suivant :
InstrumentX.play()

Ceci n'est pas un appel polymorphe de mthode. Ds que vous comprenez ce qui se passe, vous pouvez corriger le problme assez facilement, mais imaginez la difficult pour trouver l'anomalie si elle est enterre dans un gros programme.

13 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

Classes et mthodes abstraites


Dans tous ces exemples sur l'instrument de musique, les mthodes de la classe de base Instrument taient toujours factices. Si jamais ces mthodes sont appeles, c'est que vous avez fait quelque chose de travers. C'est parce que le rle de la classe Instrument est de crer une interface commune pour toutes les classes drives d'elle. La seule raison d'avoir cette interface commune est qu'elle peut tre exprime diffremment pour chaque sous-type diffrent. Elle tablit une forme de base, ainsi vous pouvez dire ce qui est commun avec toutes les classes drives. Une autre manire d'exprimer cette factorisation du code est d'appeler Instrument une classe de base abstraite (ou simplement une classe abstraite). Vous crez une classe abstraite quand vous voulez manipuler un ensemble de classes travers cette interface commune. Toutes les mthodes des classes drives qui correspondent la signature de la dclaration de classe de base seront appeles employant le mcanisme de liaison dynamique. (Cependant, comme on l'a vu dans la dernire section, si le nom de la mthode est le mme comme la classe de base mais les arguments sont diffrents, vous avez une surcharge, ce qui n'est pas probablement que vous voulez.) Si vous avez une classe abstraite comme Instrument, les objets de cette classe n'ont pratiquement aucune signification. Le rle d'Instrument est uniquement d'exprimer une interface et non pas une implmentation particulire, ainsi la cration d'un objet Instrument n'a pas de sens et vous voudrez probablement dissuader l'utilisateur de le faire. Une implmentation possible est d'afficher un message d'erreur dans toutes les mthodes d'Instrument, mais cela retarde le diagnostic l'excution et exige un code fiable et exhaustif. Il est toujours prfrable de traiter les problmes au moment de la compilation. Java fournit un mcanisme qui implmente cette fonctionnalit: c'est la mthode abstraite [38]. C'est une mthode qui est incomplte; elle a seulement une dclaration et aucun corps de mthode. Voici la syntaxe pour une dclaration de mthode abstraite [abstract] :
abstract void f();

Une classe contenant des mthodes abstraites est appele une classe abstraite. Si une classe contient une ou plusieurs mthodes abstraites, la classe doit tre qualifie comme abstract. (Autrement, le compilateur signale une erreur.) Si une classe abstraite est incomplte, comment doit ragir le compilateur si quelqu'un essaye de crer un objet de cette classe? Il ne peut pas crer sans risque un objet d'une classe abstraite, donc vous obtenez un message d'erreur du compilateur. C'est ainsi que le compilateur assure la puret de la classe abstraite et ainsi vous n'avez plus a vous soucier d'un usage impropre de la classe. Si vous hritez d'une classe abstraite et que vous voulez fabriquer des objets du nouveau type, vous devez fournir des dfinitions de mthode correspondant toutes les mthodes abstraites de la classe de base. Si vous ne le faites pas (cela peut tre votre choix ), alors la classe drive est aussi abstraite et le compilateur vous forcera qualifier cette classe avec le mot cl abstract. Il est possible de crer une classe abstraite sans qu'elle contienne des mthodes abstraites. C'est utile quand vous avez une classe pour laquelle avoir des mthodes abstraites n'a pas de sens et que vous voulez empcher la cration d'instance de cette classe. La classe Instrument peut facilement tre change en une classe abstraite. Seules certaines des mthodes seront abstraites, puisque crer une classe abstraite ne vous oblige pas a avoir que des mthodes abstraites. Voici

14 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

quoi cela ressemble : Image

Voici l'exemple de l'orchestre modifi en utilisant des classes et des mthodes abstraites :
//: c07:music4:Music4.java // Classes et mthodes abstraites. import java.util.*;

abstract class Instrument { int i; // Allou chaque fois public abstract void play(); public String what() { return

"Instrument";

}
public abstract void adjust(); }

class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return public void adjust() {} }

"Wind"; }

class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return

"Percussion"; }

15 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

public void adjust() {} }

class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return public void adjust() {} }

"Stringed"; }

class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } }

class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return }

"Woodwind"; }

public class Music4 { // Ne se proccupe pas des types: des nouveaux // ajouts au systme marcheront trs bien: static void tune(Instrument i) { // ... i.play(); }

16 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

static void tuneAll(Instrument[] e) { forint i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Upcast lors de l'ajout au tableau: orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); } } ///:~

Vous pouvez voir qu'il n'y a vraiment aucun changement except dans la classe de base. Il est utile de crer des classes et des mthodes abstraites parce qu'elles forment l'abstraction d'une classe explicite et indique autant utilisateur qu'au compilateur comment elles doivent tre utilises.

Constructeurs et polymorphisme
Comme d'habitude, les constructeurs se comportent diffremment des autres sortes de mthodes. C'est encore vrai pour le polymorphisme. Quoique les constructeurs ne soient pas polymorphes (bien que vous puissiez avoir un genre de "constructeur virtuel", comme vous le verrez dans le chapitre 12), il est important de comprendre comment les constructeurs se comportent dans des hirarchies complexes combin avec le polymorphisme. Cette comprhension vous aidera a viter de dsagrables plats de nouilles.

Ordre d'appel des constructeurs


L'ordre d'appel des constructeurs a t brivement discut dans le chapitre 4 et galement dans le chapitre 6, mais c'tait avant l'introduction du polymorphisme. Un constructeur de la classe de base est toujours appel dans le constructeur d'une classe drive, en remontant la hirarchie d'hritage de sorte qu'un constructeur pour chaque classe de base est appel. Ceci semble normal car le travail du constructeur est prcisment de construire correctement l'objet. Une classe drive a seulement accs ses propres membres, et pas ceux de la classe de base (dont les membres sont en gnral private). Seul le constructeur de la classe de base a la connaissance et l'accs appropris pour initialiser ses propres lments. Par consquent, il est essentiel que tous les constructeurs soient appels, sinon l'objet ne serait pas entirement construit. C'est pourquoi le compilateur impose un appel de constructeur pour chaque partie d'une classe

17 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

drive. Il appellera silencieusement le constructeur par dfaut si vous n'appelez pas explicitement un constructeur de la classe de base dans le corps du constructeur de la classe drive. S' il n'y a aucun constructeur de dfaut, le compilateur le rclamera (dans le cas o une classe n'a aucun constructeur, le compilateur gnrera automatiquement un constructeur par dfaut). Prenons un exemple qui montre les effets de la composition, de l'hritage, et du polymorphisme sur l'ordre de construction :
//: c07:Sandwich.java // Ordre d'appel des constructeurs. class Meal { Meal() { System.out.println("Meal()"); } }

class Bread { Bread() { System.out.println("Bread()"); } }

class Cheese { Cheese() { System.out.println("Cheese()"); } }

class Lettuce { Lettuce() { System.out.println("Lettuce()"); } }

class Lunch extends Meal { Lunch() { System.out.println("Lunch()");} }

class PortableLunch extends Lunch { PortableLunch() { System.out.println("PortableLunch()"); } }

18 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

class Sandwich extends PortableLunch { Bread b = new Bread(); Cheese c = new Cheese(); Lettuce l = new Lettuce(); Sandwich() { System.out.println("Sandwich()"); } public static void main(String[] args) { new Sandwich(); } } ///:~

Cet exemple utilise une classe complexe et d'autres classes, chaque classe a un constructeur qui s'annonce lui-mme. La classe importante est Sandwich, qui est au troisime niveau d'hritage (quatre, si vous comptez l'hritage implicite de Object) et qui a trois objets membres. Quand un objet Sandwich est cr dans le main(), l'output est :
Meal() Lunch() PortableLunch() Bread() Cheese() Lettuce() Sandwich()

Ceci signifie que l'ordre d'appel des constructeurs pour un objet complexe est le suivant : 1. Le constructeur de la classe de base est appel. Cette tape est rpte rcursivement jusqu' ce que la racine de la hirarchie soit construite d'abord, suivie par la classe drive suivante, etc, jusqu' atteindre la classe la plus drive. 2. Les initialiseurs des membres sont appels dans l'ordre de dclaration 3. Le corps du constructeur de la classe drive est appele. L'ordre d'appel des constructeurs est important. Quand vous hritez, vous savez tout au sujet de la classe de base et pouvez accder tous les membres public et protected de la classe de base. Ceci signifie que vous devez pouvoir prsumer que tous les membres de la classe de base sont valides quand vous tes dans la classe drive. Dans une mthode normale, la construction a dj eu lieu, ainsi tous les membres de toutes les parties de l'objet ont t construits. Dans le constructeur, cependant, vous devez pouvoir supposer que tous les membres que vous utilisez ont t construits. La seule manire de le garantir est d'appeler d'abord le constructeur de la classe de base. Ainsi, quand tes dans le constructeur de la classe drive, tous les membres que vous pouvez accder dans la classe de base ont t initialiss. Savoir que tous les membres sont valides l'intrieur du constructeur est galement la raison pour laquelle, autant que possible, vous devriez initialiser tous les objets membres (c'est

19 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

dire les objets mis dans la classe par composition) leur point de dfinition dans la classe (par exemple, b, c, et l dans l'exemple ci-dessus). Si vous suivez cette recommandation, vous contribuerez vous assurer que tous les membres de la classe de base et les objets membres de l'objet actuel aient t initialiss. Malheureusement, cela ne couvre pas tous les cas comme vous allez le voir dans le paragraphe suivant.

La mthode finalize() et l'hritage


Quand vous utilisez la composition pour crer une nouvelle classe, vous ne vous proccupez pas de l'achvement des objets membres de cette classe. Chaque membre est un objet indpendant et trait par le garbage collector indpendamment du fait qu'il soit un membre de votre classe. Avec l'hritage, cependant, vous devez redfinir finalize() dans la classe drive si un nettoyage spcial doit tre effectu pendant la phase de garbage collection. Quand vous redfinissez finalize() dans une classe fille, il est important de ne pas oublier d'appeler la version de finalize() de la classe de base, sinon l'achvement de la classe de base ne se produira pas. L'exemple suivant le prouve :
//: c07:Frog.java // Test de la mthode finalize avec l'hritage.

class DoBaseFinalization { public static boolean flag = false;

class Characteristic { String s; Characteristic(String c) { s = c; System.out.println( "Creating Characteristic " + s); } protected void finalize() { System.out.println( "finalizing Characteristic " + s); } }

class LivingCreature { Characteristic p = new Characteristic("is alive");

20 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

LivingCreature() { System.out.println("LivingCreature()"); } protected void finalize() { System.out.println( "LivingCreature finalize"); // Appel de la version de la classe de base, la fin! if(DoBaseFinalization.flag) try { super.finalize();

} catch(Throwable t) {} } }

class Animal extends LivingCreature { Characteristic p = new Characteristic("has heart"); Animal() { System.out.println("Animal()"); } protected void finalize() { System.out.println("Animal finalize"); if(DoBaseFinalization.flag) try { super.finalize();

} catch(Throwable t) {} } }

class Amphibian extends Animal { Characteristic p =

21 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

new Characteristic("can live in water"); Amphibian() { System.out.println("Amphibian()"); } protected void finalize() { System.out.println("Amphibian finalize"); if(DoBaseFinalization.flag) try { super.finalize();

} catch(Throwable t) {} } }

public class Frog extends Amphibian { Frog() { System.out.println("Frog()"); } protected void finalize() { System.out.println("Frog finalize"); if(DoBaseFinalization.flag) try { super.finalize();

} catch(Throwable t) {} }
public static void main(String[] args) { if(args.length

!= 0 &&

args[0].equals("finalize")) DoBaseFinalization.flag = true;


else System.out.println("not finalizing bases"); new Frog(); // Devient instantanment rcuprable par le garbage collector

22 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

System.out.println("bye!"); // Force l'appel des finalisations: System.gc(); } } ///:~

La classe DoBaseFinalization a simplement un indicateur qui contrle l&rsquo;appel de super.finalize() pour toutes les classes de la hirarchie. Cet indicateur est positionn par un argument de l&rsquo;appel en ligne, vous pouvez ainsi visualiser le comportement selon l&rsquo;invocation ou non de la finalisation dans la classe de base. Chaque classe dans la hirarchie contient galement un objet de la classe Characteristic. Vous constaterez que les objets de Characteristic sont toujours finaliss indpendamment de l&rsquo;appel conditionn des finaliseurs de la classe de base. Chaque mthode finalize() redfinie doit au moins avoir accs aux membres protected puisque la mthode finalize() de la classe Object est protected et le que compilateur ne vous permettra pas de rduire l'accs pendant l'hritage ( Friendly est moins accessible que protected). Dans Frog.main(), l'indicateur DoBaseFinalization est configur et un seul objet Frog est cr. Rappelez-vous que la phase de garbage collection, et en particulier la finalisation, ne peut pas avoir lieu pour un objet particulier, ainsi pour la forcer, l'appel System.gc() dclenche le garbage collector, et ainsi la finalisation. Sans finalisation de la classe de base, l'output est le suivant :
not finalizing bases Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water Amphibian() Frog() bye! Frog finalize finalizing Characteristic is alive finalizing Characteristic has heart finalizing Characteristic can live in water

Vous pouvez constater qu'aucune finalisation n'est appele pour les classes de base de Frog (les objets membres, eux, sont achevs, comme on s'y attendait). Mais si vous ajoutez l'argument finalize sur la ligne de commande, on obtient ce qui suit :

23 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water Amphibian() Frog() bye! Frog finalize Amphibian finalize Animal finalize LivingCreature finalize finalizing Characteristic is alive finalizing Characteristic has heart finalizing Characteristic can live in water

Bien que l'ordre de finalisation des objets membres soit le mme que l'ordre de cration, l'ordre de finalisation des objets est techniquement non spcifi. Cependant, vous avez le contrle sur cet ordre pour les classes de base. Le meilleur ordre suivre est celui qui est montr ici, et qui est l'ordre inverse de l'initialisation. Selon le modle qui est utilis pour des destructeurs en C++, vous devez d'abord excuter la finalisation des classes drives, puis la finalisation de la classe de base. La raison est que la finalisation des classes drives pourrait appeler des mthodes de la classe de base qui exigent que les composants de la classe de base soient toujours vivants, donc vous ne devez pas les dtruire prmaturment.

Comportement des mthodes polymorphes dans les constructeurs


La hirarchie d'appel des constructeurs pose un dilemme intressant. Qu'arrive t-il si l'intrieur d'un constructeur vous appelez une mthode dynamiquement attache de l'objet en cours de construction? l'intrieur d'une mthode ordinaire vous pouvez imaginer ce qui arriverait: l'appel dynamiquement attach est rsolu l'excution parce que l'objet ne peut pas savoir s'il appartient la classe dans laquelle se trouve la mthode ou bien dans une classe drive. Par cohrence, vous pourriez penser que c'est ce qui doit arriver dans les constructeurs. Ce n'est pas ce qui se passe. Si vous appelez une mthode dynamiquement attache l'intrieur d'un constructeur, c'est la dfinition redfinie de cette mthode est appele. Cependant, l'effet peut tre plutt surprenant et peut cacher des bugs difficiles trouver. Le travail du constructeur est conceptuellement d'amener l'objet l'existence (qui est peine un prouesse ordinaire). l'intrieur de n'importe quel constructeur, l'objet entier pourrait tre seulement partiellement form - vous pouvez savoir seulement que les objets de la classe de base ont t initialiss, mais vous ne pouvez pas connatre les classes filles qui hrite de vous. Cependant, un appel de mthode dynamiquement attach, atteint extrieurement la hirarchie d'hritage. Il appelle une mthode dans une classe drive. Si vous faites a

24 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

l'intrieur d'un constructeur, vous appelez une mthode qui pourrait manipuler des membres non encore initialiss - une recette trs sre pour le dsastre. Vous pouvez voir le problme dans l'exemple suivant :
//: c07:PolyConstructors.java // Constructeurs et polymorphisme ne conduisent // pas ce quoi que vous pourriez vous attendre. abstract class Glyph { abstract void draw(); Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } }

class RoundGlyph extends Glyph { int radius = 1; RoundGlyph(int r) { radius = r; System.out.println( "RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { System.out.println( "RoundGlyph.draw(), radius = " + radius); } }

public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); }

25 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

} ///:~

Dans Glyph, la mthode dessiner [draw()] est abstraite; elle a donc t conue pour tre redfinie. En effet, vous tes forcs de la redfinir dans RoundGlyph. Mais le constructeur de Glyph appelle cette mthode et l'appel aboutit RoundGlyph.draw(), ce qui semble tre l'intention. Mais regardez l'output :
Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5

Quand le constructeur de Glyph appelle draw(), le rayon [radius] n'a mme pas encore la valeur initiale de 1, il vaut zro. Le rsultat serait probablement rduit l'affichage d'un point ou mme rien du tout, avec vous, fixant un cran dsesprment vide essayant de comprendre pourquoi le programme ne marche pas. L'ordre de l'initialisation dcrit dans la section prcdente n'est pas compltement exhaustif, et c'est la cl qui va rsoudre le mystre. La procdure d'initialisation est la suivante : 1. La zone alloue l'objet est initialise zro binaire avant tout. 2. Les constructeurs des classes de base sont appels comme dcrit prcdemment. Puis, la mthode draw() redfinie est appele (et oui, avant l'appel du constructeur de RoundGlyph), et utilise radius qui vaut zro cause de la premire tape. 3. Les initialiseurs des membres sont appels dans l'ordre de dclaration. 4. Le corps du constructeur de la classe drive est appel Le bon cot est que tout est au moins initialis au zro (selon la signification de zro pour un type de donne particulier) et non laiss avec n'importe quelles valeurs. Cela inclut les rfrences d'objet qui sont incorpors l'intrieur d'une classe par composition, et qui passent null. Ainsi si vous oubliez d'initialiser une rfrence vous obtiendrez une exception l'excution. Tout le reste est zro, qui est habituellement une valeur que l'on repre en examinant l'output. D'autre part, vous devez tre assez horrifis du rsultat de ce programme. Vous avez fait une chose parfaitement logique et pourtant le comportement est mystrieusement faux, sans aucune manifestation du compilateur (C ++ a un comportement plus correct dans la mme situation). Les bugs dans ce got l peuvent facilement rester cachs et ncessiter pas mal de temps d'investigation. Il en rsulte la recommandation suivante pour les constructeurs: Faire le minimum pour mettre l'objet dans un bon tat et si possible, ne pas appeler de mthodes. Les seules mthodes qui sont appelables en toute scurit l'intrieur d'un constructeur sont celles qui sont finales dans la classe de base (mme chose pour les mthodes prives, qui sont automatiquement finales.). Celles-ci ne peuvent tre redfinies et ne rservent donc pas de surprise.

Concevoir avec l'hritage


Aprs avoir vu le polymorphisme, c'est un instrument tellement astucieux qu'on dirait que tout doit tre hrit. Ceci peut alourdir votre conception; en fait si vous faites le choix d'utiliser l'hritage d'entre lorsque vous crez une nouvelle classe partir d'une classe existante, cela peut devenir inutilement compliqu.

26 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

Une meilleure approche est de choisir d'abord la composition, quand il ne vous semble pas vident de choisir entre les deux. La composition n'oblige pas concevoir une hirarchie d'hritage, mais elle est galement plus flexible car il est alors possible de choisir dynamiquement un type (et son comportement), alors que l'hritage requiert un type exact dtermin au moment de la compilation. L'exemple suivant l'illustre :
//: c07:Transmogrify.java // Changer dynamiquement le comportement // d'un objet par la composition. abstract class Actor { abstract void act(); }

class HappyActor extends Actor { public void act() { System.out.println("HappyActor"); } }

class SadActor extends Actor { public void act() { System.out.println("SadActor"); } }

class Stage { Actor a = new HappyActor(); void change() { a = new SadActor(); } void go() { a.act(); } }

public class Transmogrify { public static void main(String[] args) { Stage s = new Stage(); s.go(); // Imprime "HappyActor" s.change();

27 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

s.go(); // Imprime "SadActor" } } ///:~

Un objet Stage contient une rfrence vers un Actor, qui est initialis par un objet HappyActor. Cela signifie que go() produit un comportement particulier. Mais puisqu'une rfrence peut tre relie un objet diffrent l'excution, une rfrence un objet SadActor peut tre substitue dans a et alors le comportement produit par go() change. Ainsi vous gagnez en flexibilit dynamique l'excution (galement appel le State Pattern. Voir Thinking in Patterns with Java, tlchargeable sur www.BruceEckel.com.). Par contre, vous ne pouvez pas dcider d'hriter diffremment l'excution; cela doit tre compltement dtermin la compilation. Voici une recommandation gnrale: Utilisez l'hritage pour exprimer les diffrences de comportement, et les champs pour exprimer les variations d'tat. Dans l'exemple ci-dessus, les deux sont utiliss: deux classes diffrentes hritent pour exprimer la diffrence dans la mthode act(), et Stage utilise la composition pour permettre son tat d'tre chang. Dans ce cas, ce changement d'tat provoque un changement de comportement.

Hritage pur contre extension


Lorsque l'on tudie l'hritage, il semblerait que la faon la plus propre de crer une hirarchie d'hritage est de suivre l'approche pure. A savoir que seules les mthodes qui ont t tablies dans la classe de base ou l'interface sont surchargeables dans la classe drive, comme le montre ce diagramme : Image

Ceci peut se nommer une relation est-un pure car l'interface d'une classe tablie ce qu'elle est. L'hritage garantie que toute classe drive aura l'interface de la classe de base et rien de moins. Si vous suivez le diagramme ci-dessus, les classes drives auront galement pas plus que l'interface de la classe de base. Ceci peut tre considr comme une substitution pure, car les objets de classe drive peuvent tre parfaitement substitus par la classe de base, et vous n'avez jamais besoin de connatre d' information supplmentaire sur les sous-classes quand vous les utilisez : Image

Cela tant, la classe de base peut recevoir tout message que vous pouvez envoyer la classe drive car les deux ont exactement la mme interface. Tout ce que vous avez besoin de faire est d'utiliser l'upcast partir de la classe drive et de ne jamais regarder en arrire pour voir quel type exact d'objet vous manipulez. En la considrant de cette manire, une relation pure est-un semble la seule faon sense de pratiquer, et toute autre conception dnote une rflexion embrouille et est par dfinition hache. Ceci aussi est un pige. Ds que vous commencez penser de cette manire, vous allez tourner en rond et dcouvrir qu'tendre

28 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

l'interface (ce que, malencontreusement, le mot cl extends semble encourager) est la solution parfaite un problme particulier. Ceci pourrait tre qualifi de relation est-comme-un car la classe drive est comme la classe de base, elle a la mme interface fondamentale mais elle a d'autres lments qui ncessitent d'implmenter des mthodes additionnelles : Image

Mais si cette approche est aussi utile et sense (selon la situation) elle a un inconvnient. La partie tendue de l'interface de la classe drive n'est pas accessible partir de la classe de base, donc une fois que vous avez utilis l'upcast vous ne pouvez pas invoquer les nouvelles mthodes : Image

Si vous n'upcastez pas dans ce cas, cela ne va pas vous incommoder, mais vous serez souvent dans une situation o vous aurez besoin de retrouver le type exact de l'objet afin de pouvoir accder aux mthodes tendues de ce type. La section suivante montre comment cela se passe.

Downcasting et identification du type l'excution


Puisque vous avez perdu l'information du type spcifique par un upcast (en remontant la hirarchie d'hritage), il est logique de retrouver le type en redescendant la hirarchie d'hritage par un downcast. Cependant, vous savez qu'un upcast est toujours sr; la classe de base ne pouvant pas avoir une interface plus grande que la classe drive, ainsi tout message que vous envoyez par l'interface de la classe de base a la garantie d'tre accept. Mais avec un downcast, vous ne savez pas vraiment qu'une forme (par exemple) est en ralit un cercle. Cela pourrait plutt tre un triangle ou un carr ou quelque chose d'un autre type. Image

Pour rsoudre ce problme il doit y avoir un moyen de garantir qu'un downcast est correct, ainsi vous n'allez pas effectuer un cast accidentel vers le mauvais type et ensuite envoyer un message que l'objet ne pourrait accepter. Ce serait assez imprudent. Dans certains langages (comme C++) vous devez effectuer une opration spciale afin d'avoir un cast ascendant sr, mais en Java tout cast est vrifi! Donc mme si il semble que vous faites juste un cast explicite ordinaire, lors de l'excution ce cast est vrifi pour assurer qu'en fait il s'agit bien du type auquel vous vous attendez. Si il ne l'est pas, vous rcuprez une ClassCastException. Cette action de vrifier les types au moment de l'excution est appel run-time type identification (RTTI). L'exemple suivant montre le comportement de la RTTI :

29 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

//: c07:RTTI.java // Downcasting & Run-time Type // Identification (RTTI). import java.util.*;

class Useful { public void f() {} public void g() {} }

class MoreUseful extends Useful { public void f() {} public void g() {} public void u() {} public void v() {} public void w() {} }

public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() }; x[0].f(); x[1].g(); // Compilation: mthode non trouve dans Useful: //! x[1].u(); ((MoreUseful)x[1]).u(); // Downcast/RTTI ((MoreUseful)x[0]).u(); // Exception envoye } } ///:~

Comme dans le diagramme, MoreUseful tend l'interface de Useful. Mais puisque il a hrit, on peut faire un

30 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

upcast vers un Useful. Vous pouvez voir ceci se produire dans l'initialisation du tableau x dans main(). Comme les deux objets du tableau sont de la classe Useful, vous pouvez envoyer les mthodes f() et g() aux deux, et si vous essayer d'invoquer u(), qui existe seulement dans MoreUseful, vous aurez un message d'erreur la compilation. Si vous voulez accder l'interface tendue d'un objet MoreUseful, vous pouvez essayer un downcast. Si c'est le type correct, cela fonctionnera. Autrement, vous allez recevoir une ClassCastException. Vous n'avez pas besoin d'crire un code spcial pour cette exception, car elle indique une erreur du programmeur qui pourrait arriver n'importe o dans un programme. La RTTI est plus riche qu'un simple cast. Par exemple, il y a une faon de connatre le type que vous manipulez avant d'essayer de le downcaster. Tout le Chapitre 12 est consacr l'tude de diffrents aspects du run-time type identification Java.

Rsum
Polymorphisme signifie diffrentes formes. Dans la programmation oriente objet, vous avez la mme physionomie (l'interface commune dans la classe de base) et diffrentes formes qui utilisent cette physionomie: les diffrentes versions des mthodes dynamiquement attaches. Vous avez vu dans ce chapitre qu'il est impossible de comprendre, ou mme crer, un exemple de polymorphisme sans utiliser l'abstraction et l'hritage. Le polymorphisme est une notion qui ne peut pas tre prsent sparment (comme on peut le faire par exemple avec un switch), mais qui fonctionne plutt en conjonction avec le schma global #big picture# des relation entre classes. Les gens sont souvent troubls par d'autres dispositifs non-orients objet de Java, comme la surcharge de mthode, qui sont parfois prsents comme tant orients objet. Ne soyez pas dupe: si ce n'est pas de la liaison tardive, ce n'est pas du polymorphisme. Pour utiliser le polymorphisme, et par consquent les techniques orientes objet, pertinemment dans vos programmes vous devez largir votre vision de la programmation pour y inclure non seulement les membres et les messages d'une classe individuelle, mais galement ce qui est partag entre les classes et leurs rapports entre elles. Bien que ceci exige un effort significatif, a vaut vraiment le coup car il en rsulte un dveloppement plus rapide, un code mieux organis, des programmes extensibles et une maintenance plus facile.

Exercices
1. Ajouter une nouvelle mthode la classe de base de Shapes.java qui affiche un message, mais sans la redfinir dans les classes drives. Expliquer ce qui se passe. Maintenant la redfinir dans une des classes drives mais pas dans les autres, et voir ce qui se passe. Finalement, la redfinir dans toutes les classes drives. 2. Ajouter un nouveau type de Shape Shapes.java et vrifier dans main() que le polymorphisme fonctionne pour votre nouveau type comme il le fait pour les anciens types. 3. Changer Music3.java pour que what() devienne une mthode toString() de la classe racine Object . Essayer d'afficher les objets Instrument en utilisant System.out.println() (sans aucun cast). 4. Ajouter un nouveau type d'Instrument Music3.java et vrifier que le polymorphisme fonctionne pour votre nouveau type. 5. Modifier Music3.java pour qu'il cre de manire alatoire des objets Instrument de la mme faon que Shapes.java le fait. 6. Crer une hirarchie d'hritage de Rongeur: Souris, Gerbille, Hamster, etc. Dans la classe de base, fournir des mthodes qui sont communes tous les Rongeurs, et les redfinir dans les classes drives pour excuter des comportements diffrents dpendant du type spcifique du Rongeur. Crer un tableau
31 of 32

7/6/01 9:47 AM

7: Polymorphism

file:///D|/Daniel/TIJ2FR/All/Chapter07.htm

7. 8. 9. 10.

11.

12.

13.

14. 15.

de Rongeur, le remplir avec diffrent types spcifiques de Rongeurs, et appeler vos mthodes de la classe de base pour voir ce qui arrive. Modifier l'Exercice 6 pour que Rongeur soit une classe abstract. Rendre les mthodes de Rongeur abstraites ds que possible. Crer une classe comme tant abstract sans inclure aucune mthode abstract, et vrifier que vous ne pouvez crer aucune instance de cette classe. Ajouter la classe Pickle Sandwich.java. Modifier l'Exercice 6 afin qu'il dmontre l'ordre des initialisations des classes de base et des classes drives. Maintenant ajouter des objets membres la fois aux classes de base et drives, et montrer dans quel ordre leurs initialisations se produisent durant la construction. Crer une hirarchie d'hritage 3 niveaux. Chaque classe dans la hirarchie devra avoir une mthode finalize(), et devra invoquer correctement la version de la classe de base de finalize(). Dmontrer que votre hirarchie fonctionne de manire approprie. Crer une classe de base avec deux mthodes. Dans la premire mthode, appeler la seconde mthode. Faire hriter une classe et redfinir la seconde mthode. Crer un objet de la classe drive, upcaster le vers le type de base, et appeler la premire mthode. Expliquer ce qui se passe. Crer une classe de base avec une mthode abstract print() qui est redfinie dans une classe drive. La version redfinie de la mthode affiche la valeur d'une variable int dfinie dans la classe drive. Au point de dfinition de cette variable, lui donner une valeur non nulle. Dans le constructeur de la classe de base, appeler cette mthode. Dans main(), crer un objet du type driv, et ensuite appeler sa mthode print(). Expliquer les rsultats. Suivant l'exemple de Transmogrify.java, crer une classe Starship contenant une rfrence AlertStatus qui peut indiquer trois tats diffrents. Inclure des mthodes pour changer les tats. Crer une classe abstract sans mthodes. Driver une classe et ajouter une mthode. Crer une mthode static qui prend une rfrence vers la classe de base, effectue un downcast vers la classe drive, et appelle la mthode. Dans main(), dmontrer que cela fonctionne. Maintenant mettre la dclaration abstract pour la mthode dans la classe de base, liminant ainsi le besoin du downcast.

[38] Pour les programmeurs C++, ceci est analogue aux fonctions virtuelles pures du C++.

32 of 32

7/6/01 9:47 AM

Vous aimerez peut-être aussi

  • Appendix A
    Appendix A
    Document44 pages
    Appendix A
    whaloo
    Pas encore d'évaluation
  • Introduction
    Introduction
    Document13 pages
    Introduction
    whaloo
    Pas encore d'évaluation
  • Chapter 13 F
    Chapter 13 F
    Document20 pages
    Chapter 13 F
    whaloo
    Pas encore d'évaluation
  • Chapter 15 B
    Chapter 15 B
    Document26 pages
    Chapter 15 B
    whaloo
    Pas encore d'évaluation
  • Preface
    Preface
    Document5 pages
    Preface
    whaloo
    Pas encore d'évaluation
  • Classement Usuel Du Hetre
    Classement Usuel Du Hetre
    Document6 pages
    Classement Usuel Du Hetre
    whaloo
    Pas encore d'évaluation
  • Chapter 15 A
    Chapter 15 A
    Document49 pages
    Chapter 15 A
    whaloo
    Pas encore d'évaluation
  • Chapter 13 e
    Chapter 13 e
    Document24 pages
    Chapter 13 e
    whaloo
    Pas encore d'évaluation
  • Chapter 13 B
    Chapter 13 B
    Document18 pages
    Chapter 13 B
    whaloo
    Pas encore d'évaluation
  • Chapter 12
    Chapter 12
    Document27 pages
    Chapter 12
    whaloo
    Pas encore d'évaluation
  • Chapter 13 D
    Chapter 13 D
    Document34 pages
    Chapter 13 D
    whaloo
    Pas encore d'évaluation
  • Chapter 13 A
    Chapter 13 A
    Document12 pages
    Chapter 13 A
    whaloo
    Pas encore d'évaluation
  • Chapter 13 C
    Chapter 13 C
    Document18 pages
    Chapter 13 C
    whaloo
    Pas encore d'évaluation
  • Chapter 11
    Chapter 11
    Document77 pages
    Chapter 11
    whaloo
    Pas encore d'évaluation
  • Chapter 04
    Chapter 04
    Document46 pages
    Chapter 04
    whaloo
    Pas encore d'évaluation
  • Chapter 12
    Chapter 12
    Document27 pages
    Chapter 12
    whaloo
    Pas encore d'évaluation
  • Chapter 08
    Chapter 08
    Document53 pages
    Chapter 08
    whaloo
    Pas encore d'évaluation
  • Chapter 07
    Chapter 07
    Document32 pages
    Chapter 07
    whaloo
    Pas encore d'évaluation
  • Chapter 01
    Chapter 01
    Document43 pages
    Chapter 01
    whaloo
    Pas encore d'évaluation
  • Chapter 09
    Chapter 09
    Document115 pages
    Chapter 09
    whaloo
    Pas encore d'évaluation
  • Chapter 06
    Chapter 06
    Document33 pages
    Chapter 06
    whaloo
    Pas encore d'évaluation
  • Chapter 03
    Chapter 03
    Document55 pages
    Chapter 03
    whaloo
    Pas encore d'évaluation
  • Chapter 05
    Chapter 05
    Document22 pages
    Chapter 05
    whaloo
    Pas encore d'évaluation
  • Chapter 02
    Chapter 02
    Document23 pages
    Chapter 02
    whaloo
    Pas encore d'évaluation
  • Appendix C
    Appendix C
    Document8 pages
    Appendix C
    whaloo
    Pas encore d'évaluation
  • Chapter 02
    Chapter 02
    Document23 pages
    Chapter 02
    whaloo
    Pas encore d'évaluation
  • Appendix B
    Appendix B
    Document9 pages
    Appendix B
    whaloo
    Pas encore d'évaluation
  • Appendix A
    Appendix A
    Document44 pages
    Appendix A
    whaloo
    Pas encore d'évaluation
  • Chapter 02
    Chapter 02
    Document23 pages
    Chapter 02
    whaloo
    Pas encore d'évaluation