Je cherchais des tutoriels expliquant à propos de Java Cloneable
, mais je n’ai pas de bons liens, et Stack Overflow devient de plus en plus un choix évident.
J'aimerais savoir ce qui suit:
Cloneable
signifie que nous pouvons avoir un clone ou une copie d'objets en implémentant l'interface Cloneable
. Quels sont les avantages et les inconvénients de cela?La première chose que vous devez savoir sur Cloneable
est: ne l'utilisez pas.
Il est très difficile d'implémenter le clonage avec Cloneable
correct, et l'effort en vaut la peine.
Au lieu de cela, utilisez d'autres options, comme Apache-commons SerializationUtils
(deep-clone) ou BeanUtils
(shallow-clone), ou utilisez simplement un constructeur de copie.
Voir ici pour les points de vue de Josh Bloch sur le clonage avec Cloneable
, ce qui explique les nombreux inconvénients de l’approche. ( Joshua Bloch était un employé de Sun et a dirigé le développement de nombreuses Java.)
Clonable en soi n'est malheureusement qu'une interface marqueur, c'est-à-dire qu'il ne définit pas la méthode clone ().
Ce qui fait est de changer le comportement de la méthode protégée Object.clone (), qui lève une exception CloneNotSupportedException pour les classes qui n'implémentent pas Cloneable, et effectue une copie superficielle adaptée aux membres pour les classes qui le font.
Même s'il s'agit du comportement que vous recherchez, vous devrez toujours implémenter votre propre méthode clone () afin de le rendre public.
Lors de l’implémentation de votre propre clone (), l’idée est de commencer par l’objet créé par super.clone (), qui a la garantie d’être de la bonne classe, puis de créer une population supplémentaire de champs dans le cas où une copie superficielle n’est pas ce qu’elle est. tu veux. L'appel d'un constructeur à partir de clone () poserait problème, car cela romprait l'héritage dans le cas où une sous-classe voudrait ajouter sa propre logique clonable supplémentaire. s'il appelait super.clone (), il obtiendrait un objet de la mauvaise classe dans ce cas.
Cette approche contourne cependant toute logique pouvant être définie dans vos constructeurs, ce qui pourrait poser problème.
Un autre problème est que toutes les sous-classes qui oublient de remplacer clone () hériteront automatiquement de la copie superficielle par défaut, ce qui ne correspond probablement pas à ce que vous souhaitez en cas d'état mutable (qui sera désormais partagé entre la source et la copie).
La plupart des développeurs n'utilisent pas Cloneable pour ces raisons et implémentent simplement un constructeur de copie.
Pour plus d'informations et les pièges potentiels de Cloneable, je recommande vivement le livre Effective Java de Joshua Bloch
Alors, utilisez judicieusement Cloneable. Cela ne vous procure pas suffisamment d'avantages par rapport aux efforts que vous devez faire pour bien faire les choses.
Le clonage est un paradigme de programmation de base. Le fait que Java l'ait mal implémenté à bien des égards ne diminue en rien le besoin de clonage. Et il est facile d'implémenter un clonage qui fonctionnera comme vous le souhaitez, superficiel Vous pouvez même utiliser le nom clone pour la fonction et ne pas implémenter Cloneable si vous le souhaitez.
Supposons que j'ai les classes A, B et C, où B et C sont dérivés de A. Si j'ai une liste d'objets de type A comme ceci:
ArrayList<A> list1;
Maintenant, cette liste peut contenir des objets de type A, B ou C. Vous ne savez pas quel type sont les objets. Donc, vous ne pouvez pas copier la liste comme ceci:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(new A(a));
}
Si l'objet est réellement de type B ou C, vous n'obtiendrez pas la bonne copie. Et si A est abstrait? Maintenant, certaines personnes ont suggéré ceci:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
if(a instanceof A) {
list2.add(new A(a));
} else if(a instanceof B) {
list2.add(new B(a));
} else if(a instanceof C) {
list2.add(new C(a));
}
}
C'est une très, très mauvaise idée. Que faire si vous ajoutez un nouveau type dérivé? Que se passe-t-il si B ou C sont dans un autre package et que vous n’y avez pas accès dans cette classe?
Ce que vous voudriez faire est ceci:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(a.clone());
}
Beaucoup de gens ont expliqué pourquoi l’implémentation de base du clone Java est problématique. Mais elle est facilement surmontée de cette façon:
En classe A:
public A clone() {
return new A(this);
}
En classe B:
@Override
public B clone() {
return new B(this);
}
En classe C:
@Override
public C clone() {
return new C(this):
}
Je n'implémente pas Cloneable, j'utilise simplement le même nom de fonction. Si vous n'aimez pas cela, nommez-le autrement.
A) Il n'y a pas beaucoup d'avantages à cloner par rapport à un constructeur de copie. Le plus important est probablement la possibilité de créer un nouvel objet du même type dynamique (en supposant que le type déclaré est clonable et qu'il possède une méthode de clonage public).
B) Le clone par défaut crée une copie superficielle et restera une copie superficielle à moins que l'implémentation de votre clone ne la modifie. Cela peut être difficile, surtout si votre classe a des champs finaux
Bozho a raison, un clone peut être difficile à obtenir. Une copie constructeur/usine servira la plupart des besoins.
Quels sont les inconvénients de Cloneable?
Le clonage est très dangereux si l'objet que vous copiez a une composition. Dans ce cas, vous devez envisager un effet secondaire possible, car le clone crée une copie superficielle:
Supposons que vous ayez un objet pour gérer les manipulations liées à la base de données. Disons que cet objet a l'objet Connection
en tant que propriété.
Ainsi, lorsque quelqu'un crée un clone de originalObject
, l'objet en cours de création crée, disons, cloneObject
. Ici, les originalObject
et cloneObject
contiennent la même référence pour l'objet Connection
.
Supposons que originalObject
ferme l'objet Connection
. Le cloneObject
ne fonctionnera donc plus car l'objet connection
était partagé entre eux et il était effectivement fermé par le originalObject
.
Un problème similaire peut se produire si vous voulez cloner un objet dont IOStream est la propriété.
Comment se produit le clonage récursif si l'objet est un objet composite?
Cloneable effectue une copie superficielle. La signification est que les données de l'objet original et de l'objet clone pointeront vers la même référence/mémoire. Au contraire, dans le cas d’une copie profonde, les données de la mémoire de l’objet original sont copiées dans la mémoire de l’objet clone.