La plupart des classes Event sont similaires, mais
Bell et Restart sont spéciales. Bell sonne, et
si elle n'a pas sonné un nombre suffisant de fois, elle ajoute un nouvel objet
Bell à la liste des événements afin de sonner à nouveau plus tard. Notez comme les
classes internes semblent bénéficier de l'héritage multiple : Bell
possède toutes les méthodes d'Event mais elle semble disposer également de toutes
les méthodes de la classe externe GreenhouseControls.
Restart est responsable de l'initialisation du système, il
ajoute donc tous les événements appropriés. Bien sûr, une manière plus flexible de réaliser ceci
serait d'éviter le codage en dur des événements et de les extraire d'un fichier à la place (c'est
précisément ce qu'un exercice du Chapitre 11 demande de faire). Puisque Restart()
n'est qu'un objet Event comme un autre, on peut aussi ajouter un objet
Restart depuis Restart.action() afin que le système se relance de
lui-même régulièrement. Et tout ce qu'on a besoin de faire dans main() est de
créer un objet GreenhouseControls et ajouter un objet Restart
pour lancer le processus.
Cet exemple devrait vous avoir convaincu de l'intérêt des classes internes,
spécialement dans le cas des structures de contrôle. Si ce n'est pas le cas, dans le Chapitre 13,
vous verrez comment les classes internes sont utilisées pour décrire élégamment les actions d'une
interface graphique utilisateur. A la fin de ce chapitre vous devriez être complètement
convaincu.
Résumé
Les interfaces et les classes internes sont des concepts plus sophistiqués
que ce que vous pourrez trouver dans beaucoup de langages de programmation orientés objets. Par
exemple, rien de comparable n'existe en C++. Ensemble, elles résolvent le même problème que celui
que le C++ tente de résoudre avec les fonctionnalités de l'héritage multiple. Cependant, l'héritage
multiple en C++ se révèle relativement ardu à mettre en oeuvre, tandis que les interfaces et les
classes internes en Java sont, en comparaison, d'un abord nettement plus facile.
Bien que les fonctionnalités en elles-mêmes soient relativement simples,
leur utilisation relève de la conception, de même que le polymorphisme. Avec le temps, vous
reconnaîtrez plus facilement les situations dans lesquelles utiliser une interface, ou une classe
interne, ou les deux. Mais à ce point du livre vous devriez à tout le moins vous sentir à l'aise
avec leur syntaxe et leur sémantique. Vous intègrerez ces techniques au fur et à mesure que vous
les verrez utilisées.
Exercices
Les solutions d'exercices sélectionnés peuvent être trouvées dans le
document électronique The Thinking in Java Annotated Solution Guide, disponible pour un
faible coût sur www.BruceEckel.com.
- Prouvez que les champs d'une interface sont implicitement
static et final.
- Créez une interface contenant trois méthodes, dans son
propre package. Implémentez cette interface dans un package
différent.
- Prouvez que toutes les méthodes d'une interface sont
automatiquement public.
- Dans c07:Sandwich.java, créez une interface appelée
FastFood (avec les méthodes appropriées) et changez Sandwich afin
qu'il implémente FastFood.
- Créez trois interfaces, chacune avec deux méthodes. Créez
une nouvelle interface héritant des trois, en ajoutant une nouvelle méthode. Créez
une classe implémentant la nouvelle interface et héritant déjà d'une classe concrète. Ecrivez
maintenant quatre méthodes, chacune d'entre elles prenant l'une des quatre
interfaces en argument. Dans main(), créez un objet de votre
classe et passez-le à chacune des méthodes.
- Modifiez l'exercice 5 en créant une classe abstract et en
la dérivant dans la dernière classe.
- Modifiez Music5.java en ajoutant une
interfacePlayable. Enlevez la déclaration de play()
d'Instrument. Ajoutez Playable aux classes dérivées en l'incluant
dans la liste implements. Changez tune() afin qu'il accepte un
Playable au lieu d'un Instrument.
- Changez l'exercice 6 du Chapitre 7 afin que Rodent soit
une interface.
- Dans Adventure.java, ajoutez une
interface appelée CanClimb respectant la forme des autres
interfaces.
- Ecrivez un programme qui importe et utilise
Month2.java.
- En suivant l'exemple donné dans Month2.java, créez une
énumération des jours de la semaine.
- Créez une interface dans son propre package contenant au
moins une méthode. Créez une classe dans un package séparé. Ajoutez une classe interne
protected qui implémente l'interface. Dans un troisième package,
dérivez votre classe, et dans une méthode renvoyez un objet de la classe interne
protected, en le transtypant en interface durant le
retour.
- Créez une interface contenant au moins une méthode, et
implémentez cette interface en définissant une classe interne à l'intérieur d'une
méthode, qui renvoie une référence sur votre interface.
- Répétez l'exercice 13 mais définissez la classe interne à l'intérieur
d'une portée à l'intérieur de la méthode.
- Répétez l'exercice 13 en utilisant une classe interne anonyme.
- Créez une classe interne private qui implémente une
interface public. Ecrivez une méthode qui renvoie une référence sur une instance
de la classe interne private, transtypée en interface. Montrez
que la classe interne est complètement cachée en essayant de la transtyper à nouveau.
- Créez une classe avec un constructeur autre que celui par défaut et sans
constructeur par défaut. Créez une seconde classe disposant d'une méthode qui renvoie une référence
à la première classe. Créez un objet à renvoyer en créant une classe interne anonyme dérivée de la
première classe.
- Créez une classe avec un champ private et une méthode
private. Créez une classe interne avec une méthode qui modifie le champ de la
classe externe et appelle la méthode de la classe externe. Dans une seconde méthode de la classe
externe, créez un objet de la classe interne et appelez sa méthode ; montrez alors l'effet sur
l'objet de la classe externe.
- Répétez l'exercice 18 en utilisant une classe interne anonyme.
- Créez une classe contenant une classe interne static.
Dans main(), créez une instance de la classe interne.
- Créez une interface contenant une classe interne
static. Implémentez cette interface et créez une instance de la
classe interne.
- Créez une classe contenant une classe interne contenant elle-même une
classe interne. Répétez ce schéma en utilisant des classes internes static. Notez
les noms des fichiers .class produits par le compilateur.
- Créez une classe avec une classe interne. Dans une classe séparée, créez
une instance de la classe interne.
- Créez une classe avec une classe interne disposant d'un constructeur autre
que celui par défaut. Créez une seconde classe avec une classe interne héritant de la première
classe interne.
- Corrigez le problème dans WindError.java.
- Modifiez Sequence.java en ajoutant une méthode
getRSelector() qui produise une implémentation différente de l'interface
Selector afin de parcourir la séquence en ordre inverse, de la fin vers le
début.
- Créez une interface U contenant trois méthodes. Créez une
classe A avec une méthode qui produise une référence sur un U en
construisant une classe interne anonyme. Créez une seconde classe B qui contienne
un tableau de U. B doit avoir une méthode qui accepte et stocke
une référence sur un U dans le tableau, une deuxième méthode qui positionne une
référence (spécifiée par un argument de la méthode) dans le tableau à null, et une
troisième méthode qui se déplace dans le tableau et appelle les méthodes de l'objet
U. Dans main(), créez un groupe d'objets A et un
objet B. Remplissez l'objet B avec les références
U produites par les objets A. Utilisez B pour
revenir dans les objets A. Enlevez certaines des références U de
B.
- Dans GreenhouseControls.java, ajoutez des classes
internes Event qui contrôlent des ventilateurs.
- Montrez qu'une classe interne peut accéder aux éléments
private de sa classe externe. Déterminez si l'inverse est vrai.
[38]Cette approche m'a été inspirée par un
e-mail de Rich Hoffarth.
[39]Merci à Martin Danner pour avoir posé
cette question lors d'un séminaire.
[40]Ceci est très différent du concept des
classes imbriquées en C++, qui est simplement un mécanisme de camouflage de noms. Il n'y a
aucun lien avec l'objet externe et aucune permission implicite en C++.
[41]Merci encore à Martin
Danner.
[42]Attention cependant, '$' est un
méta-caractère pour les shells unix et vous pourrez parfois rencontrer des difficultés en listant
les fichiers .class. Ceci peut paraître bizarre de la part de Sun, une entreprise
résolument tournée vers unix. Je pense qu'ils n'ont pas pris en compte ce problème car ils
pensaient que l'attention se porterait surtout sur les fichiers sources.
[43]Pour je ne sais quelle raison, ce
problème m'a toujours semblé plaisant ; il vient de mon livre précédent C++ Inside &
Out, mais Java offre une solution bien plus élégante.