 |
 |
6) Réutiliser les classes |
|
 |
|
Texte original |
 |
Traducteur :
Olivier Thomann |
|
 |
///
|
Ce chapitre contient
4 pages
1
2
3
4
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
//: c06:Jurassic.java
// Making an entire class final.
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
} ///:~
|
 |
// ! c06:Jurassic.java // Mettre une classe entière final.
class SmallBrain {}
final class Dinosaur { int i = 7; int j = 1; SmallBrain x = new SmallBrain(); void f() {} }
// ! class Further extends Dinosaur {} // erreur : Ne peut pas étendre la classe final 'Dinosaur'
public class Jurassic { public static void main(String[] args) { Dinosaur n = new Dinosaur(); n.f(); n.i = 40; n.j++; } } ///:~
|
 |
 |
 |
Note that the data members can be
final or not, as you choose. The same rules apply to final for
data members regardless of whether the class is defined as final.
Defining the class as final simply prevents inheritance—nothing
more. However, because it prevents inheritance all
methods in a final class are implicitly final, since there’s
no way to override them. So the compiler has the same efficiency options as it
does if you explicitly declare a method final.
|
 |
Notons que les données membres peuvent ou non être final,
comme on le choisit. Les mêmes règles s'appliquent à final pour les données
membres sans tenir compte du fait que la classe est ou non final. Définir une
classe comme final empêche simplement l'héritage - rien de plus. Quoiqu'il en
soit, parce que cela empêche l'héritage, toutes les méthodes d'une classe final
sont implicitement final, étant donné qu'il n'y a aucun moyen de les surcharger.
Donc le compilateur à les mêmes options d'efficacité que si on définissait explicitement une
méthode final.
|
 |
 |
 |
You can add the final specifier to
a method in a final class, but it doesn’t add any
meaning.
|
 |
On peut ajouter le modificateur final à une méthode dans
une classe final, mais ça ne rajoute aucune signification.
|
 |
 |
 |
Final caution
|
 |
Attention finale
|
 |
 |
 |
It can seem to be sensible to make a
method final while you’re designing a class. You might feel that
efficiency
is very important when using your class and that no one could possibly want to
override your methods anyway. Sometimes this is true.
|
 |
Il peut paraître raisonnable de déclarer une méthode final
alors qu'on conçoit une classe. On peut décider que l'efficacité est très importante quand on
utilise la classe et que personne ne pourrait vouloir surcharger les méthodes de toute manière.
Cela est parfois vrai.
|
 |
 |
 |
But be careful with your assumptions. In
general, it’s difficult to anticipate how a class can be reused,
especially a general-purpose class. If you define a method as final you
might prevent the possibility of reusing your class through inheritance in some
other programmer’s project simply because you couldn’t imagine it
being used that way.
|
 |
Mais il faut être prudent avec ces hypothèses. En général, il est difficile
d' anticiper comment une classe va être réutilisée, surtout une classe générique. Si on définit une
méthode comme final, on devrait empêcher la possibilité de réutiliser cette classe
par héritage dans les projets d'autres programmeurs simplement parce qu'on ne pourrait pas
l'imaginer être utilisée de cette manière.
|
 |
 |
 |
The standard Java library is a good
example of this. In particular, the Java 1.0/1.1 Vector class was
commonly used and might have been even more useful if, in the name of
efficiency, all the methods hadn’t been made final. It’s
easily conceivable that you might want to inherit and override with such a
fundamentally useful class, but the designers somehow decided this wasn’t
appropriate. This is ironic for two reasons. First, Stack is inherited
from Vector, which says that a Stack is a Vector,
which isn’t really true from a logical standpoint. Second, many of the
most important methods of Vector, such as addElement( ) and
elementAt( ) are synchronized. As you will see in Chapter 14,
this incurs a significant performance overhead that probably wipes out any gains
provided by final. This lends credence to the theory that programmers are
consistently bad at guessing where optimizations should occur. It’s just
too bad that such a clumsy design made it into the standard library where we
must all cope with it. (Fortunately, the Java 2 container library replaces
Vector with ArrayList, which behaves much more civilly.
Unfortunately, there’s still plenty of new code being written that uses
the old container library.)
|
 |
La bibliothèque standard de Java en est un bon exemple. En particulier, la
classe Vector en Java 1.0/1.1 était communément utilisée et pourrait avoir encore
été plus utile si, au nom de l'efficacité, toutes les méthodes n'avaient pas été
final. Ça parait facilement concevable que l'on puisse vouloir hériter et
surcharger une telle classe fondamentale, mais les concepteurs d'une manière ou d'une autre ont
décidé que ce n'était pas approprié. C'est ironique pour deux raisons. Premièrement,
Stack hérite de Vector, ce qui dit qu'une Stack
est un Vector, ce qui n'est pas vraiment vrai d'un point de vue logique.
Deuxièmement, beaucoup des méthodes importantes de Vector, telles que
addElement( ) et elementAt( ) sont synchronized.
Comme nous verrons au chapitre 14, ceci implique un surcoût significatif au niveau des performances
qui rend caduque tout gain fourni par final. Ceci donne de la crédibilité à la
théorie que les programmeurs sont constamment mauvais pour deviner où les optimisations devraient
être faites.Il est vraiment dommage qu'une conception aussi maladroite ait fait son chemin dans la
bibliothèque standard que nous utilisons tous. Heureusement, la bibliothèque de collections Java 2
remplace Vector avec ArrayList, laquelle se comporte bien mieux.
Malheureusement , il y a encore beaucoup de code nouveau écrit qui utilise encore l'ancienne
bibliothèques de collections.
|
 |
 |
 |
It’s also interesting to note that
Hashtable, another important standard library class, does not have
any final methods. As mentioned elsewhere in this book, it’s quite
obvious that some classes were designed by completely different people than
others. (You’ll see that the method names in Hashtable are much
briefer compared to those in Vector, another piece of evidence.) This is
precisely the sort of thing that should not be obvious to consumers of a
class library. When things are inconsistent it just makes more work for the
user. Yet another paean to the value of design and code walkthroughs. (Note that
the Java 2 container library replaces Hashtable with
HashMap.)
|
 |
Il est intéressant de noter également que Hashtable, une
autre classe importante de la bibliothèque standard, n'a pas une
seule méthode final. Comme mentionné à plusieurs endroits dans ce livre, il est
plutôt évident que certaines classes ont été conçues par des personnes totalement différentes. Vous
verrez que les noms de méthodes dans Hashtable sont beaucoup plus court comparés à
ceux de Vector, une autre preuve. C'est précisément ce genre de choses qui ne
devrait pas être évident aux utilisateurs d'une bibliothèque de classes. Quand plusieurs choses
sont inconsistantes, cela fait simplement plus de travail pour l'utilisateur. Encore un autre grief
à la valeur de la conception et de la qualité du code. À noter que la bibliothèque de collection de
Java 2 remplaces Hashtable avec HashMap.
|
 |
 |
 |
Initialization and class
loading
|
 |
Initialisation et chargement de classes
|
 |
 |
 |
In more traditional languages, programs
are loaded all at once as part of the startup process. This is followed by
initialization, and then the program begins. The process of initialization in
these languages must be carefully controlled so that the order of initialization
of statics doesn’t cause trouble. C++, for example, has problems if
one static expects another static to be valid before the second
one has been initialized.
|
 |
Dans des langages plus traditionnels, les programmes sont chargés tout d'un
coup au moment du démarrage. Ceci est suivi par l'initialisation et ensuite le programme commence.
Le processus d'initialisation dans ces langages doit être contrôlé avec beaucoup d'attention afin
que l'ordre d'initialisation des statics ne pose pas de problème. C++, par
exemple, a des problèmes si une static attend qu'une autre static
soit valide avant que la seconde ne soit initialisée.
|
 |
 |
 |
Java doesn’t have this problem
because it takes a different approach to loading. Because everything in Java is
an object, many activities become easier, and this is one of them. As you will
learn more fully in the next chapter, the compiled code for each class exists in
its own separate file. That file isn’t loaded until the code is needed. In
general, you can say that “Class code is loaded at the point of first
use.” This is often not until the first object of that class is
constructed, but loading also occurs when a static field or static
method is accessed.
|
 |
Java n'a pas ce problème parce qu'il a une autre approche du chargement.
Parce que tout en Java est un objet, beaucoup d'actions deviennent plus facile, et ceci en est un
exemple. Comme vous l'apprendrez plus complètement dans le prochain chapitre, le code compilé de
chaque classe existe dans son propre fichier séparé. Ce fichier n'est pas chargé tant que ce n'est
pas nécessaire. En général, on peut dire que « le code d'une classe est chargé au moment de la
première utilisation ». C'est souvent au moment où le premier objet de cette classe est
construit, mais le chargement se produit également lorsqu'on accède à un champ
static ou une méthode static.
|
 |
 |
 |
The point of first use is also where the
static initialization takes place. All the static objects and the
static code block will be initialized in textual
order (that is, the order that you write them down in the
class definition) at the point of loading. The statics, of course, are
initialized only
once.
|
 |
Le point de première utilisation est également là où l'initialisation des
statics a lieu. Tous les objets static et le bloc de code
static sera initialisé dans l'ordre textuel (l'ordre dans lequel ils sont définis
dans la définition de la classe) au moment du chargement. Les statics, bien sûr,
ne sont initialisés qu'une seule fois.
|
 |
 |
 |
Initialization with inheritance
|
 |
Initialisation avec héritage
|
 |
 |
 |
It’s helpful to look at the whole
initialization process, including
inheritance, to get a full picture of what happens. Consider the following
code:
|
 |
Il est utile de regarder l'ensemble du processus d'initialisation, incluant
l'héritage pour obtenir une compréhension globale de ce qui se passe. Considérons le code
suivant:
|
 |
 |
 |
//: c06:Beetle.java
// The full process of initialization.
class Insect {
int i = 9;
int j;
Insect() {
prt("i = " + i + ", j = " + j);
j = 39;
}
static int x1 =
prt("static Insect.x1 initialized");
static int prt(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
int k = prt("Beetle.k initialized");
Beetle() {
prt("k = " + k);
prt("j = " + j);
}
static int x2 =
prt("static Beetle.x2 initialized");
public static void main(String[] args) {
prt("Beetle constructor");
Beetle b = new Beetle();
}
} ///:~
|
 |
// ! c06:Beetle.java // Le processus complet d'initialisation.
class Insect { int i = 9; int j; Insect() { prt("i = " + i + ", j = " + j); j = 39; } static int x1 = prt("static Insect.x1 initialisé"); static int prt(String s) { System.out.println(s); return 47; } }
public class Beetle extends Insect { int k = prt("Beetle.k initialisé"); Beetle() { prt("k = " + k); prt("j = " + j); } static int x2 = prt("static Beetle.x2 initialisé"); public static void main(String[] args) { prt("Constructeur Beetle"); Beetle b = new Beetle(); } } ///:~
|
 |
 |
 |
The output for this program
is:
|
 |
La sortie de ce programme est:
|
 |
 |
 |
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
|
 |
static Insect.x1 initialisé static Beetle.x2 initialisé Constructeur Beetle i = 9, j = 0 Beetle.k initialisé k = 47 j = 39
|
 |
 |
 |
The first thing that happens when you run
Java on Beetle is that you try to access Beetle.main( ) (a
static method), so the loader goes out and finds the compiled code for
the Beetle class (this
happens to be in a file called Beetle.class). In the process of loading
it, the loader notices that it has a base class (that’s what the
extends keyword says), which it then loads. This will happen whether or
not you’re going to make an object of that base class. (Try commenting out
the object creation to prove it to yourself.)
|
 |
La première chose qui se passe quand on exécute Beetle en
Java est qu'on essaye d'accéder à Beetle.main( ) (une méthode
static), donc le chargeur cherche et trouve le code compilé pour la classe
Beetle (en général dans le fichier appelé Beetle.class). Dans le
processus de son chargement, le chargeur remarque qu'elle a une classe de base (c'est ce que le
mot-clé extends veut dire), laquelle est alors chargée. Ceci se produit qu'on
construise ou non un objet de la classe de base. Essayez de commenter la création de l'objet pour
vous le prouver.
|
 |
 |
 |
If the base class has a base class, that
second base class would then be loaded, and so on. Next, the
static initialization in
the root base class (in this case, Insect) is performed, and then the
next derived class, and so on. This is important because the derived-class
static initialization might depend on the base class member being initialized
properly.
|
 |
Si la classe de base a une classe de base, cette seconde classe de base
sera à son tour chargée, etc. Ensuite, l'initialisation static dans la classe de base racine (dans ce cas,
Insect) est effectuée, ensuite la prochaine classe dérivée, etc. C'est important
parce que l'initialisation static de la classe dérivée pourrait dépendre de l'initialisation
correcte d'un membre de la classe de base.
|
 |
 |
 |
At this point, the necessary classes have
all been loaded so the object can be created. First, all the primitives in this
object are set to their default values and the object references are set to
null—this happens in one fell swoop by setting the memory in the
object to binary zero. Then the base-class constructor will be called. In
this case the call is automatic, but you can also specify the base-class
constructor call (as the first operation in the Beetle( )
constructor) using super. The base class construction goes through the
same process in the same order as the derived-class constructor. After the
base-class constructor completes, the instance variables are initialized in
textual order. Finally, the rest of the body of the constructor is
executed.
|
 |
À ce point, les classes nécessaires ont été chargées, donc l'objet peut
être créé. Premièrement, toutes les primitives dans l'objet sont initialisées à leurs valeurs par
défaut et les références objets sont initialisées à null - ceci se produit en une
seule passe en mettant la mémoire dans l'objet au zéro binaire. Ensuite le constructeur de la
classe de base est appelé. Dans ce cas, l'appel est automatique, mais on peut également spécifier
le constructeur de la classe de base (comme la première opération dans le constructeur
Beetle( )) en utilisant super. La constructeur de la classe
de base suit le même processus dans le même ordre que le constructeur de la classe dérivée. Lorsque
le constructeur de la classe de base a terminé, les variables d'instance sont initialisées dans
l'ordre textuel. Finalement le reste du corps du constructeur est exécuté.
|
 |
 |
 |
Summary
|
 |
Résumé
|
 |
 |
 |
Both inheritance and composition allow
you to create a new type from existing types. Typically, however, you use
composition to reuse existing types as part of the underlying implementation of
the new type, and inheritance when you want to reuse the interface. Since the
derived class has the base-class interface, it can be upcast to the base,
which is critical for polymorphism, as you’ll see in the next chapter.
|
 |
L'héritage et la composition permettent tous les deux de créer de nouveaux
types depuis des types existants. Typiquement, quoiqu'il en soit, on utilise la composition pour
réutiliser des types existants comme partie de l'implémentation sous-jacente du nouveau type, et
l'héritage quand on veut réutiliser l'interface. Étant donné que la classe dérivée possède
l'interface de la classe de base, on peut faire un transtypage ascendant vers la
classe de base, lequel est critique pour le polymorphisme, comme vous le verrez dans le prochain
chapitre.
|
 |
 |
 |
Despite the strong emphasis on
inheritance in object-oriented programming, when you start a design you should
generally prefer composition during the first cut and use inheritance only when
it is clearly necessary. Composition tends to be more flexible. In addition, by
using the added artifice of inheritance with your member type, you can change
the exact type, and thus the behavior, of those member objects at run-time.
Therefore, you can change the behavior of the composed object at
run-time.
|
 |
En dépit de l'importance particulièrement forte de l'héritage dans la
programmation orienté objet, quand on commence une conception on devrait généralement préférer la
composition durant la première passe et utiliser l'héritage seulement quand c'est clairement
nécessaire. La composition tend à être plus flexible. De plus, par le biais de l'héritage, vous
pouvez changer le type exact de vos objets, et donc, le comportement, de ces objets membres à
l'exécution. Par conséquent, on peut changer le comportement d'objets composés à
l'exécution.
|
 |
 |
 |
Although code reuse through composition
and inheritance is helpful for rapid project development, you’ll generally
want to redesign your class hierarchy before allowing
other programmers to become dependent on it. Your goal is a hierarchy in which
each class has a specific use and is neither too big (encompassing so much
functionality that it’s unwieldy to reuse) nor annoyingly small (you
can’t use it by itself or without adding
functionality).
|
 |
Bien que la réutilisation du code à travers la composition et l'héritage
soit utile pour un développement rapide, on voudra généralement concevoir à nouveau la hiérarchie
de classes avant de permettre aux autres programmeurs d'en devenir dépendant. Votre but est une
hiérarchie dans laquelle chaque classe a un usage spécifique et n'est ni trop grosse (englobant
tellement de fonctionnalités qu'elle en est difficile à manier pour être réutilisée) ni trop
ennuyeusement petite (on ne peut pas l'utiliser par elle-même ou sans ajouter de nouvelles
fonctionnalités).
|
 |
 |
 |
Exercises
|
 |
Exercices
|
 |
 |
 |
Solutions to selected exercises
can be found in the electronic document The Thinking in Java Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
|
 |
Les solutions aux 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 depuis www.BruceEckel.com.
|
 |
 |
 |
- Create two classes,
A and B, with default constructors (empty argument lists) that
announce themselves. Inherit a new class called C from A, and
create a member of class B inside C. Do not create a constructor
for C. Create an object of class C and observe the
results.
- Modify
Exercise 1 so that A and B have constructors with arguments
instead of default constructors. Write a constructor for C and perform
all initialization within C’s constructor.
- Create a simple
class. Inside a second class, define a field for an object of the first class.
Use lazy initialization to instantiate this
object.
- Inherit a
new class from class Detergent. Override scrub( ) and add a
new method called
sterilize( ).
- Take
the file Cartoon.java and comment out the constructor for the
Cartoon class. Explain what
happens.
- Take the
file Chess.java and comment out the constructor for the Chess
class. Explain what
happens.
- Prove that
default constructors are created for you by the
compiler.
- Prove that
the base-class constructors are (a) always called, and (b) called before
derived-class
constructors.
- Create
a base class with only a nondefault constructor, and a derived class with both a
default and nondefault constructor. In the derived-class constructors, call the
base-class
constructor.
- Create
a class called Root that contains an instance of each of classes (that
you also create) named Component1, Component2, and
Component3. Derive a class Stem from Root that also
contains an instance of each “component.” All classes should have
default constructors that print a message about that
class.
- Modify
Exercise 10 so that each class only has nondefault
constructors.
- Add a
proper hierarchy of cleanup( ) methods to all the classes in
Exercise 11.
- Create
a class with a method that is overloaded three times. Inherit a new class, add a
new overloading of the method, and show that all four methods are available in
the derived
class.
- In
Car.java add a service( ) method to Engine and call
this method in
main( ).
- Create
a class inside a package. Your class should contain a protected method.
Outside of the package, try to call the protected method and explain the
results. Now inherit from your class and call the protected method from
inside a method of your derived
class.
- Create a
class called Amphibian. From this, inherit a class called Frog.
Put appropriate methods in the base class. In main( ), create a
Frog and upcast it to Amphibian, and demonstrate that all the
methods still
work.
- Modify
Exercise 16 so that Frog overrides the method definitions from the base
class (provides new definitions using the same method signatures). Note what
happens in
main( ).
- Create
a class with a static final field and a final field and
demonstrate the difference between the
two.
- Create a class
with a blank final reference to an object. Perform the initialization of
the blank final inside a method (not the constructor) right before you
use it. Demonstrate the guarantee that the final must be initialized
before use, and that it cannot be changed once
initialized.
- Create
a class with a final method. Inherit from that class and attempt to
override that
method.
- Create a
final class and attempt to inherit from
it.
- Prove that class
loading takes place only once. Prove that loading may be caused by either the
creation of the first instance of that class, or the access of a static
member.
- In
Beetle.java, inherit a specific type of beetle from class Beetle,
following the same format as the existing classes. Trace and explain the
output.
[ Previous Chapter ]
[ Short TOC ]
[ Table of Contents ]
[ Index ]
[ Next Chapter ]
Last Update:04/24/2000
|
 |
- Créer deux classes, A et B, avec des
constructeurs par défaut (liste d'arguments vide) qui s'annoncent eux-même. Faire hériter une
nouvelle classe C de A, et créer une classe membre
B à l'intérieur de C. Ne pas créer un constructeur pour
C. Créer un objet d'une classe C et observer les
résultats.
- Modifier l'exercice 1 afin que A et B
aient des constructeurs avec arguments au lieu de constructeurs par défaut. Écrire un constructeur
pour C et effectuer toutes les initialisations à l'intérieur du constructeur de
C.
- Créer une simple classe. À l'intérieur d'une seconde classe, définir un
champ pour un objet de la première classe. Utiliser l'initialisation paresseuse pour instancier cet
objet.
- Hériter une nouvelle classe de la classe Detergent.
Redéfinir scrub( ) et ajouter une nouvelle méthode appelée
sterilize( ).
- Prendre le fichier Cartoon.java et enlever le commentaire
autour du constructeur de la classe Cartoon. Expliquer ce qui arrive.
- Prendre le fichier Chess.java et enlever le commentaire
autour du constructeur de la classe Chess. Expliquer ce qui se passe.
- Prouver que des constructeurs par défaut sont créés pour vous par le
compilateur.
- Prouver que les constructeurs de la classe de base sont (a) toujours
appelés et (b) appelés avant les constructeurs des classes dérivées.
- Créer une classe de base avec seulement un constructeur qui ne soit pas un
constructeur par défaut, et une classe dérivée avec à la fois une constructeur par défaut et un
deuxième constructeur. Dans les constructeurs de la classe dérivée, appeler le constructeur de la
classe de base.
- Créer une classe appelée Root qui contient une instance
de chaque classe (que vous aurez également créé) appelées Component1,
Component2, et Component3. Dériver une classe
Stem de Root qui contienne également une instance de chaque
« component ». Toutes les classes devraient avoir un constructeur par défaut qui affiche
un message au sujet de cette classe.
- Modifier l'exercice 10 de manière à ce que chaque classe ait des
constructeurs qui ne soient pas des constructeurs par défaut.
- Ajouter une hiérarchie propre de méthodes cleanup( )
à toutes les classes dans l'exercice 11.
- Créer une classe avec une méthode surchargée trois fois. Hériter une
nouvelle classe, ajouter une nouvelle surcharge de la méthode et montrer que les quatre méthodes
sont disponibles dans la classe dérivée.
- Dans Car.java ajouter une méthode
service( ) à Engine et appeler cette méthode dans
main( ).
- Créer une classe à l'intérieur d'un package. Cette classe doit contenir
une méthode protected. À l'extérieur du package, essayer d'appeler la méthode
protected et expliquer les résultats. Maintenant hériter de cette classe et
appeler la méthode protected depuis l'intérieur d'une méthode de la classe
dérivée.
- Créer une classe appelée Amphibian. De celle-ci, hériter
une classe appelée Frog. Mettre les méthodes appropriées dans la classe de base.
Dans main( ), créer une Frog et faire un transtypage
ascendant Amphibian, et démontrer que toutes les méthodes fonctionnent
encore.
- Modifier l'exercice 16 de manière que Frog redéfinisse
les définitions de méthodes de la classe de base (fournir de nouvelles définitions utilisant les
mêmes signatures des méthodes). Noter ce qui se passe dans
main( ).
- Créer une classe avec un champ static final et un champ
final et démontrer la différence entre les deux.
- Créer une classe avec une référence final sans
initialisation vers un objet. Exécuter l'initialisation de cette final sans
initialisation à l'intérieur d'une méthode (pas un constructeur) juste avant de l'utiliser.
Démontrer la garantie que le final doit être initialisé avant d'être utilisé, et
ne peut pas être changé une fois initialisé.
- Créer une classe avec une méthode final. Hériter de cette
classe et tenter de redéfinir cette méthode.
- Créer une classe final et tenter d'en
hériter.
- Prouver que le chargement d'une classe n'a lieu qu'une fois. Prouver que
le chargement peut être causé soit par la création de la première instance de cette classe, soit
par l'accès à un membre static.
- Dans Beetle.java, hériter un type spécifique de
coccinelle de la classe Beetle, suivant le même format des classes existantes.
Regarder et expliquer le flux de sortie du programme.
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |