 |
 |
A) Passage et Retour d'Objets |
|
 |
|
Texte original |
 |
Traducteur : Jérome QUELIN |
|
 |
///
|
Ce chapitre contient 6 pages
1
2
3
4
5
6
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
Deep copy via serialization
|
 |
Copie profonde via la sérialisation
|
 |
 |
 |
When you consider Java’s object
serialization (introduced in Chapter 11), you might observe that an object
that’s serialized and then deserialized is, in effect,
cloned.
|
 |
Quand on examine la sérialisation d'objets dans Java (présentée au Chapitre
11), on se rend compte qu'un objet sérialisé puis désérialisé est, en fait, cloné.
|
 |
 |
 |
So why not use
serialization to perform deep
copying? Here’s an example that compares the two approaches by timing
them:
|
 |
Pourquoi alors
ne pas utiliser la sérialisation pour réaliser une copie profonde ? Voici un exemple qui
compare les deux approches en les chronométrant :
|
 |
 |
 |
//: appendixa:Compete.java import java.io.*;
class Thing1 implements Serializable {} class Thing2 implements Serializable { Thing1 o1 = new Thing1(); }
class Thing3 implements Cloneable { public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.err.println("Thing3 can't clone"); } return o; } }
class Thing4 implements Cloneable { Thing3 o3 = new Thing3(); public Object clone() { Thing4 o = null; try { o = (Thing4)super.clone(); } catch(CloneNotSupportedException e) { System.err.println("Thing4 can't clone"); } // Clone the field, too: o.o3 = (Thing3)o3.clone(); return o; } }
public class Compete { static final int SIZE = 5000; public static void main(String[] args) throws Exception { Thing2[] a = new Thing2[SIZE]; for(int i = 0; i < a.length; i++) a[i] = new Thing2(); Thing4[] b = new Thing4[SIZE]; for(int i = 0; i < b.length; i++) b[i] = new Thing4(); long t1 = System.currentTimeMillis(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); for(int i = 0; i < a.length; i++) o.writeObject(a[i]); // Now get copies: ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buf.toByteArray())); Thing2[] c = new Thing2[SIZE]; for(int i = 0; i < c.length; i++) c[i] = (Thing2)in.readObject(); long t2 = System.currentTimeMillis(); System.out.println( "Duplication via serialization: " + (t2 - t1) + " Milliseconds"); // Now try cloning: t1 = System.currentTimeMillis(); Thing4[] d = new Thing4[SIZE]; for(int i = 0; i < d.length; i++) d[i] = (Thing4)b[i].clone(); t2 = System.currentTimeMillis(); System.out.println( "Duplication via cloning: " + (t2 - t1) + " Milliseconds"); } } ///:~
|
 |
//: appendixa:Compete.java import java.io.*;
class Thing1 implements Serializable {} class Thing2 implements Serializable { Thing1 o1 = new Thing1(); }
class Thing3 implements Cloneable { public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.err.println("Thing3 can't clone"); } return o; } }
class Thing4 implements Cloneable { Thing3 o3 = new Thing3(); public Object clone() { Thing4 o = null; try { o = (Thing4)super.clone(); } catch(CloneNotSupportedException e) { System.err.println("Thing4 can't clone"); } // Clone aussi la donnée membre : o.o3 = (Thing3)o3.clone(); return o; } }
public class Compete { static final int SIZE = 5000; public static void main(String[] args) throws Exception { Thing2[] a = new Thing2[SIZE]; for(int i = 0; i < a.length; i++) a[i] = new Thing2(); Thing4[] b = new Thing4[SIZE]; for(int i = 0; i < b.length; i++) b[i] = new Thing4(); long t1 = System.currentTimeMillis(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); for(int i = 0; i < a.length; i++) o.writeObject(a[i]); // Récupère les copies: ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buf.toByteArray())); Thing2[] c = new Thing2[SIZE]; for(int i = 0; i < c.length; i++) c[i] = (Thing2)in.readObject(); long t2 = System.currentTimeMillis(); System.out.println( "Duplication via serialization: " + (t2 - t1) + " Milliseconds"); // Maintenant on tente le clonage : t1 = System.currentTimeMillis(); Thing4[] d = new Thing4[SIZE]; for(int i = 0; i < d.length; i++) d[i] = (Thing4)b[i].clone(); t2 = System.currentTimeMillis(); System.out.println( "Duplication via cloning: " + (t2 - t1) + " Milliseconds"); } } ///:~
|
 |
 |
 |
Thing2 and Thing4 contain
member objects so that there’s some deep copying going on. It’s
interesting to notice that while Serializable classes are easy to set up,
there’s much more work going on to duplicate them. Cloning involves a lot
of work to set up the class, but the actual duplication of objects is relatively
simple. The results really tell the tale. Here is the output from three
different runs:
|
 |
Thing2 et Thing4 contiennent des objets
membres afin qu'une copie profonde soit nécessaire. Il est intéressant de noter que bien que les
classes Serializable soient plus faciles à implémenter, elles nécessitent plus de
travail pour les copier. Le support du clonage demande plus de travail pour créer la classe, mais
la duplication des objets est relativement simple. Les résultats sont édifiants. Voici la sortie
obtenue pour trois exécutions :
|
 |
 |
 |
Duplication via serialization: 940 Milliseconds Duplication via cloning: 50 Milliseconds Duplication via serialization: 710 Milliseconds Duplication via cloning: 60 Milliseconds Duplication via serialization: 770 Milliseconds Duplication via cloning: 50 Milliseconds
|
 |
Duplication via serialization: 940 Milliseconds Duplication via cloning: 50 Milliseconds
Duplication via serialization: 710 Milliseconds Duplication via cloning: 60 Milliseconds
Duplication via serialization: 770 Milliseconds Duplication via cloning: 50 Milliseconds
|
 |
 |
 |
Despite the significant time difference
between serialization and cloning, you’ll also notice that the
serialization technique seems to vary more in its duration, while cloning tends
to be more stable.
|
 |
Outre la différence significative de temps entre la sérialisation et le
clonage, vous noterez aussi que la sérialisation semble beaucoup plus sujette aux variations,
tandis que le clonage a tendance à être plus stable.
|
 |
 |
 |
Adding cloneability further down a hierarchy
|
 |
Supporter le clonage plus bas dans la hiérarchie
|
 |
 |
 |
If you create a new class, its base class
defaults to Object, which defaults to noncloneability (as you’ll
see in the next section). As long as you don’t explicitly add
cloneability, you won’t get it.
But you can add it in at any
layer and it will then be cloneable from that layer downward, like
this:
|
 |
Si une nouvelle classe est créée, sa classe de base par défaut est
Object, qui par défaut n'est pas cloneable (comme vous le verrez dans la section
suivante). Tant qu'on n'implémente pas explicitement le clonage, celui-ci ne sera pas
disponible. Mais on peut le rajouter à n'importe quel niveau et la classe sera cloneable à
partir de ce niveau dans la hiérarchie, comme ceci :
|
 |
 |
 |
//: appendixa:HorrorFlick.java // You can insert Cloneability // at any level of inheritance. import java.util.*;
class Person {} class Hero extends Person {} class Scientist extends Person implements Cloneable { public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { // this should never happen: // It's Cloneable already! throw new InternalError(); } } } class MadScientist extends Scientist {}
public class HorrorFlick { public static void main(String[] args) { Person p = new Person(); Hero h = new Hero(); Scientist s = new Scientist(); MadScientist m = new MadScientist();
// p = (Person)p.clone(); // Compile error // h = (Hero)h.clone(); // Compile error s = (Scientist)s.clone(); m = (MadScientist)m.clone(); } } ///:~
|
 |
//: appendixa:HorrorFlick.java // On peut implémenter le Clonage // à n'importe quel niveau de la hiérarchie. import java.util.*;
class Person {} class Hero extends Person {} class Scientist extends Person implements Cloneable { public Object clone() { try { return super.clone(); } catch(CloneNotSupportedException e) { // Ceci ne devrait jamais arriver : // la classe est Cloneable ! throw new InternalError(); } } } class MadScientist extends Scientist {}
public class HorrorFlick { public static void main(String[] args) { Person p = new Person(); Hero h = new Hero(); Scientist s = new Scientist(); MadScientist m = new MadScientist();
// p = (Person)p.clone(); // Erreur lors de la compilation // h = (Hero)h.clone(); // Erreur lors de la compilation s = (Scientist)s.clone(); m = (MadScientist)m.clone(); } } ///:~
|
 |
 |
 |
Before cloneability was added, the
compiler stopped you from trying to clone things. When cloneability is added in
Scientist, then Scientist and all its descendants are
cloneable.
|
 |
Tant que le clonage n'est pas supporté, le compilateur bloque toute
tentative de clonage. Si le clonage est ajouté dans la classe Scientist, alors
Scientist et tous ses descendants sont cloneables.
|
 |
 |
 |
Why this strange design?
|
 |
Pourquoi cet étrange design ?
|
 |
 |
 |
If all this seems to be a strange scheme,
that’s because it is. You might wonder why it worked out this way. What is
the meaning behind this design?
|
 |
Si tout ceci vous semble étrange, c'est parce que ça l'est réellement. On
peut se demander comment on en est arrivé là. Que se cache-t-il derrière cette conception
?
|
 |
 |
 |
Originally, Java was designed as a
language to control hardware boxes, and definitely not with the Internet in
mind. In a general-purpose language like this, it makes sense that the
programmer be able to clone any object. Thus, clone( ) was placed in
the root class Object, but it was a public method so you
could always clone any object. This seemed to be the most flexible approach, and
after all, what could it hurt?
|
 |
Originellement, Java a été conçu pour piloter des boîtiers, sans aucune
pensée pour l'Internet. Dans un langage générique tel que celui-ci, il semblait sensé que le
programmeur soit capable de cloner n'importe quel objet. C'est ainsi que clone() a
été placée dans la classe de base Object, mais c'était une méthode
public afin qu'un objet puisse toujours être cloné. Cela semblait l'approche la
plus flexible, et après tout, quel mal y avait-il à cela ?
|
 |
 |
 |
Well, when Java was seen as the ultimate
Internet programming language, things changed. Suddenly, there are security
issues, and of course, these issues are dealt with using objects, and you
don’t necessarily want anyone to be able to clone your security objects.
So what you’re seeing is a lot of patches applied on the original simple
and straightforward scheme: clone( ) is now protected in
Object. You must override it and implement Cloneable
and deal with the exceptions.
|
 |
Puis, quand Java s'est révélé comme le langage de programmation idéal pour
Internet, les choses ont changé. Subitement, des problèmes de sécurité sont apparus, et bien sûr,
ces problèmes ont été réglés en utilisant des objets, et on ne voulait pas que n'importe qui soit
capable de cloner ces objets de sécurité. Ce qu'on voit donc est une suite de patchs appliqués sur
l'arrangement initialement simple : clone() est maintenant
protected dans Object. Il faut la redéfinir et
implementer Cloneableet traiter les
exceptions.
|
 |
 |
 |
It’s worth noting that you must use
the Cloneable interface only if you’re going to call
Object’s clone( ), method, since that method checks at
run-time to make sure that your class implements Cloneable. But for
consistency (and since Cloneable is empty anyway) you should implement
it.
|
 |
Il est bon de noter qu'on n'est obligé d'utiliser l'interface
Cloneable que si on fait un appel à la méthode clone()
de Object, puisque cette méthode vérifie lors de l'exécution que la classe
implémente Cloneable. Mais dans un souci de cohérence (et puisque de toute façon
Cloneable est vide), il vaut mieux l'implémenter.
|
 |
 |
 |
Controlling cloneability
|
 |
Contrôler la clonabilité
|
 |
 |
 |
You might suggest that, to remove
cloneability, the
clone( ) method simply be made private, but this won’t
work since you cannot take a base-class method and make it less accessible in a
derived class. So it’s not that simple. And yet, it’s necessary to
be able to control whether an object can be cloned. There are actually a number
of attitudes you can take to this in a class that you design:
|
 |
On pourrait penser que, pour supprimer le support du clonage, il
suffit de rendre private la méthode clone(), mais ceci ne
marchera pas car on ne peut prendre une méthode de la classe de base et la rendre moins accessible
dans une classe dérivée. Ce n'est donc pas si simple. Et pourtant, il est essentiel d'être capable
de contrôler si un objet peut être cloné ou non. Une classe peut adopter plusieurs attitudes à ce
propos :
|
 |
 |
 |
- Indifference. You
don’t do anything about cloning, which means that your class can’t
be cloned but a class that inherits from you can add cloning if it wants. This
works only if the default Object.clone( ) will do something
reasonable with all the fields in your
class.
- Support
clone( ). Follow the standard practice of implementing
Cloneable and overriding clone( ). In the overridden
clone( ), you call super.clone( ) and catch all
exceptions (so your overridden clone( ) doesn’t throw any
exceptions).
- Support
cloning conditionally. If your class holds references to other objects that
might or might not be cloneable (a container class, for example), your
clone( ) can try to clone all of the objects for which you have
references, and if they throw exceptions just pass those exceptions out to the
programmer. For example, consider a special sort of ArrayList that tries
to clone all the objects it holds. When you write such an ArrayList, you
don’t know what sort of objects the client programmer might put into your
ArrayList, so you don’t know whether they can be
cloned.
- Don’t
implement Cloneable but override clone( ) as
protected, producing the correct copying behavior for any fields. This
way, anyone inheriting from this class can override clone( ) and
call super.clone( ) to produce the correct copying behavior. Note
that your implementation can and should invoke super.clone( ) even
though that method expects a Cloneable object (it will throw an exception
otherwise), because no one will directly invoke it on an object of your type. It
will get invoked only through a derived class, which, if it is to work
successfully, implements
Cloneable.
- Try
to prevent cloning by not implementing Cloneable and overriding
clone( ) to throw an exception. This is successful only if any class
derived from this calls super.clone( ) in its redefinition of
clone( ). Otherwise, a programmer may be able to get around
it.
- Prevent cloning
by making your class final. If clone( ) has not been
overridden by any of your ancestor classes, then it can’t be. If it has,
then override it again and throw CloneNotSupportedException. Making the
class final is the only way to guarantee that cloning is prevented. In
addition, when dealing with security objects or other situations in which you
want to control the number of objects created you should make all constructors
private and provide one or more special methods for creating objects.
That way, these methods can restrict the number of objects created and the
conditions in which they’re created. (A particular case of this is the
singleton pattern shown in Thinking in Patterns with Java,
downloadable at
www.BruceEckel.com.)
|
 |
- L'indifférence. Rien n'est fait pour supporter le clonage, ce qui signifie
que la classe ne peut être clonée, mais qu'une classe dérivée peut implémenter le clonage si elle
veut. Ceci ne fonctionne que si Object.clone() traite comme il faut tous les
champs de la classe.
- Implémenter clone(). Respecter la marche à suivre pour
l'implémentation de Cloneable et redéfinir clone(). Dans la
méthode clone() redéfinie, appeler super.clone() et intercepter
toutes les exceptions (afin que la méthode clone() redéfinie ne génère pas
d'exceptions).
- Supporter le clonage conditionnellement. Si la classe contient des
références sur d'autres objets qui peuvent ou non être cloneables (une classe conteneur, par
exemple), la méthode clone() peut essayer de cloner tous les objets référencés, et
s'ils génèrent des exceptions, relayer ces exceptions au programmeur. Par exemple, prenons le cas
d'une sorte d'ArrayList qui essayerait de cloner tous les objets qu'elle contient.
Quand on écrit une telle ArrayList, on ne peut savoir quelle sorte d'objets le
programmeur client va pouvoir stocker dans l'ArrayList, on ne sait donc pas s'ils
peuvent être clonés.
- Ne pas implémenter Cloneable mais redéfinir
clone() en protected, en s'assurant du fonctionnement correct du
clonage pour chacun des champs. De cette manière, toute classe dérivée peut redéfinir
clone() et appeler super.clone() pour obtenir le comportement
attendu lors du clonage. Cette implémentation peut et doit invoquer super.clone()
même si cette méthode attend un objet Cloneable (elle génère une exception sinon),
car personne ne l'invoquera directement sur un objet de la classe. Elle ne sera invoquée qu'à
travers une classe dérivée, qui, elle, implémente Cloneable si elle veut obtenir
le fonctionnement désiré.
- Tenter de bloquer le clonage en n'implémentant pas
Cloneable et en redéfinissant clone() afin de générer une
exception. Ceci ne fonctionne que si toutes les classes dérivées appellent
super.clone() dans leur redéfinition de clone(). Autrement, un
programmeur est capable de contourner ce mécanisme.
- Empêcher le clonage en rendant la classe final. Si
clone() n'a pas été redéfinie par l'une des classes parentes, alors elle ne peut
plus l'être. Si elle a déjà été redéfinie, la redéfinir à nouveau et générer une exception
CloneNotSupportedException. Rendre la classe final est la seule
façon d'interdire catégoriquement le clonage. De plus, si on manipule des objets de sécurité ou
dans d'autres situations dans lesquelles on veut contrôler le nombre d'objets créés, il faut rendre
tous les constructeurs private et fournir une ou plusieurs méthodes spéciales pour
créer les objets. De cette manière, les méthodes peuvent restreindre le nombre d'objets créés et
les conditions dans lesquelles ils sont créés (un cas particulier en est le patron
singleton présenté dans Thinking in Patterns with Java, téléchargeable à
www.BruceEckel.com).
|
 |
 |
 |
Here’s
an example that shows the various ways cloning can be implemented and then,
later in the hierarchy, “turned off”:
|
 |
Voici un exemple qui montre les différentes façons dont le clonage peut
être implémenté et interdit plus bas dans la hiérarchie :
|
 |
 |
 |
//: appendixa:CheckCloneable.java // Checking to see if a reference can be cloned.
// Can't clone this because it doesn't // override clone(): class Ordinary {}
// Overrides clone, but doesn't implement // Cloneable: class WrongClone extends Ordinary { public Object clone() throws CloneNotSupportedException { return super.clone(); // Throws exception } }
// Does all the right things for cloning: class IsCloneable extends Ordinary implements Cloneable { public Object clone() throws CloneNotSupportedException { return super.clone(); } }
// Turn off cloning by throwing the exception: class NoMore extends IsCloneable { public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } }
class TryMore extends NoMore { public Object clone() throws CloneNotSupportedException { // Calls NoMore.clone(), throws exception: return super.clone(); } }
class BackOn extends NoMore { private BackOn duplicate(BackOn b) { // Somehow make a copy of b // and return that copy. This is a dummy // copy, just to make the point: return new BackOn(); } public Object clone() { // Doesn't call NoMore.clone(): return duplicate(this); } }
// Can't inherit from this, so can't override // the clone method like in BackOn: final class ReallyNoMore extends NoMore {}
public class CheckCloneable { static Ordinary tryToClone(Ordinary ord) { String id = ord.getClass().getName(); Ordinary x = null; if(ord instanceof Cloneable) { try { System.out.println("Attempting " + id); x = (Ordinary)((IsCloneable)ord).clone(); System.out.println("Cloned " + id); } catch(CloneNotSupportedException e) { System.err.println("Could not clone "+id); } } return x; } public static void main(String[] args) { // Upcasting: Ordinary[] ord = { new IsCloneable(), new WrongClone(), new NoMore(), new TryMore(), new BackOn(), new ReallyNoMore(), }; Ordinary x = new Ordinary(); // This won't compile, since clone() is // protected in Object: //! x = (Ordinary)x.clone(); // tryToClone() checks first to see if // a class implements Cloneable: for(int i = 0; i < ord.length; i++) tryToClone(ord[i]); } } ///:~
|
 |
//: appendixa:CheckCloneable.java // Vérifie si une référence peut être clonée.
// Ne peut être clonée car ne redéfinit pas clone() : class Ordinary {}
// Redéfinit clone, mais n'implémente pas // Cloneable : class WrongClone extends Ordinary { public Object clone() throws CloneNotSupportedException { return super.clone(); // Génère une exception } }
// Fait le nécessaire pour le clonage : class IsCloneable extends Ordinary implements Cloneable { public Object clone() throws CloneNotSupportedException { return super.clone(); } }
// Interdit le clonage en générant une exception : class NoMore extends IsCloneable { public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } }
class TryMore extends NoMore { public Object clone() throws CloneNotSupportedException { // Appelle NoMore.clone(), génère une excpetion : return super.clone(); } }
class BackOn extends NoMore { private BackOn duplicate(BackOn b) { // Crée une copie de b d'une façon ou d'une autre // et renvoie cette copie. C'est une copie sans // intérêt, juste pour l'exemple : return new BackOn(); } public Object clone() { // N'appelle pas NoMore.clone() : return duplicate(this); } }
// On ne peut dériver cette classe, donc on ne peut // redéfinir la méthode clone comme dans BackOn: final class ReallyNoMore extends NoMore {}
public class CheckCloneable { static Ordinary tryToClone(Ordinary ord) { String id = ord.getClass().getName(); Ordinary x = null; if(ord instanceof Cloneable) { try { System.out.println("Attempting " + id); x = (Ordinary)((IsCloneable)ord).clone(); System.out.println("Cloned " + id); } catch(CloneNotSupportedException e) { System.err.println("Could not clone "+id); } } return x; } public static void main(String[] args) { // Transtypage ascendant : Ordinary[] ord = { new IsCloneable(), new WrongClone(), new NoMore(), new TryMore(), new BackOn(), new ReallyNoMore(), }; Ordinary x = new Ordinary(); // Ceci ne compilera pas, puisque clone() // est protected dans Object: //! x = (Ordinary)x.clone(); // tryToClone() vérifie d'abord si // une classe implémente Cloneable : for(int i = 0; i < ord.length; i++) tryToClone(ord[i]); } } ///:~
|
 |
 |
 |
The first class, Ordinary,
represents the kinds of classes we’ve seen throughout this book: no
support for cloning, but as it turns out, no prevention of cloning either. But
if you have a reference to an Ordinary object that might have been upcast
from a more derived class, you can’t tell if it can be cloned or
not.
|
 |
La première classe, Ordinary, représente le genre de
classes que nous avons rencontré tout au long de ce livre : pas de support du clonage, mais
pas de contrôle sur la clonabilité non plus. Mais si on dispose d'une référence sur un objet
Ordinary qui peut avoir été transtypé à partir d'une classe dérivée, on ne peut
savoir s'il est peut être cloné ou non.
|
 |
 |
 |
The class WrongClone shows an
incorrect way to implement cloning. It does override Object.clone( )
and makes that method public, but it doesn’t implement
Cloneable, so when super.clone( ) is called (which results in
a call to Object.clone( )), CloneNotSupportedException is
thrown so the cloning doesn’t work.
|
 |
La classe WrongClone montre une implémentation incorrecte
du clonage. Elle redéfinit bien Object.clone() et rend la méthode
public, mais elle n'implémente pas Cloneable, donc quand
super.clone() est appelée (ce qui revient à un appel à
Object.clone()), une exception CloneNotSupportedException est
générée et le clonage échoue.
|
 |
 |
 |
In IsCloneable you can see all the
right actions performed for cloning: clone( ) is overridden and
Cloneable is implemented. However, this clone( ) method and
several others that follow in this example do not catch
CloneNotSupportedException, but instead pass it through to the caller,
who must then put a try-catch block around it. In your own clone( )
methods you will typically catch CloneNotSupportedException inside
clone( ) rather than passing it through. As you’ll see, in
this example it’s more informative to pass the exceptions
through.
|
 |
La classe IsCloneable effectue toutes les actions
nécessaires au clonage : clone() est redéfinie et Cloneable
implémentée. Cependant, cette méthode clone() et plusieurs autres qui suivent dans
cet exemple n'interceptent pasCloneNotSupportedException, mais la font
suivre à l'appelant, qui doit alors l'envelopper dans un bloc try-catch. Dans les méthodes
clone() typiques il faut intercepter CloneNotSupportedException
à l'intérieur de clone() plutôt que de la propager. Cependant dans cet
exemple, il est plus intéressant de propager les exceptions.
|
 |
 |
 |
Class NoMore attempts to
“turn off” cloning in the way that the Java designers intended: in
the derived class clone( ), you throw
CloneNotSupportedException. The clone( ) method in class
TryMore properly calls super.clone( ), and this resolves to
NoMore.clone( ), which throws an exception and prevents
cloning.
|
 |
La classe NoMore tente d'interdire le clonage comme les
concepteurs de Java pensaient le faire : en générant une exception
CloneNotSupportedException dans la méthode clone() de la classe
dérivée. La méthode clone() de la classe TryMore appelle
super.clone(), ce qui revient à appeler NoMore.clone(), qui
génère une exception et empêche donc le clonage.
|
 |
 |
 |
But what if the programmer doesn’t
follow the “proper” path of calling
super.clone( )
inside the overridden clone( ) method? In BackOn, you can see
how this can happen. This class uses a separate method duplicate( )
to make a copy of the current object and calls this method inside
clone( ) instead of calling super.clone( ). The
exception is never thrown and the new class is cloneable. You can’t rely
on throwing an exception to prevent making a cloneable class. The only sure-fire
solution is shown in ReallyNoMore, which is final and thus cannot
be inherited. That means if clone( ) throws an exception in the
final class, it cannot be modified with inheritance and the prevention of
cloning is assured. (You cannot explicitly call Object.clone( ) from
a class that has an arbitrary level of inheritance; you are limited to calling
super.clone( ), which has access to only the direct base class.)
Thus, if you make any objects that involve security issues, you’ll want to
make those classes final.
|
 |
Mais que se passe-t-il si le programmeur ne respecte pas la chaîne d'appel
« recommandée » et n'appelle pas super.clone() à l'intérieur de la
méthode clone() redéfinie ? C'est ce qui se passe dans la classe
BackOn. Cette classe utilise une méthode séparée duplicate() pour
créer une copie de l'objet courant et appelle cette méthode dans clone()au
lieu d'appeler super.clone(). L'exception n'est donc jamais générée et la
nouvelle classe est cloneable. La seule solution vraiment sûre est montrée dans
ReallyNoMore, qui est final et ne peut donc être dérivée. Ce qui
signifie que si clone() génère une exception dans la classe
final, elle ne peut être modifiée via l'héritage et la prévention du clonage est
assurée (on ne peut appeler explicitement Object.clone() depuis une classe qui a
un niveau arbitraire d'héritage ; on en est limité à appeler super.clone(),
qui a seulement accès à sa classe parente directe). Implémenter des objets qui traite de sujets
relatifs à la sécurité implique donc de rendre ces classes final.
|
 |
 |
 |
The first method you see in class
CheckCloneable is tryToClone( ), which takes any
Ordinary object and checks to see whether it’s cloneable with
instanceof. If so, it casts the object to an IsCloneable, calls
clone( ) and casts the result back to Ordinary, catching any
exceptions that are thrown. Notice the use of run-time type identification (see
Chapter 12) to print the class name so you can see what’s
happening.
|
 |
La première méthode qu'on voit dans la classe
CheckCloneable est tryToClone(), qui prend n'importe quel objet
Ordinary et vérifie s'il est cloneable grâce à instanceof. Si
c'est le cas, il transtype l'objet en IsCloneable, appelle
clone() et retranstype le résultat en Ordinary, interceptant
toutes les exceptions générées. Remarquez l'utilisation de l'identification dynamique du type (voir
Chapitre 12) pour imprimer le nom de la classe afin de suivre le déroulement du
programme.
|
 |
 |
 |
In main( ), different types
of Ordinary objects are created and upcast to Ordinary in the
array definition. The first two lines of code after that create a plain
Ordinary object and try to clone it. However, this code will not compile
because clone( ) is a protected method in Object. The
remainder of the code steps through the array and tries to clone each object,
reporting the success or failure of each. The output is:
|
 |
Dans main(), différents types d'objets Ordinary sont créés et
transtypés en Ordinary dans la définition du tableau. Les deux premières lignes de
code qui suivent créent un objet Ordinary et tentent de le cloner. Cependant ce
code ne compile pas car clone() est une méthode protected dans
Object. Le reste du code parcourt le tableau et essaye de cloner chaque objet,
reportant le succès ou l'échec de l'opération. Le résultat est :
|
 |
 |
 |
Attempting IsCloneable Cloned IsCloneable Attempting NoMore Could not clone NoMore Attempting TryMore Could not clone TryMore Attempting BackOn Cloned BackOn Attempting ReallyNoMore Could not clone ReallyNoMore
|
 |
Attempting IsCloneable Cloned IsCloneable Attempting NoMore Could not clone NoMore Attempting TryMore Could not clone TryMore Attempting BackOn Cloned BackOn Attempting ReallyNoMore Could not clone ReallyNoMore
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |