 |
 |
7) Polymorphisme |
|
 |
|
Texte original |
 |
Traducteur :
Jérome Dannoville |
|
 |
///
|
Ce chapitre contient
4 pages
1
2
3
4
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
The new methods are what( ),
which returns a String reference with a description of the class, and
adjust( ), which provides some way to adjust each
instrument.
|
 |
Les nouvelles méthodes sont what(), qui renvoie une
référence sur une String décrivant la classe, et adjust(), qui
fournit un moyen d'ajuster chaque instrument.
|
 |
 |
 |
In main( ), when you place
something inside the Instrument array you automatically upcast to
Instrument.
|
 |
Dans le main(), quand on met quelque chose dans le tableau
d' Instrument, on upcast automatiquement en
Instrument.
|
 |
 |
 |
You can see that the tune( )
method is blissfully ignorant of all the code changes that have happened around
it, and yet it works correctly. This is exactly what polymorphism is supposed to
provide. Your code changes don’t cause damage to parts of the program that
should not be affected. Put another way, polymorphism is one of the most
important techniques that allow the programmer to “separate the things
that change from the things that stay the
same.”
|
 |
Vous pouvez
constater que la méthode 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 abîmer les parties du programme qui
ne devraient pas être affectées. Dit autrement, le polymorphisme est une des techniques majeures
permettant au programmeur de « séparer les choses qui changent des choses qui restent les
mêmes. »
|
 |
 |
 |
Overriding vs.
overloading
|
 |
Redéfinition et Surcharge
|
 |
 |
 |
Let’s take a different look at the
first example in this chapter. In the following program, the interface of the
method play( ) is changed in the process of overriding it, which
means that you haven’t overridden the method, but instead
overloaded it. The compiler allows you to overload methods so it gives no
complaint. But the behavior is probably not what you want. Here’s the
example:
|
 |
Regardons sous un angle différent le premier exemple de ce chapitre. Dans
le programme suivant, l'interface de la méthode play() est changée dans le but de
la redéfinir, ce qui signifie que vous n'avez pas redéfinie la méthode, mais plutôt
surchargée. Le compilateur vous permet de surcharger des méthodes, il ne proteste donc
pas. Mais le comportement n'est probablement pas celui que vous vouliez. Voici
l'exemple :
|
 |
 |
 |
//: c07:WindError.java
// Accidentally changing the 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()");
}
}
class WindX extends InstrumentX {
// OOPS! Changes the method interface:
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); // Not the desired behavior!
}
} ///:~
|
 |
//: 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()"); } }
class WindX extends InstrumentX { // OUPS! L'interface de la méthode 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é! } } ///:~
|
 |
 |
 |
There’s another confusing aspect
thrown in here. In InstrumentX, the play( ) method takes an
int that has the identifier NoteX. That is, even though
NoteX is a class name, it can also be used as an identifier without
complaint. But in WindX, play( ) takes a NoteX
reference that has an identifier n. (Although you could even say
play(NoteX NoteX) without an error.) Thus it appears that the programmer
intended to override play( ) but mistyped the method a bit. The
compiler, however, assumed that an overload and not an override was intended.
Note that if you follow the standard Java naming convention, the argument
identifier would be noteX (lowercase ‘n’), which would
distinguish it from the class name.
|
 |
Il y a un autre aspect déroutant ici. Dans InstrumentX, la
méthode 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 référence de NoteX qui a pour identificateur
n (bien que vous puissiez même écrire play(NoteX NoteX) sans
erreur). En fait, il s'avère que le programmeur a désiré redéfinir play() mais
s'est trompé de type. Du coup le compilateur a supposé qu'une surcharge était souhaitée et non pas
une redéfinition. 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.
|
 |
 |
 |
In tune, the InstrumentX
i is sent the play( ) message, with one of
NoteX’s members (MIDDLE_C) as an argument. Since
NoteX contains int definitions, this means that the int
version of the now-overloaded play( ) method is called, and since
that has not been overridden the base-class version is
used.
|
 |
Dans tune, le message play() est envoyé à
l'InstrumentX i, avec comme argument un de membres de NoteX
(MIDDLE_C). Puisque NoteX contient des définitions
d'int, ceci signifie que c'est la version avec int de la méthode
play(), dorénavant surchargée, qui est appelée. Comme elle n'a pas été
redéfinie, c'est donc la méthode de la classe de base qui est utilisée.
|
 |
 |
 |
The output is:
|
 |
L'output est le suivant :
|
 |
 |
 |
InstrumentX.play()
|
 |
InstrumentX.play()
|
 |
 |
 |
This certainly doesn’t appear to be
a polymorphic method call. Once you understand what’s happening, you can
fix the problem fairly easily, but imagine how difficult it might be to find the
bug if it’s buried in a program of significant
size.
|
 |
Ceci n'est pas un appel polymorphe de méthode. Dès que vous comprenez ce
qui se passe, vous pouvez corriger le problème assez facilement, mais imaginez la difficulté pour
trouver l'anomalie si elle est enterrée dans un gros programme.
|
 |
 |
 |
Abstract classes and methods
|
 |
Classes et méthodes abstraites
|
 |
 |
 |
In all the instrument examples, the
methods in the base class Instrument were always “dummy”
methods. If these methods are ever called, you’ve done something wrong.
That’s because the intent of Instrument is to create a common
interface for all the classes derived from it.
|
 |
Dans tous ces exemples sur l'instrument de musique, les méthodes de la
classe de base Instrument étaient toujours factices. Si jamais ces méthodes sont
appelées, c'est que vous avez fait quelque chose de travers. C'est parce que le rôle de la classe
Instrument est de créer une interface commune pour toutes les classes
dérivées d'elle.
|
 |
 |
 |
The only reason to establish this common
interface is so it can be
expressed differently for each different subtype. It establishes a basic form,
so you can say what’s in common with all the derived classes. Another way
of saying this is to call Instrument an abstract base class
(or simply
an abstract class). You create an abstract class when you want to
manipulate a set of classes through this common interface. All derived-class
methods that match the signature of the base-class declaration will be called
using the dynamic binding mechanism. (However, as seen in the last section, if
the method’s name is the same as the base class but the arguments are
different, you’ve got overloading, which probably isn’t what you
want.)
|
 |
La seule raison d'avoir cette interface commune est qu'elle peut être
exprimée différemment pour chaque sous-type différent. Elle établit une forme de base, ainsi vous
pouvez dire ce qui est commun avec toutes les classes dérivées. Une autre manière d'exprimer cette
factorisation du code est d'appeler Instrument une classe de base abstraite (ou
simplement une classe abstraite). Vous créez une classe abstraite quand vous voulez manipuler un
ensemble de classes à travers cette interface commune. Toutes les méthodes des classes dérivées qui
correspondent à la signature de la déclaration de classe de base seront appelées employant le
mécanisme de liaison dynamique. (Cependant, comme on l'a vu dans la dernière section, si le nom de
la méthode est le même comme la classe de base mais les arguments sont différents, vous avez une
surcharge, ce qui n'est pas probablement que vous voulez.)
|
 |
 |
 |
If you have an abstract class like
Instrument, objects of that class almost always have no meaning. That is,
Instrument is meant to express only the interface, and not a particular
implementation, so creating an Instrument object makes no sense, and
you’ll probably want to prevent the user from doing it. This can be
accomplished by making all the methods in Instrument print error
messages, but that delays the information until run-time and requires reliable
exhaustive testing on the user’s part. It’s always better to catch
problems at compile-time.
|
 |
Si vous avez une classe abstraite comme Instrument, les
objets de cette classe n'ont pratiquement aucune signification. Le rôle
d'Instrument est uniquement d'exprimer une interface et non pas une implémentation
particulière, ainsi la création d'un objet Instrument n'a pas de sens et vous
voudrez probablement dissuader l'utilisateur de le faire. Une implémentation possible est
d'afficher un message d'erreur dans toutes les méthodes d'Instrument, mais cela
retarde le diagnostic à l'exécution et exige un code fiable et exhaustif. Il est toujours
préférable de traiter les problèmes au moment de la compilation.
|
 |
 |
 |
Java provides a mechanism for doing this
called the abstract
method[37].
This is a method that is incomplete; it has only a declaration and no method
body. Here is the syntax for an abstract method declaration:
|
 |
Java fournit un mécanisme qui implémente cette fonctionnalité: c'est la
méthode abstraite [37]. C'est une méthode qui est incomplète; elle a seulement une déclaration et
aucun corps de méthode. Voici la syntaxe pour une déclaration de méthode abstraite
[abstract] :
|
 |
 |
 |
abstract void f();
|
 |
abstract void f();
|
 |
 |
 |
A class containing abstract methods is
called an abstract class. If a class contains one or more abstract
methods, the class must be qualified as abstract. (Otherwise, the
compiler gives you an error message.)
|
 |
Une classe contenant des méthodes abstraites est appelée une classe
abstraite. Si une classe contient une ou plusieurs méthodes abstraites, la classe doit être
qualifiée comme abstract. (Autrement, le compilateur signale une
erreur.)
|
 |
 |
 |
If an abstract class is incomplete, what
is the compiler supposed to do when someone tries to make an object of that
class? It cannot safely create an object of an abstract class, so you get an
error message from the compiler. This way the compiler ensures the purity of the
abstract class, and you don’t need to worry about misusing
it.
|
 |
Si une classe abstraite est incomplète, comment doit réagir le compilateur
si quelqu'un essaye de créer un objet de cette classe? Il ne peut pas créer 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.
|
 |
 |
 |
If you
inherit
from an abstract class and you want to make objects of the new type, you must
provide method definitions for all the abstract methods in the base class. If
you don’t (and you may choose not to), then the derived class is also
abstract and the compiler will force you to qualify that class with the
abstract keyword.
|
 |
Si vous héritez d'une classe abstraite et que vous voulez fabriquer des
objets du nouveau type, vous devez fournir des définitions de méthode correspondant à toutes les
méthodes abstraites de la classe de base. Si vous ne le faites pas (cela peut être votre choix ),
alors la classe dérivée est aussi abstraite et le compilateur vous forcera à qualifier cette classe
avec le mot clé abstract.
|
 |
 |
 |
It’s possible to create a class as
abstract without including any abstract methods. This is useful
when you’ve got a class in which it doesn’t make sense to have any
abstract methods, and yet you want to prevent any instances of that
class.
|
 |
Il est possible de créer une classe abstraite sans qu'elle contienne des
méthodes abstraites. C'est utile quand vous avez une classe pour laquelle avoir des méthodes
abstraites n'a pas de sens et que vous voulez empêcher la création d'instance de cette
classe.
|
 |
 |
 |
The Instrument class can easily be
turned into an abstract class. Only some of the methods will be
abstract, since making a class abstract doesn’t force you to make
all the methods abstract. Here’s what it looks
like:
|
 |
La classe Instrument peut facilement être changée en une
classe abstraite. Seules certaines des méthodes seront abstraites, puisque créer une classe
abstraite ne vous oblige pas a avoir que des méthodes abstraites. Voici à quoi cela
ressemble :
|
 |
 |
 |
|
 |
|
 |
 |
 |
Here’s the orchestra example
modified to use abstract classes and methods:
|
 |
Voici l'exemple de l'orchestre modifié en utilisant des classes et des
méthodes abstraites :
|
 |
 |
 |
//: c07:music4:Music4.java
// Abstract classes and methods.
import java.util.*;
abstract class Instrument {
int i; // storage allocated for each
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 "Wind"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play() {
System.out.println("Percussion.play()");
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play() {
System.out.println("Stringed.play()");
}
public String what() { return "Stringed"; }
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 Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play();
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
}
public static void main(String[] args) {
Instrument[] orchestra = new Instrument[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind();
orchestra[i++] = new Percussion();
orchestra[i++] = new Stringed();
orchestra[i++] = new Brass();
orchestra[i++] = new Woodwind();
tuneAll(orchestra);
}
} ///:~
|
 |
//: c07:music4:Music4.java // Classes et méthodes abstraites. import java.util.*;
abstract class Instrument { int i; // Alloué à chaque fois public abstract void play(); public String what() { return "Instrument"size="4">; } public abstract void adjust(); }
class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} }
class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} }
class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } 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 Music4 { // Ne se préoccupe pas des types: des nouveaux // ajoutés au système marcheront très 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; // 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); } } ///:~
|
 |
 |
 |
You can see that there’s really no
change except in the base class.
|
 |
Vous pouvez voir qu'il n'y a vraiment aucun changement excepté dans la
classe de base.
|
 |
 |
 |
It’s helpful to create abstract
classes and methods because they make the abstractness of a class explicit,
and tell both the user and the compiler how it was
intended to be
used.
|
 |
Il est utile de créer des classes et des méthodes abstraites parce qu'elles
forment l'abstraction d'une classe explicite et indique autant à utilisateur qu'au compilateur
comment elles doivent être utilisées.
|
 |
 |
 |
Constructors and polymorphism
|
 |
Constructeurs et polymorphisme
|
 |
 |
 |
As usual,
constructors are different from
other kinds of methods. This is also true when polymorphism is involved. Even
though constructors are not polymorphic (although you can have a kind of
“virtual constructor,” as you will see in Chapter 12), it’s
important to understand the way constructors work in complex hierarchies and
with polymorphism. This understanding will help you avoid unpleasant
entanglements.
|
 |
Comme d'habitude,
les constructeurs se comportent différemment des autres sortes de méthodes. 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 hiérarchies complexes combiné avec le
polymorphisme. Cette compréhension vous aidera a éviter de désagréables plats de
nouilles.
|
 |
 |
 |
Order of constructor
calls
|
 |
Ordre d'appel des constructeurs
|
 |
 |
 |
The order of constructor calls was
briefly discussed in Chapter 4 and again in Chapter 6, but that was before
polymorphism was introduced.
|
 |
L'ordre d'appel des constructeurs a été brièvement discuté dans le chapitre
4 et également dans le chapitre 6, mais c'était avant l'introduction du polymorphisme.
|
 |
 |
 |
A constructor for the base class is
always called in the constructor for a derived class, chaining up the
inheritance hierarchy so that a constructor for every base class is called. This
makes sense because the constructor has a special job: to see that the object is
built properly. A derived class has access to its own members only, and not to
those of the base class (whose members are typically private). Only the
base-class constructor has the proper knowledge and access to initialize its own
elements. Therefore, it’s essential that all constructors get called,
otherwise the entire object wouldn’t be constructed. That’s why the
compiler enforces a constructor call for every portion of a derived class. It
will silently call the default constructor if you don’t explicitly call a
base-class constructor in the derived-class constructor body. If there is no
default constructor, the compiler will complain. (In the case where a class has
no constructors, the compiler will automatically synthesize a default
constructor.)
|
 |
Un constructeur de la classe de base est toujours appelé dans le
constructeur d'une classe dérivée, en remontant la hiérarchie d'héritage de sorte qu'un
constructeur pour chaque classe de base est appelé. Ceci semble normal car le travail du
constructeur est précisément de construire correctement l'objet. Une classe dérivée a seulement
accès à ses propres membres, et pas à ceux de la classe de base (dont les membres sont en général
private). Seul le constructeur de la classe de base a la connaissance et l'accès
appropriés pour initialiser ses propres éléments. Par conséquent, il est essentiel que tous les
constructeurs soient appelés, sinon l'objet ne serait pas entièrement construit. C'est pourquoi le
compilateur impose un appel de constructeur pour chaque partie d'une classe dérivée. Il appellera
silencieusement le constructeur par défaut si vous n'appelez pas explicitement un constructeur de
la classe de base dans le corps du constructeur de la classe dérivée. S' il n'y a aucun
constructeur de défaut, le compilateur le réclamera (dans le cas où une classe n'a aucun
constructeur, le compilateur générera automatiquement un constructeur par défaut).
|
 |
 |
 |
Let’s take a look at an example
that shows the effects of composition, inheritance, and polymorphism on the
order of construction:
|
 |
Prenons un exemple qui montre les effets de la composition, de l'héritage,
et du polymorphisme sur l'ordre de construction :
|
 |
 |
 |
//: c07:Sandwich.java
// Order of constructor calls.
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()");
}
}
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();
}
} ///:~
|
 |
//: 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()"); } }
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(); } } ///:~
|
 |
 |
 |
This example creates a complex class out
of other classes, and each class has a constructor that announces itself. The
important class is Sandwich, which reflects three levels of inheritance
(four, if you count the implicit inheritance from Object) and three
member objects. When a Sandwich object is created in main( ),
the output is:
|
 |
Cet exemple utilise une classe complexe et d'autres classes, chaque classe
a un constructeur qui s'annonce lui-même. La classe importante est Sandwich, qui
est au troisième niveau d'héritage (quatre, si vous comptez l'héritage 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()
|
 |
Meal() Lunch() PortableLunch() Bread() Cheese() Lettuce() Sandwich()
|
 |
 |
 |
This means that the order of constructor
calls for a complex object is as follows:
|
 |
Ceci signifie que l'ordre d'appel des constructeurs pour un objet complexe
est le suivant :
|
 |
 |
 |
- The base-class constructor
is called. This step is repeated recursively such that the root of the hierarchy
is constructed first, followed by the next-derived class, etc., until the
most-derived class is
reached.
- Member
initializers are called in the order of
declaration.
- The
body of the derived-class constructor is
called.
|
 |
- Le constructeur de la classe de base est appelé. Cette étape est répétée
récursivement jusqu'à ce que la racine de la hiérarchie soit construite d'abord, suivie par la
classe dérivée suivante, etc, jusqu'à atteindre la classe la plus dérivée.
- Les initialiseurs des membres sont appelés dans l'ordre de
déclaration
- Le corps du constructeur de la classe dérivée est appelée.
|
 |
 |
 |
The order of the
constructor calls is important. When you inherit, you know all about the base
class and can access any public and protected members of the base
class. This means that you must be able to assume that all the members of the
base class are valid when you’re in the derived class. In a normal method,
construction has already taken place, so all the members of all parts of the
object have been built. Inside the constructor, however, you must be able to
assume that all members that you use have been built. The only way to guarantee
this is for the base-class constructor to be called first. Then when
you’re in the derived-class constructor, all the members you can access in
the base class have been initialized. “Knowing that all members are
valid” inside the constructor is also the reason that, whenever possible,
you should initialize all member objects (that is, objects placed in the class
using composition) at their point of definition in the class (e.g., b,
c, and l in the example above). If you follow this practice, you
will help ensure that all base class members and member objects of the
current object have been initialized. Unfortunately, this doesn’t handle
every case, as you will see in the next
section.
|
 |
L'ordre d'appel des constructeurs est
important. Quand vous héritez, vous savez tout au sujet de la classe de base et pouvez accéder à
tous les membres public et protected de la classe de base. Ceci
signifie que vous devez pouvoir présumer que tous les membres de la classe de base sont valides
quand vous êtes dans la classe dérivée. Dans une méthode normale, la construction a déjà 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 manière de le garantir est d'appeler d'abord le constructeur de la classe de base. Ainsi,
quand êtes dans le constructeur de la classe dérivée, tous les membres que vous pouvez accéder dans
la classe de base ont été initialisés. Savoir que tous les membres sont valides à l'intérieur du
constructeur est également la raison pour laquelle, autant que possible, vous devriez initialiser
tous les objets membres (c'est à dire les objets mis dans la classe par composition) à leur point
de définition 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é initialisés. Malheureusement, cela ne couvre pas tous les cas
comme vous allez le voir dans le paragraphe suivant.
|
 |
 |
 |
Inheritance and finalize( )
|
 |
La méthode finalize() et l'héritage
|
 |
 |
 |
When you use composition to create a new
class, you never worry about finalizing the member objects of that class. Each
member is an independent object, and thus is garbage
collected and finalized regardless of whether it happens to be a member of your
class. With inheritance, however, you must override
finalize( ) in the
derived class if you have any special cleanup that must happen as part of
garbage collection. When you override finalize( ) in an inherited
class, it’s important to remember to call the base-class version of
finalize( ), since otherwise the base-class finalization will not
happen. The following example proves this:
|
 |
Quand vous utilisez la composition pour créer une nouvelle classe, vous ne
vous préoccupez pas de l'achèvement des objets membres de cette classe. Chaque membre est un objet
indépendant et traité par le garbage collector indépendamment du fait qu'il soit un membre de votre
classe. Avec l'héritage, cependant, vous devez redéfinir finalize() dans la classe
dérivée si un nettoyage spécial doit être effectué pendant la phase de garbage collection. Quand
vous redéfinissez 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'achèvement de la classe de base ne se produira pas. L'exemple suivant le
prouve :
|
 |
 |
 |
//: c07:Frog.java
// Testing finalize with inheritance.
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");
LivingCreature() {
System.out.println("LivingCreature()");
}
protected void finalize() throws Throwable {
System.out.println(
"LivingCreature finalize");
// Call base-class version LAST!
if(DoBaseFinalization.flag)
super.finalize();
}
}
class Animal extends LivingCreature {
Characteristic p =
new Characteristic("has heart");
Animal() {
System.out.println("Animal()");
}
protected void finalize() throws Throwable {
System.out.println("Animal finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
}
class Amphibian extends Animal {
Characteristic p =
new Characteristic("can live in water");
Amphibian() {
System.out.println("Amphibian()");
}
protected void finalize() throws Throwable {
System.out.println("Amphibian finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
}
public class Frog extends Amphibian {
Frog() {
System.out.println("Frog()");
}
protected void finalize() throws Throwable {
System.out.println("Frog finalize");
if(DoBaseFinalization.flag)
super.finalize();
}
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(); // Instantly becomes garbage
System.out.println("Bye!");
// Force finalizers to be called:
System.gc();
}
} ///:~
|
 |
//: c07:Frog.java // Test de la méthode finalize avec l'héritage.
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"); 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 = 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 instantanément récupérable par le garbage collector System.out.println("bye!"); // Force l'appel des finalisations: System.gc(); } } ///:~
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |