 |
 |
7) Polymorphisme |
|
 |
|
Texte original |
 |
Traducteur : Jérome DANNOVILLE |
|
 |
///
|
Ce chapitre contient 4 pages
1
2
3
4
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
The class DoBaseFinalization
simply holds a flag that indicates to each class in the hierarchy whether to
call
super.finalize( ).
This flag is set based on a command-line argument, so you can view the behavior
with and without base-class finalization.
|
 |
La classe DoBaseFinalization a simplement un indicateur
qui contrôle l’appel de super.finalize() pour toutes les classes de la
hiérarchie. Cet indicateur est positionné par un argument de l’appel en ligne, vous pouvez
ainsi visualiser le comportement selon l’invocation ou non de la finalisation dans la classe
de base.
|
 |
 |
 |
Each class in the hierarchy also contains
a member object of class Characteristic. You will see that regardless of
whether the base class finalizers are called, the Characteristic member
objects are always finalized.
|
 |
Chaque classe dans la hiérarchie contient également un objet de la classe
Characteristic. Vous constaterez que les objets de Characteristic
sont toujours finalisés indépendamment de l’appel conditionné des finaliseurs de la classe de
base.
|
 |
 |
 |
Each overridden finalize( )
must have access to at least protected members since the
finalize( ) method in class Object is protected and
the compiler will not allow you to reduce the access during inheritance.
(“Friendly” is less
accessible than protected.)
|
 |
Chaque méthode finalize() redéfinie doit au moins avoir
accès aux membres protected puisque la méthode finalize() de la
classe Object est protected et le que compilateur ne vous
permettra pas de réduire l'accès pendant l'héritage (« Friendly » est moins accessible
que protected).
|
 |
 |
 |
In
Frog.main( ), the DoBaseFinalization flag is
configured and a single Frog object is created. Remember that garbage
collection—and in particular finalization—might not happen for any
particular object, so to enforce this, the call to System.gc( )
triggers garbage collection, and thus finalization. Without base-class
finalization, the output is:
|
 |
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()
déclenche 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
|
 |
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
|
 |
 |
 |
You can see that, indeed, no finalizers
are called for the base classes of Frog (the member objects are
finalized, as you would expect). But if you add the “finalize”
argument on the command line, you get:
|
 |
Vous pouvez constater qu'aucune finalisation n'est appelée pour les classes
de base de Frog (les objets membres, eux, sont achevés, comme on s'y attendait).
Mais si vous ajoutez l'argument « finalize » sur la ligne de commande, on obtient ce qui
suit :
|
 |
 |
 |
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
|
 |
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
|
 |
 |
 |
Although the order the member objects are
finalized is the same order that they are created, technically the
order of
finalization of objects is unspecified. With base classes, however, you have
control over the order of finalization. The best order to use is the one
that’s shown here, which is the reverse of the order of initialization.
Following the form that’s used in C++ for destructors, you should perform
the derived-class finalization first, then the base-class finalization.
That’s because the derived-class finalization could call some methods in
the base class that require that the base-class components are still alive, so
you must not destroy them
prematurely.
|
 |
Bien que l'ordre de finalisation des objets membres soit le même que
l'ordre de création, l'ordre de finalisation des objets est techniquement non spécifié. Cependant,
vous avez le contrôle 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 modèle qui est utilisé
pour des destructeurs en C++, vous devez d'abord exécuter la finalisation des classes dérivées,
puis la finalisation de la classe de base. La raison est que la finalisation des classes dérivées
pourrait appeler des méthodes de la classe de base qui exigent que les composants de la classe de
base soient toujours vivants, donc vous ne devez pas les détruire prématurément.
|
 |
 |
 |
Behavior of polymorphic methods inside
constructors
|
 |
Comportement des méthodes polymorphes dans les constructeursname="Index722">
|
 |
 |
 |
The hierarchy of constructor calls brings
up an interesting dilemma. What happens if you’re inside a constructor and
you call a dynamically bound method of the object being constructed? Inside an
ordinary method you can imagine what will happen—the dynamically bound
call is resolved at run-time because the object cannot know whether it belongs
to the class that the method is in or some class derived from it. For
consistency, you might think this is what should happen inside
constructors.
|
 |
La hiérarchie d'appel des constructeurs pose un dilemme intéressant.
Qu'arrive t-il si à l'intérieur d'un constructeur vous appelez une méthode dynamiquement attachée
de l'objet en cours de construction? À l'intérieur d'une méthode ordinaire vous pouvez imaginer ce
qui arriverait: l'appel dynamiquement attaché est résolu à l'exécution parce que l'objet ne peut
pas savoir s'il appartient à la classe dans laquelle se trouve la méthode ou bien dans une classe
dérivée. Par cohérence, vous pourriez penser que c'est ce qui doit arriver dans les
constructeurs.
|
 |
 |
 |
This is not exactly the case. If you call
a dynamically bound method inside a constructor, the overridden definition for
that method is used. However, the effect can be rather unexpected, and
can conceal some difficult-to-find bugs.
|
 |
Ce n'est pas ce qui se passe. Si vous appelez une méthode dynamiquement
attachée à l'intérieur d'un constructeur, c'est la définition redéfinie de cette méthode est
appelée. Cependant, l'effet peut être plutôt surprenant et peut cacher des bugs difficiles
à trouver.
|
 |
 |
 |
Conceptually, the constructor’s job
is to bring the object into existence (which is hardly an ordinary feat). Inside
any constructor, the entire object might be only partially formed—you can
know only that the base-class objects have been initialized, but you cannot know
which classes are inherited from you. A dynamically bound method call, however,
reaches “outward” into the inheritance hierarchy. It calls a method
in a derived class. If you do this inside a constructor, you call a method that
might manipulate members that haven’t been initialized yet—a sure
recipe for disaster.
|
 |
Le travail du constructeur est conceptuellement d'amener l'objet à
l'existence (qui est à peine un prouesse ordinaire). À l'intérieur 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é initialisés, mais vous ne pouvez pas connaître les classes
filles qui hérite de vous. Cependant, un appel de méthode dynamiquement attaché, atteint
« extérieurement » la hiérarchie d'héritage. Il appelle une méthode dans une classe
dérivée. Si vous faites ça à l'intérieur d'un constructeur, vous appelez une méthode qui pourrait
manipuler des membres non encore initialisés - une recette très sûre pour le désastre.
|
 |
 |
 |
You can see the problem in the following
example:
|
 |
Vous pouvez voir le problème dans l'exemple suivant :
|
 |
 |
 |
//: c07:PolyConstructors.java // Constructors and polymorphism // don't produce what you might expect.
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); } } ///:~
|
 |
//: 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); } } ///:~
|
 |
 |
 |
In Glyph, the draw( )
method is abstract, so it is designed to be overridden. Indeed, you are
forced to override it in RoundGlyph. But the Glyph constructor
calls this method, and the call ends up in RoundGlyph.draw( ), which
would seem to be the intent. But look at the output:
|
 |
Dans Glyph, la méthode dessiner [draw()]
est abstraite; elle a donc été conçue pour être redéfinie. En effet, vous êtes forcés de la
redéfinir dans RoundGlyph. Mais le constructeur de Glyph appelle
cette méthode 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
|
 |
Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5
|
 |
 |
 |
When Glyph’s constructor
calls draw( ), the value of radius isn’t even the
default initial value 1. It’s 0. This would probably result in either a
dot or nothing at all being drawn on the screen, and you’d be left
staring, trying to figure out why the program won’t work.
|
 |
Quand le constructeur de Glyph appelle
draw(), le rayon [radius] n'a même pas encore la valeur initiale
de 1, il vaut zéro. Le résultat serait probablement réduit à l'affichage d'un point ou même à rien
du tout, avec vous, fixant un écran désespérément vide essayant de comprendre pourquoi le programme
ne marche pas.
|
 |
 |
 |
The
order of initialization described
in the previous section isn’t quite complete, and that’s the key to
solving the mystery. The actual process of initialization is:
|
 |
L'ordre de l'initialisation décrit dans la section précédente n'est pas
complètement exhaustif, et c'est la clé qui va résoudre le mystère. La procédure d'initialisation
est la suivante :
|
 |
 |
 |
- The storage allocated for
the object is initialized to binary zero before anything else
happens.
- The
base-class constructors are called as described previously. At this point, the
overridden draw( ) method is called (yes, before the
RoundGlyph constructor is called), which discovers a radius value
of zero, due to step
1.
- Member
initializers are called in the order of
declaration.
- The
body of the derived-class constructor is
called.
|
 |
- La zone allouée à l'objet est initialisée à zéro binaire avant
tout.
- Les constructeurs des classes de base sont appelés comme décrit
précédemment. Puis, la méthode draw() redéfinie est appelée (et oui,
avant l'appel du constructeur de RoundGlyph), et utilise radius qui vaut
zéro à cause de la première étape.
- 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é
|
 |
 |
 |
There’s an
upside to this, which is that everything is at least initialized to zero (or
whatever zero means for that particular data type) and not just left as garbage.
This includes object references that are embedded inside a class via
composition, which become null. So if you forget to initialize that
reference you’ll get an exception at run-time. Everything else gets zero,
which is usually a telltale value when looking at output.
|
 |
Le bon coté est que tout est au moins initialisé au zéro (selon la
signification de zéro pour un type de donnée particulier) et non laissé avec n'importe quelles
valeurs. Cela inclut les références d'objet qui sont incorporés à l'intérieur d'une classe par
composition, et qui passent à null. Ainsi si vous oubliez d'initialiser une
référence vous obtiendrez une exception à l'exécution. Tout le reste est à zéro, qui est
habituellement une valeur que l'on repère en examinant l'output.
|
 |
 |
 |
On the other hand, you should be pretty
horrified at the outcome of this program. You’ve done a perfectly logical
thing, and yet the behavior is mysteriously wrong, with no complaints from the
compiler. (C++ produces more rational behavior in this situation.) Bugs like
this could easily be buried and take a long time to discover.
|
 |
D'autre part, vous devez être assez horrifiés du résultat de ce programme.
Vous avez fait une chose parfaitement logique et pourtant le comportement est mystérieusement faux,
sans aucune manifestation du compilateur (C ++ a un comportement plus correct dans la même
situation). Les bugs dans ce goût là peuvent facilement rester cachés et nécessiter pas mal de
temps d'investigation.
|
 |
 |
 |
As a result, a good guideline for
constructors is, “Do as little as possible to set the object into a good
state, and if you can possibly avoid it, don’t call any methods.”
The only safe methods to call inside a constructor are those that are
final in the base class. (This also applies to
private
methods, which are automatically final.) These cannot be overridden and
thus cannot produce this kind of
surprise.
|
 |
Il en résulte la recommandation suivante pour les constructeurs:
« Faire le minimum pour mettre l'objet dans un bon état et si possible, ne pas appeler de
méthodes. » Les seules méthodes qui sont appelables en toute sécurité à l'intérieur d'un
constructeur sont celles qui sont finales dans la classe de base (même chose pour les méthodes
privées, qui sont automatiquement finales.). Celles-ci ne peuvent être redéfinies et ne réservent
donc pas de surprise.
|
 |
 |
 |
Designing with
inheritance
|
 |
Concevoir avec l'héritage
|
 |
 |
 |
Once you learn about polymorphism, it can
seem that everything ought to be inherited because polymorphism is such a clever
tool. This can burden your designs; in fact if you choose inheritance first when
you’re using an existing class to make a new class, things can become
needlessly complicated.
|
 |
Après avoir vu le polymorphisme, c'est un instrument tellement astucieux
qu'on dirait que tout doit être hérité. Ceci peut alourdir votre conception; en fait si vous faites
le choix d'utiliser l'héritage d'entrée lorsque vous créez une nouvelle classe à partir d'une
classe existante, cela peut devenir inutilement compliqué.
|
 |
 |
 |
A better approach is to choose
composition first, when it’s
not obvious which one you should use. Composition does not force a design into
an inheritance hierarchy. But composition is also more flexible since it’s
possible to dynamically choose a type (and thus behavior) when using
composition, whereas inheritance requires an exact type to be known at
compile-time. The following example illustrates this:
|
 |
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
hiérarchie d'héritage, mais elle est également plus flexible car il est alors possible de choisir
dynamiquement un type (et son comportement), alors que l'héritage requiert un type exact déterminé
au moment de la compilation. L'exemple suivant l'illustre :
|
 |
 |
 |
//: c07:Transmogrify.java // Dynamically changing the behavior of // an object via 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(); // Prints "HappyActor" s.change(); s.go(); // Prints "SadActor" } } ///:~
|
 |
//: 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(); s.go(); // Imprime "SadActor" } } ///:~
|
 |
 |
 |
A Stage object contains a
reference to an Actor, which is initialized to a HappyActor
object. This means go( ) produces a particular behavior. But since a
reference can be rebound to a different object at run-time, a reference for a
SadActor object can be substituted in a and then the behavior
produced by go( ) changes. Thus you gain dynamic flexibility at
run-time. (This is also called the State Pattern. See Thinking in
Patterns with Java, downloadable at www.BruceEckel.com.) In contrast,
you can’t decide to inherit differently at run-time; that must be
completely determined at compile-time.
|
 |
Un objet Stage contient une référence vers un
Actor, qui est initialisé par un objet HappyActor. Cela signifie
que go() produit un comportement particulier. Mais puisqu'une référence peut être
reliée à un objet différent à l'exécution, une référence à un objet SadActor peut
être substituée dans a et alors le comportement produit par go()
change. Ainsi vous gagnez en flexibilité dynamique à l'exécution (également appelé le State
Pattern. Voir Thinking in Patterns with Java, téléchargeable sur
www.BruceEckel.com.). Par contre, vous ne pouvez pas décider d'hériter différemment à
l'exécution; cela doit être complètement déterminé à la compilation.
|
 |
 |
 |
A general guideline is “Use
inheritance to express differences in behavior, and fields to express variations
in state.” In the above example, both are used: two different classes are
inherited to express the difference in the act( ) method, and
Stage uses composition to allow its state to be changed. In this case,
that change in state happens to produce a change in
behavior.
|
 |
Voici une recommandation générale: « Utilisez l'héritage pour exprimer
les différences de comportement, et les champs pour exprimer les variations d'état. » Dans
l'exemple ci-dessus, les deux sont utilisés: deux classes différentes héritent pour exprimer la
différence dans la méthode 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.
|
 |
 |
 |
Pure inheritance vs.
extension
|
 |
Héritage pur contre extensionname="Index739">
|
 |
 |
 |
When studying inheritance, it would seem
that the cleanest way to create an inheritance hierarchy is to take the
“pure” approach. That is, only methods that have been established in
the base class or interface are to be overridden in the derived class, as
seen in this diagram:
|
 |
Lorsque l'on étudie l'héritage, il semblerait que la façon la plus propre
de créer une hiérarchie d'héritage est de suivre l'approche « pure. » A savoir que seules
les méthodes qui ont été établies dans la classe de base ou l'interface sont
surchargeables dans la classe dérivée, comme le montre ce diagramme :
|
 |
 |
 |
|
 |
|
 |
 |
 |
This can be termed a pure
“is-a” relationship because the interface of
a class establishes what it is. Inheritance guarantees that any derived class
will have the interface of the base class and nothing less. If you follow the
above diagram, derived classes will also have no more than the base class
interface.
|
 |
Ceci peut se nommer une relation « est-un » pure car l'interface
d'une classe établie ce qu'elle est. L'héritage garantie que toute classe dérivée aura l'interface
de la classe de base et rien de moins. Si vous suivez le diagramme ci-dessus, les classes dérivées
auront également pas plus que l'interface de la classe de base.
|
 |
 |
 |
This can be thought of as
pure substitution, because derived class objects
can be perfectly substituted for the base class, and you never need to know any
extra information about the subclasses when you’re using
them:
|
 |
Ceci peut être considéré comme une substitution pure, car les
objets de classe dérivée peuvent être parfaitement substitués par la classe de base, et vous n'avez
jamais besoin de connaître d' information supplémentaire sur les sous-classes quand vous les
utilisez :
|
 |
 |
 |
|
 |
|
 |
 |
 |
That is, the base class can receive any
message you can send to the derived class because the two have exactly the same
interface. All you need to do is upcast from the derived class and never look
back to see what exact type of object you’re dealing with. Everything is
handled through polymorphism.
|
 |
Cela étant, la classe de base peut recevoir tout message que vous pouvez
envoyer à la classe dérivée car les deux ont exactement la même interface. Tout ce que vous avez
besoin de faire est d'utiliser l'upcast à partir de la classe dérivée et de ne jamais regarder en
arrière pour voir quel type exact d'objet vous manipulez.
|
 |
 |
 |
When you see it this way, it seems like a
pure “is-a” relationship is the only sensible way to do things, and
any other design indicates muddled thinking and is by
definition broken. This too is a trap. As soon as you start thinking this way,
you’ll turn around and discover that extending the interface (which,
unfortunately, the keyword extends seems to
encourage) is the perfect solution to a particular problem. This could be termed
an “is-like-a” relationship because the
derived class is like the base class—it has the same fundamental
interface—but it has other features that require additional methods to
implement:
|
 |
En la considérant de cette manière, une relation pure « est-un »
semble la seule façon sensée de pratiquer, et toute autre conception dénote une réflexion
embrouillée et est par définition hachée. Ceci aussi est un piège. Dès que vous commencez à penser
de cette manière, vous allez tourner en rond et découvrir qu'étendre l'interface (ce que,
malencontreusement, le mot clé extends semble encourager) est la solution parfaite
à un problème particulier. Ceci pourrait être qualifié de relation « est-comme-un » car
la classe dérivée est comme la classe de base, elle a la même interface fondamentale mais
elle a d'autres éléments qui nécessitent d'implémenter des méthodes
additionnelles :
|
 |
 |
 |
|
 |
|
 |
 |
 |
While this is also a useful and sensible
approach (depending on the situation) it has a drawback. The extended part of
the interface in the derived class is not available from the base class, so once
you upcast you can’t call the new methods:
|
 |
Mais si cette approche est aussi utile et sensée (selon la situation) elle
a un inconvénient. La partie étendue de l'interface de la classe dérivée 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 méthodes :
|
 |
 |
 |
|
 |
|
 |
 |
 |
If you’re not upcasting in this
case, it won’t bother you, but often you’ll get into a situation in
which you need to rediscover the exact type of the object so you can access the
extended methods of that type. The following section shows how this is
done.
|
 |
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 accéder aux méthodes étendues de ce type. La section suivante montre comment cela
se passe.
|
 |
 |
 |
Downcasting and run-time type identification
|
 |
Downcasting et identification du type à l'exécution
|
 |
 |
 |
Since you lose the specific type
information via an upcast (moving up the inheritance hierarchy), it makes
sense that to retrieve the type information—that is, to move back down the
inheritance hierarchy—you use a downcast.
However, you know an upcast is always safe; the base class cannot have a bigger
interface than the derived class, therefore every message you send through the
base class interface is guaranteed to be accepted. But with a downcast, you
don’t really know that a shape (for example) is actually a circle. It
could instead be a triangle or square or some other type.
|
 |
Puisque vous avez perdu l'information du type spécifique par un
upcast (en remontant la hiérarchie d'héritage), il est logique de retrouver le type en
redescendant la hiérarchie d'héritage par un downcast. Cependant, vous savez qu'un upcast
est toujours sûr; la classe de base ne pouvant pas avoir une interface plus grande que la classe
dérivée, 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
réalité un cercle. Cela pourrait plutôt être un triangle ou un carré ou quelque chose d'un autre
type.
|
 |
 |
 |
|
 |
|
 |
 |
 |
To solve this problem there must be some
way to guarantee that a downcast is correct, so you won’t accidentally
cast to the wrong type and then send a message that the object can’t
accept. This would be quite unsafe.
|
 |
Pour résoudre ce problème 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.
|
 |
 |
 |
In some languages (like C++) you must
perform a special operation in order to get a type-safe downcast, but in Java
every cast is checked! So even though it looks like you’re just
performing an ordinary parenthesized cast, at run-time this cast is checked to
ensure that it is in fact the type you think it is. If it isn’t, you get a
ClassCastException. This act of checking types at run-time is called
run-time type identification
(RTTI). The following example demonstrates the behavior of
RTTI:
|
 |
Dans certains langages (comme C++) vous devez effectuer une opération
spéciale afin d'avoir un cast ascendant sûr, mais en Java tout cast est vérifié! Donc même
si il semble que vous faites juste un cast explicite ordinaire, lors de l'exécution ce cast est
vérifié pour assurer qu'en fait il s'agit bien du type auquel vous vous attendez. Si il ne l'est
pas, vous récupérez une ClassCastException. Cette action de vérifier les types au
moment de l'exécution est appelé run-time type identification (RTTI). L'exemple suivant
montre le comportement de la RTTI :
|
 |
 |
 |
//: 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(); // Compile-time: method not found in Useful: //! x[1].u(); ((MoreUseful)x[1]).u(); // Downcast/RTTI ((MoreUseful)x[0]).u(); // Exception thrown } } ///:~
|
 |
//: 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: méthode non trouvée dans Useful: //! x[1].u(); ((MoreUseful)x[1]).u(); // Downcast/RTTI ((MoreUseful)x[0]).u(); // Exception envoyée } } ///:~
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |