 |
 |
A) Passage et Retour d'Objets |
|
 |
|
Texte original |
 |
Traducteur : Jérome QUELIN |
|
 |
|
Ce chapitre contient 6 pages
1
2
3
4
5
6
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
|
 |
05.07.01 - version 5.5 : - Ajout des tags de séparation de pages (Armel). 28.04.01 - version 5.4 : - Nettoyage du code html (titres, paragraphes, tableaux, images, ancres) par Armel. 17.07.2000 - version 5.3 : - Corrections apportées par Olivier Thomann. 26.06.2000 - version 5.2 : - Insertion du journal de log. 19.06.2000 - version 5.1 : - Première publication sur eGroups. Traducteur : - Jérome QUELIN Texte original : -Thinking in Java, 2nd edition, Revision 10 © 2000 by Bruce Eckel
|
 |
 |
 |
A: Passing & Returning Objects
|
 |
A : Passage & et Retour d'Objets
|
 |
 |
 |
By now you should be reasonably
comfortable with the idea that when you’re “passing” an
object, you’re actually passing a reference.
|
 |
Vous devriez maintenant être conscient que lorsque vous
« passez » un objet, vous passez en fait une référence sur cet objet.
|
 |
 |
 |
In many programming languages you can use
that language’s “regular” way to pass objects around, and most
of the time everything works fine. But it always seems that there comes a point
at which you must do something irregular and suddenly things get a bit more
complicated (or in the case of C++, quite complicated). Java is no exception,
and it’s important that you understand exactly what’s happening as
you pass objects around and manipulate them. This appendix will provide that
insight.
|
 |
Presque tous les langages de programmation possèdent une façon
« normale » de passer des objets, et la plupart du temps tout se passe bien. Mais il
arrive toujours un moment où on doit faire quelque chose d'un peu hors-norme, et alors les choses
se compliquent un peu (voire beaucoup dans le cas du C++). Java ne fait pas exception à la règle,
et il est important de comprendre exactement les mécanismes du passage d'arguments et de la
manipulation des objets passés. Cette annexe fournit des précisions quant à ces
mécanismes.
|
 |
 |
 |
Another way to pose the question of this
appendix, if you’re coming from a programming language so equipped, is
“Does Java have
pointers?” Some have claimed that pointers are hard and dangerous and
therefore bad, and since Java is all goodness and light and will lift your
earthly programming burdens, it cannot possibly contain such things. However,
it’s more accurate to say that Java has pointers; indeed, every object
identifier in Java (except for primitives) is one of these pointers, but their
use is restricted and guarded not only by the compiler but by the run-time
system. Or to put it another way, Java has pointers, but no pointer arithmetic.
These are what I’ve been calling “references,” and you can
think of them as “safety pointers,” not unlike the safety scissors
of elementary school—they aren’t sharp, so you cannot hurt yourself
without great effort, but they can sometimes be slow and
tedious.
|
 |
Ou si vous préférez, si vous provenez d'un langage de programmation qui en
disposait, cette annexe répond à la question « Est-ce que Java utilise des
pointeurs ? ». Nombreux sont ceux qui ont affirmé que les pointeurs sont difficiles à
manipuler et dangereux, donc à proscrire, et qu'en tant que langage propre et pur destiné à alléger
le fardeau quotidien de la programmation, Java ne pouvait décemment contenir de telles choses.
Cependant, il serait plus exact de dire que Java dispose de pointeurs ; en fait, chaque
identifiant d'objet en Java (les scalaires exceptés) est un pointeur, mais leur utilisation est
restreinte et surveillée non seulement par le compilateur mais aussi par le système d'exécution.
Autrement dit, Java utilise les pointeurs, mais pas les pointeurs arithmétiques. C'est ce que j'ai
appelé les « références » ; et vous pouvez y penser comme à des « pointeurs
sécurisés », un peu comme des ciseaux de cours élémentaire - ils ne sont pas pointus, on ne peut
donc se faire mal avec qu'en le cherchant bien, mais ils peuvent être lents et ennuyeux.
|
 |
 |
 |
Passing references around
|
 |
Passage de références
|
 |
 |
 |
When you pass a
reference
into a method, you’re still pointing to the same object. A simple
experiment demonstrates this:
|
 |
Quand on passe une référence à une méthode, on pointe toujours sur le
même objet. Un simple test le démontre :
|
 |
 |
 |
//: appendixa:PassReferences.java // Passing references around.
public class PassReferences { static void f(PassReferences h) { System.out.println("h inside f(): " + h); } public static void main(String[] args) { PassReferences p = new PassReferences(); System.out.println("p inside main(): " + p); f(p); } } ///:~
|
 |
//: appendixa:PassReferences.java // Le passage de références.
public class PassReferences { static void f(PassReferences h) { System.out.println("h inside f(): " + h); } public static void main(String[] args) { PassReferences p = new PassReferences(); System.out.println("p inside main(): " + p); f(p); } } ///:~
|
 |
 |
 |
The method toString( ) is
automatically invoked in the print statements, and PassReferences
inherits directly from Object with no redefinition of
toString( ). Thus, Object’s version of
toString( ) is used, which prints out the class of the object
followed by the address where that object is located (not the reference, but the
actual object storage). The output looks like this:
|
 |
La méthode toString() est automatiquement appelée dans
l'instruction print, dont PassReferences hérite directement de
Object comme la méthode toString() n'est pas redéfinie. La
version toString() de Object est donc utilisée, qui affiche la
classe de l'objet suivie de l'adresse mémoire où se trouve l'objet (non pas la référence, mais bien
là où est stocké l'objet). La sortie ressemble à ceci :
|
 |
 |
 |
p inside main(): PassReferences@1653748 h inside f(): PassReferences@1653748
|
 |
p inside main(): PassReferences@1653748 h inside f(): PassReferences@1653748
|
 |
 |
 |
You can see that both p and
h refer to the same object. This is far more efficient than duplicating a
new PassReferences object just so that you can send an argument to a
method. But it brings up an important
issue.
|
 |
On peut constater que p et h référencent
bien le même objet. Ceci est bien plus efficace que de créer un nouvel objet
PassReferences juste pour envoyer un argument à une méthode. Mais ceci amène une
importante question.
|
 |
 |
 |
Aliasing
|
 |
Aliasing
|
 |
 |
 |
Aliasing means that more than one
reference is tied to the same object, as in the above example. The problem with
aliasing occurs when someone writes to that object. If the owners of the
other references aren’t expecting that object to change, they’ll be
surprised. This can be demonstrated with a simple example:
|
 |
L'aliasing veut dire que plusieurs références peuvent être attachées au
même objet, comme dans l'exemple précédent. Le problème de l'aliasing survient quand quelqu'un
modifie cet objet. Si les propriétaires des autres références ne s'attendent pas à ce que
l'objet change, ils vont avoir des surprises. Ceci peut être mis en évidence avec un simple
exemple :
|
 |
 |
 |
//: appendixa:Alias1.java // Aliasing two references to one object.
public class Alias1 { int i; Alias1(int ii) { i = ii; } public static void main(String[] args) { Alias1 x = new Alias1(7); Alias1 y = x; // Assign the reference System.out.println("x: " + x.i); System.out.println("y: " + y.i); System.out.println("Incrementing x"); x.i++; System.out.println("x: " + x.i); System.out.println("y: " + y.i); } } ///:~
|
 |
//: appendixa:Alias1.java // Aliasing : deux références sur un même objet.
public class Alias1 { int i; Alias1(int ii) { i = ii; } public static void main(String[] args) { Alias1 x = new Alias1(7); Alias1 y = x; // Assigne la référence. System.out.println("x: " + x.i); System.out.println("y: " + y.i); System.out.println("Incrementing x"); x.i++; System.out.println("x: " + x.i); System.out.println("y: " + y.i); } } ///:~
|
 |
 |
 |
In the line:
|
 |
Dans la ligne :
|
 |
 |
 |
Alias1 y = x; // Assign the reference
|
 |
Alias1 y = x; // Assigne la référence.
|
 |
 |
 |
a new Alias1 reference is created, but instead of being assigned to a fresh object created with new, it’s assigned to an existing reference. So the contents of reference x, which is the address of the object x is pointing to, is assigned to y, and thus both x and y are attached to the same object. So when x’s i is incremented in the statement:
|
 |
une nouvelle référence Alias1 est créée, mais au lieu de
se voir assigner un nouvel objet créé avec new, elle reçoit une référence
existante. Le contenu de la référence x, qui est l'adresse de l'objet sur lequel
pointe x, est assigné à y ; et donc x et
y sont attachés au même objet. Donc quand on incrémente le i de
x dans l'instruction :
|
 |
 |
 |
x.i++;
|
 |
x.i++
|
 |
 |
 |
y’s i will be affected as well. This can be seen in the output:
|
 |
le i de y sera modifié lui aussi. On peut
le vérifier dans la sortie :
|
 |
 |
 |
x: 7 y: 7 Incrementing x x: 8 y: 8
|
 |
x: 7 y: 7 Incrementing x x: 8 y: 8
|
 |
 |
 |
One good solution in this case is to
simply not do it: don’t consciously alias more than one reference to an
object at the same scope. Your code will be much easier to understand and debug.
However, when you’re passing a reference in as an argument—which is
the way Java is supposed to work—you automatically alias because the local
reference that’s created can modify the “outside object” (the
object that was created outside the scope of the method). Here’s an
example:
|
 |
Une bonne solution dans ce cas est tout simplement de ne pas le
faire : ne pas aliaser plus d'une référence à un même objet dans la même portée. Le code en
sera d'ailleurs plus simple à comprendre et à débugguer. Cependant, quand on passe une référence en
argument - de la façon dont Java est supposé le faire - l'aliasing entre automatiquement en jeu, et
la référence locale créée peut modifier « l'objet extérieur » (l'objet qui a été créé en
dehors de la portée de la méthode). En voici un exemple :
|
 |
 |
 |
//: appendixa:Alias2.java // Method calls implicitly alias their // arguments.
public class Alias2 { int i; Alias2(int ii) { i = ii; } static void f(Alias2 reference) { reference.i++; } public static void main(String[] args) { Alias2 x = new Alias2(7); System.out.println("x: " + x.i); System.out.println("Calling f(x)"); f(x); System.out.println("x: " + x.i); } } ///:~
|
 |
//: appendixa:Alias2.java // Les appels de méthodes aliasent implicitement // leurs arguments.
public class Alias2 { int i; Alias2(int ii) { i = ii; } static void f(Alias2 reference) { reference.i++; } public static void main(String[] args) { Alias2 x = new Alias2(7); System.out.println("x: " + x.i); System.out.println("Calling f(x)"); f(x); System.out.println("x: " + x.i); } } ///:~
|
 |
 |
 |
The output is:
|
 |
Le résultat est :
|
 |
 |
 |
x: 7 Calling f(x) x: 8
|
 |
x: 7 Calling f(x) x: 8
|
 |
 |
 |
The method is changing its argument, the
outside object. When this kind of situation arises, you must decide whether it
makes sense, whether the user expects it, and whether it’s going to cause
problems.
|
 |
La méthode modifie son argument, l'objet extérieur. Dans ce genre de
situations, il faut décider si cela a un sens, si l'utilisateur s'y attend, et si cela peut causer
des problèmes.
|
 |
 |
 |
In general, you call a method in order to
produce a return value and/or a change of state in the object that the method
is called for. (A method is how you “send a message” to that
object.) It’s much less common to call a method in order to manipulate its
arguments; this is referred to as “calling a method for its
side effects.” Thus, when you create a
method that modifies its arguments the user must be clearly instructed and
warned about the use of that method and its potential surprises. Because of the
confusion and pitfalls, it’s much better to avoid changing the
argument.
|
 |
En général, on appelle une méthode afin de produire une valeur de retour et/ou une
modification de l'état de l'objet sur lequel est appelée la méthode (une méthode consiste
à « envoyer un message » à cet objet). Il est bien moins fréquent d'appeler une méthode
afin de modifier ses arguments ; on appelle cela « appeler une méthode pour
ses effets de bord ». Une telle méthode qui modifie ses arguments doit être
clairement documentée et prévenir à propos de ses surprises potentielles. A cause de la confusion
et des chausses-trappes engendrés, il vaut mieux s'abstenir de modifier les arguments.
|
 |
 |
 |
If you need to modify an argument during
a method call and you don’t intend to modify the outside argument, then
you should protect that argument by making a copy inside your method.
That’s the subject of much of this
appendix.
|
 |
S'il y a
besoin de modifier un argument durant un appel de méthode sans que cela ne se répercute sur l'objet
extérieur, alors il faut protéger cet argument en en créant une copie à l'intérieur de la méthode.
Cette annexe traite principalement de ce sujet.
|
 |
 |
 |
Making local copies
|
 |
Création de copies locales
|
 |
 |
 |
To review: All argument passing in Java
is performed by passing references. That is, when you pass “an
object,” you’re really passing only a reference to an object that
lives outside the method, so if you perform any modifications with that
reference, you modify the outside object. In addition:
- Aliasing happens
automatically during argument
passing.
- There are no
local objects, only local
references.
- References
have scopes, objects do
not.
- Object lifetime
is never an issue in
Java.
- There is no
language support (e.g., “const”) to prevent objects from being
modified (that is, to prevent the negative effects of
aliasing).
|
 |
En résumé : tous les passages d'arguments en Java se font par
référence. C'est à dire que quand on passe « un objet », on ne passe réellement qu'une
référence à un objet qui vit en dehors de la méthode ; et si des modifications sont faites sur
cette référence, on modifie l'objet extérieur. De plus :
- l'aliasing survient automatiquement durant le passage
d'arguments ;
- il n'y a pas d'objets locaux, que des références
locales ;
- les références ont une portée, les objets non ;
- la durée de vie d'un objet n'est jamais un problème en
Java ;
- le langage ne fournit pas d'aide (tel que « const ») pour éviter
qu'un objet ne soit modifié (c'est à dire pour se prémunir contre les effets négatifs de
l'aliasing).
|
 |
 |
 |
If you’re
only reading information from an object and not modifying it, passing a
reference is the most efficient form of argument passing. This is nice; the
default way of doing things is also the most efficient. However, sometimes
it’s necessary to be able to treat the object as if it were
“local” so that changes you make affect only a local copy and do not
modify the outside object. Many programming languages support the ability to
automatically make a local copy of the outside object, inside the
method[79]. Java
does not, but it allows you to produce this
effect.
|
 |
Si on ne fait que lire les informations d'un objet et qu'on ne le modifie
pas, la forme la plus efficace de passage d'arguments consiste à passer une référence. C'est bien,
car la manière de faire par défaut est aussi la plus efficace. Cependant, on peut avoir besoin de
traiter l'objet comme s'il était « local » afin que les modifications apportées n'affectent
qu'une copie locale et ne modifient pas l'objet extérieur. De nombreux langages proposent de créer
automatiquement une copie locale de l'objet extérieur, à l'intérieur de la méthode href="#fn79" name="fnB79">[79]. Java ne dispose pas de cette
fonctionnalité, mais il permet tout de même de mettre en oeuvre cet effet.
|
 |
 |
 |
Pass by value
|
 |
Passage par valeur
|
 |
 |
 |
This brings up the terminology issue,
which always seems good for an argument. The term is
“pass by value,” and the meaning depends on
how you perceive the operation of the program. The general meaning is that you
get a local copy of whatever you’re passing, but the real question is how
you think about what you’re passing. When it comes to the meaning of
“pass by value,” there are two fairly distinct
camps:
|
 |
Ceci nous amène à discuter terminologie, ce qui est toujours bon dans un
débat. Le sens de l'expression « passage par valeur » dépend de la perception qu'on a du
fonctionnement du programme. Le sens général est qu'on récupère une copie locale de ce qu'on passe,
mais cela est tempéré par notre façon de penser à propos de ce qu'on passe. Deux camps bien
distincts s'affrontent quant au sens de « passage par valeur » :
|
 |
 |
 |
- Java passes everything by
value. When you’re passing primitives into a method, you get a distinct
copy of the primitive. When you’re passing a reference into a method, you
get a copy of the reference. Ergo, everything is pass-by-value. Of course, the
assumption is that you’re always thinking (and caring) that references are
being passed, but it seems like the Java design has gone a long way toward
allowing you to ignore (most of the time) that you’re working with a
reference. That is, it seems to allow you to think of the reference as
“the object,” since it implicitly dereferences it whenever you make
a method call.
- Java
passes primitives by value (no argument there), but objects are passed by
reference. This is the world view that the reference is an alias for the object,
so you don’t think about passing references, but instead say
“I’m passing the object.” Since you don’t get a local
copy of the object when you pass it into a method, objects are clearly not
passed by value. There appears to be some support for this view within Sun,
since one of the “reserved but not implemented” keywords was
byvalue. (There’s no knowing, however, whether that keyword will
ever see the light of
day.)
|
 |
- Java passe tout par valeur. Quand on passe un scalaire à une méthode, on
obtient une copie distincte de ce scalaire. Quand on passe une référence à une méthode, on obtient
une copie de la référence. Ainsi, tous les passages d'arguments se font par valeur. Bien sûr, cela
suppose qu'on raisonne en terme de références, mais Java a justement été conçu afin de vous
permettre d'ignorer (la plupart du temps) que vous travaillez avec une référence. C'est à dire
qu'il permet d'assimiler la référence à « l'objet », car il la déréférence automatiquement
lorsqu'on fait un appel à une méthode.
- Java passe les scalaires par valeur (pas de contestations sur ce point),
mais les objets sont passés par référence. La référence est considérée comme un alias sur
l'objet ; on ne pense donc pas passer une référence, mais on se dit plutôt « je
passe l'objet ». Comme on n'obtient pas une copie locale de l'objet quand il est passé à une
méthode, il est clair que les objets ne sont pas passés par valeur. Sun semble plutôt soutenir ce
point de vue, puisque l'un des mot-clefs « réservés mais non implémentés » est
byvalue (bien que rien ne précise si ce mot-clef verra le jour).
|
 |
 |
 |
Having given both camps
a good airing, and after saying “It depends on how you think of a
reference,” I will attempt to sidestep the issue. In the end, it
isn’t that important—what is important is that you understand
that passing a reference allows the caller’s object to be changed
unexpectedly.
|
 |
Après avoir présenté les deux camps et précisé que « cela dépend de la
façon dont on considère une référence », je vais tenter de mettre le problème de côté. En fin
de compte, ce n'est pas si important que cela - ce qui est important, c'est de comprendre
que passer une référence permet de modifier l'objet passé en argument.
|
 |
 |
 |
Cloning objects
|
 |
Clonage d'objets
|
 |
 |
 |
The most likely reason for making a local
copy of an object is if you’re going to modify that object and you
don’t want to modify the caller’s object. If you decide that you
want to make a local copy, you simply use the clone( ) method to
perform the operation. This is a method that’s defined as protected
in the base class Object, and which you must override as public
in any derived classes that you want to clone. For example, the standard
library class ArrayList overrides clone( ), so we can call
clone( ) for ArrayList:
|
 |
La raison la plus courante de créer une copie locale d'un objet est qu'on
veut modifier cet objet sans impacter l'objet de l'appelant. Si on décide de créer une copie
locale, la méthode clone() permet de réaliser cette opération. C'est une méthode
définie comme protected dans la classe de base Object, et qu'il
faut redéfinir comme public dans les classes dérivées qu'on veut cloner. Par
exemple, la classe ArrayList de la bibliothèque standard redéfinit
clone(),on peut donc appeler clone() sur une
ArrayList :
|
 |
 |
 |
//: appendixa:Cloning.java // The clone() operation works for only a few // items in the standard Java library. import java.util.*;
class Int { private int i; public Int(int ii) { i = ii; } public void increment() { i++; } public String toString() { return Integer.toString(i); } }
public class Cloning { public static void main(String[] args) { ArrayList v = new ArrayList(); for(int i = 0; i < 10; i++ ) v.add(new Int(i)); System.out.println("v: " + v); ArrayList v2 = (ArrayList)v.clone(); // Increment all v2's elements: for(Iterator e = v2.iterator(); e.hasNext(); ) ((Int)e.next()).increment(); // See if it changed v's elements: System.out.println("v: " + v); } } ///:~
|
 |
//: appendixa:Cloning.java // L'opération clone() ne marche que pour quelques // composants de la bibliothèque Java standard. import java.util.*;
class Int { private int i; public Int(int ii) { i = ii; } public void increment() { i++; } public String toString() { return Integer.toString(i); } }
public class Cloning { public static void main(String[] args) { ArrayList v = new ArrayList(); for(int i = 0; i < 10; i++ ) v.add(new Int(i)); System.out.println("v: " + v); ArrayList v2 = (ArrayList)v.clone(); // Incrémente tous les éléments de v2 : for(Iterator e = v2.iterator(); e.hasNext(); ) ((Int)e.next()).increment(); // Vérifie si les éléments de v ont été modifiés : System.out.println("v: " + v); } } ///:~
|
 |
 |
 |
The clone( ) method produces
an Object, which must then be recast to the proper type. This example
shows how ArrayList’s clone( ) method does not
automatically try to clone each of the objects that the ArrayList
contains—the old ArrayList and the cloned ArrayList are
aliased to the same objects. This is often called a
shallow copy, since
it’s copying only the “surface” portion of an object. The
actual object consists of this “surface,” plus all the objects that
the references are pointing to, plus all the objects those objects are
pointing to, etc. This is often referred to as the
“web of objects.”
Copying the entire mess is called a
deep
copy.
|
 |
La méthode clone() produit un Object,
qu'il faut alors retranstyper dans le bon type. Cet exemple montre que la méthode
clone() de ArrayList n'essaie pas de cloner chacun des
objets que l'ArrayList contient - l'ancienne ArrayList et
l'ArrayList clonée référencent les mêmes objets. On appelle souvent cela
une copie superficielle, puisque seule est copiée la « surface » d'un
objet. L'objet réel est en réalité constitué de cette « surface », plus les objets sur
lesquels les références pointent, plus tous les objets sur lesquels ces objets pointent,
etc... On s'y réfère souvent en parlant de « réseau d'objets ». On appelle copie
profonde le fait de copier la totalité de ce fouillis.
|
 |
 |
 |
You can see the effect of the shallow
copy in the output, where the actions performed on v2 affect
v:
|
 |
On peut voir les effets de la copie
superficielle dans la sortie, où les actions réalisées sur v2 affectent
v :
|
 |
 |
 |
v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
 |
v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
 |
 |
 |
Not trying to clone( ) the
objects contained in the ArrayList is probably a fair assumption because
there’s no guarantee that those objects are
cloneable[80].
|
 |
Ne pas essayer d'appeler clone() sur les objets contenus
dans l'ArrayList est vraisemblablement une hypothèse raisonnable, car rien ne
garantit que ces objets sont cloneables [80].
|
 |
 |
 |
Adding cloneability to a class
|
 |
Rendre une classe cloneable
|
 |
 |
 |
Even though the clone method is defined
in the base-of-all-classes Object, cloning is not automatically
available in every
class[81]. This
would seem to be counterintuitive to the idea that base-class methods are always
available in derived classes. Cloning in Java goes against this idea; if you
want it to exist for a class, you must specifically add code to make cloning
work.
|
 |
Bien que le méthode clone soit définie dans la classe
Object, base de toutes les classes, le clonage n'est pas disponible dans toutes
les classes [81]. Cela semble
contraire à l'idée que les méthodes de la classe de base sont toujours disponibles dans les classes
dérivées. Le clonage dans Java va contre cette idée ; si on veut le rendre disponible dans une
classe, il faut explicitement ajouter du code pour que le clonage fonctionne.
|
 |
 |
 |
Using a trick with protected
|
 |
Utilisation d'une astuce avec protected
|
 |
 |
 |
To prevent default cloneability in every
class you create, the
clone( ) method is
protected in the base class Object. Not only does this mean that
it’s not available by default to the client programmer who is simply using
the class (not subclassing it), but it also means that you cannot call
clone( ) via a reference to the base class. (Although that might
seem to be useful in some situations, such as to polymorphically clone a bunch
of Objects.) It is in effect a way to give you, at compile-time, the
information that your object is not cloneable—and oddly enough most
classes in the standard Java library are not cloneable. Thus, if you
say:
|
 |
Afin d'éviter de rendre chaque classe qu'on crée cloneable par défaut, la
méthode clone() est protected dans la classe de base
Object. Cela signifie non seulement qu'elle n'est pas disponible par défaut pour
le programmeur client qui ne fait qu'utiliser la classe (sans en hériter), mais cela veut aussi
dire qu'on ne peut pas appeler clone() via une référence à la classe de base (bien
que cela puisse être utile dans certaines situations, comme le clonage polymorphique d'un ensemble
d'Objects). C'est donc une manière de signaler, lors de la compilation, que
l'objet n'est pas cloneable - et bizarrement, la plupart des classes de la bibliothèque standard
Java ne le sont pas. Donc, si on écrit :
|
 |
 |
 |
Integer x = new Integer(1); x = x.clone();
|
 |
Integer x = new Integer(1); x = x.clone();
|
 |
 |
 |
You will get, at compile-time, an error
message that says clone( ) is not accessible (since Integer
doesn’t override it and it defaults to the protected version).
|
 |
On aura un message d'erreur lors de la compilation disant que
clone() n'est pas accessible (puisque Integer ne la redéfinit pas
et qu'elle se réfère donc à la version protected).
|
 |
 |
 |
If, however, you’re in a class
derived from Object (as all classes are), then you have permission to
call Object.clone( ) because it’s
protected and you’re an inheritor. The base
class clone( ) has useful functionality—it performs the actual
bitwise duplication of the derived-class object, thus acting as the
common cloning operation. However, you then need to make your clone
operation public for it to be accessible. So, two
key issues when you clone are:
- Virtually always call
super.clone( )
- Make your clone
public
|
 |
Si, par contre, on se trouve
dans une classe dérivée d'Object (comme le sont toutes les classes), alors on a la
permission d'appeler Object.clone() car elle est protected
et qu'on est un héritier. La méthode clone() de la classe de base fonctionne -
elle duplique effectivement bit à bit l'objet de la classe dérivée, réalisant une
opération de clonage classique. Cependant, il faut tout de même rendre sa propre méthode
de clonage public pour la rendre accessible. Donc, les deux points capitaux
quand on clone sont :
- Toujours appeler super.clone()
- Rendre sa méthode clone public
|
 |
 |
 |
You’ll
probably want to override clone( ) in any further derived classes,
otherwise your (now public) clone( ) will be used, and that
might not do the right thing (although, since Object.clone( ) makes
a copy of the actual object, it might). The protected trick works only
once—the first time you inherit from a class that has no cloneability and
you want to make a class that’s cloneable. In any classes inherited from
your class the clone( ) method is available since it’s not
possible in Java to reduce the access of a method during derivation. That is,
once a class is cloneable, everything derived from it is cloneable unless you
use provided mechanisms (described later) to “turn off”
cloning.
|
 |
On voudra probablement redéfinir clone() dans de futures
classes dérivées, sans quoi le clone() (maintenant public) de la
classe actuelle sera utilisé, et pourrait ne pas marcher (cependant, puisque
Object.clone() crée une copie de l'objet, ça pourrait marcher). L'astuce
protected ne marche qu'une fois - la première fois qu'on crée une classe dont on
veut qu'elle soit cloneable héritant d'une classe qui ne l'est pas. Dans chaque classe dérivée de
cette classe la méthode clone() sera accessible puisqu'il n'est pas possible en
Java de réduire l'accès à une méthode durant la dérivation. C'est à dire qu'une fois qu'une classe
est cloneable, tout ce qui en est dérivé est cloneable à moins d'utiliser les mécanismes (décrits
ci-après) pour « empêcher » le clonage.
|
 |
 |
 |
Implementing the Cloneable interface
|
 |
Implémenter l'interface Cloneable
|
 |
 |
 |
There’s one more thing you need to
do to complete the cloneability of an object: implement the
Cloneable interface. This
interface is a bit strange, because it’s
empty!
|
 |
Il y a une dernière chose à faire pour rendre un objet cloneable :
implémenter l'interface Clonable. Cette interface est un peu
spéciale, car elle est vide !
|
 |
 |
 |
interface Cloneable {}
|
 |
interface Cloneable {}
|
 |
 |
 |
The reason for implementing this empty interface is obviously not because you are going to upcast to Cloneable and call one of its methods. The use of interface here is considered by some to be a “hack” because it’s using a feature for something other than its original intent. Implementing the Cloneable interface acts as a kind of a flag, wired into the type of the class.
|
 |
La raison d'implémenter cette interface vide n'est
évidemment pas parce qu'on va surtyper jusqu'à Cloneable et appeler une de ses
méthodes. L'utilisation d'interface dans ce contexte est considérée par certains
comme une « astuce » car on utilise une de ses fonctionnalités dans un but autre que celui
auquel on pensait originellement. Implémenter l'interface Cloneable agit comme une
sorte de flag, codé en dur dans le type de la classe.
|
 |
 |
 |
There are two reasons for the existence of the Cloneable interface. First, you might have an upcast reference to a base type and not know whether it’s possible to clone that object. In this case, you can use the instanceof keyword (described in Chapter 12) to find out whether the reference is connected to an object that can be cloned:
|
 |
L'interface Cloneable existe
pour deux raisons. Premièrement, on peut avoir une référence transtypée à un type de base et ne pas
savoir s'il est possible de cloner cet objet. Dans ce cas, on peut utiliser le mot-clef
instanceof (décrit au chapitre 12) pour savoir si la référence est connectée à un
objet qui peut être cloné :
|
 |
 |
 |
if(myReference instanceof Cloneable) // ...
|
 |
if(myReference instanceof Cloneable) // ...
|
 |
 |
 |
The second reason is that mixed into this design for cloneability was the thought that maybe you didn’t want all types of objects to be cloneable. So Object.clone( ) verifies that a class implements the Cloneable interface. If not, it throws a CloneNotSupportedException exception. So in general, you’re forced to implement Cloneable as part of support for cloning.
|
 |
La deuxième raison en est qu'on ne veut pas forcément que tous les types
d'objets soient cloneables. Donc Object.clone() vérifie qu'une classe implémente
l'interface Cloneable, et si ce n'est pas le cas, elle génère une exception
CloneNotSupportedException. Donc en général, on est forcé d'implémenter
Cloneable comme partie du mécanisme de clonage.
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |