web-dev-qa-db-fra.com

clone () vs constructeur de copie vs méthode d'usine?

J'ai fait un rapide Google pour implémenter clone () en Java et j'ai trouvé: http://www.javapractices.com/topic/TopicAction.do?Id=71

Il a le commentaire suivant:

les constructeurs de copie et les méthodes de fabrique statique offrent une alternative au clone et sont beaucoup plus faciles à implémenter.

Tout ce que je veux faire est de faire une copie en profondeur. Implémenter clone () semble avoir beaucoup de sens, mais cet article très Google me fait un peu peur.

Voici les problèmes que j'ai remarqués:

Les constructeurs de copie ne fonctionnent pas avec Generics.

Voici un pseudo-code qui ne compilera pas.

public class MyClass<T>{
   ..
   public void copyData(T data){
       T copy=new T(data);//This isn't going to work.    
   }
   ..
}

Exemple 1: Utilisation d'un constructeur de copie dans une classe générique.

Les méthodes d'usine n'ont pas de noms standard.

C'est assez agréable d'avoir une interface pour le code réutilisable.

public class MyClass<T>{
    ..
    public void copyData(T data){
        T copy=data.clone();//Throws an exception if the input was not cloneable
    }
    ..
}

Exemple 2: Utilisation de clone () dans une classe générique.

J'ai remarqué que le clone n'est pas une méthode statique, mais ne serait-il pas nécessaire de faire des copies complètes de tous les champs protégés? Lors de la mise en œuvre de clone (), l’effort supplémentaire de générer des exceptions dans des sous-classes non clonables me semble trivial.

Est-ce que je manque quelque chose? N'importe quelles idées seraient appréciées.

75
User1

En gros, le clone est cassé . Rien ne fonctionnera facilement avec les génériques. Si vous avez quelque chose comme ça (raccourci pour faire passer le message):

public class SomeClass<T extends Copyable> {


    public T copy(T object) {
        return (T) object.copy();
    }
}

interface Copyable {
    Copyable copy();
}

Ensuite, avec un avertissement du compilateur, vous pouvez faire le travail. Étant donné que les génériques sont effacés au moment de l'exécution, les avertissements du compilateur seront générés lors de la copie. Ce n'est pas évitable dans ce cas.. Cela peut être évité dans certains cas (merci, kb304) mais pas dans tous. Prenons le cas où vous devez prendre en charge une sous-classe ou une classe inconnue implémentant l'interface (par exemple, vous effectuez une itération dans une collection d'objets à copier qui ne génère pas nécessairement la même classe).

49
Yishai

Il y a aussi le motif Builder. Voir Effective Java pour plus de détails.

Je ne comprends pas votre évaluation. Dans un constructeur de copie, vous connaissez parfaitement le type. Pourquoi est-il nécessaire d'utiliser des génériques?

public class C {
   public int value;
   public C() { }
   public C(C other) {
     value = other.value;
   }
}

Il y avait une question similaire récemment ici .

public class G<T> {
   public T value;
   public G() { }
   public G(G<? extends T> other) {
     value = other.value;
   }
}

Un échantillon exécutable:

public class GenTest {
    public interface Copyable<T> {
        T copy();
    }
    public static <T extends Copyable<T>> T copy(T object) {
        return object.copy();
    }
    public static class G<T> implements Copyable<G<T>> {
        public T value;
        public G() {
        }
        public G(G<? extends T> other) {
            value = other.value;
        }
        @Override
        public G<T> copy() {
            return new G<T>(this);
        }
    }
    public static void main(String[] args) {
        G<Integer> g = new G<Integer>();
        g.value = 1;
        G<Integer> f = g.copy();
        g.value = 2;
        G<Integer> h = copy(g);
        g.value = 3;
        System.out.printf("f: %s%n", f.value);
        System.out.printf("g: %s%n", g.value);
        System.out.printf("h: %s%n", h.value);
    }
}
25
akarnokd

Java n'a pas de constructeur de copie dans le même sens que C++.

Vous pouvez avoir un constructeur qui prend un objet du même type comme argument, mais peu de classes le supportent. (moins que le nombre qui supporte clone capable)

Pour un clone générique, j'ai une méthode d'assistance qui crée une nouvelle instance d'une classe et copie les champs de l'original (une copie superficielle) en utilisant des réflexions (en réalité quelque chose comme des réflexions mais plus rapide)

Pour une copie en profondeur, une approche simple consiste à sérialiser l'objet et à le désérialiser.

BTW: Ma suggestion est d'utiliser des objets immuables, vous n'aurez donc pas besoin de les cloner. ;)

8
Peter Lawrey

Je pense que la réponse de Yishai pourrait être améliorée afin que nous ne puissions avoir aucun avertissement avec le code suivant:

public class SomeClass<T extends Copyable<T>> {

    public T copy(T object) {
        return object.copy();
    }
}

interface Copyable<T> {
    T copy();
}

Ainsi, une classe devant implémenter une interface copiable doit ressembler à ceci:

public class MyClass implements Copyable<MyClass> {

    @Override
    public MyClass copy() {
        // copy implementation
        ...
    }

}
5
Alepac

Ci-dessous quelques inconvénients à cause desquels de nombreux développeurs n'utilisent pas Object.clone()

  1. Pour utiliser la méthode Object.clone(), nous devons ajouter de nombreuses syntaxes à notre code, comme implémenter l’interface Cloneable, définir la méthode clone() et gérer CloneNotSupportedException, et enfin appeler Object.clone() et le convertir en objet.
  2. L’interface Cloneable manque de la méthode clone(), c’est une interface marqueur et elle n’a aucune méthode, mais nous devons l’implémenter simplement pour dire à JVM que nous pouvons exécuter clone() sur notre objet.
  3. Object.clone() étant protégé, nous devons fournir notre propre clone() et appeler indirectement Object.clone().
  4. Nous n’avons aucun contrôle sur la construction de l’objet car Object.clone() n’appelle aucun constructeur.
  5. Si nous écrivons la méthode clone() dans une classe enfant, par exemple. Person alors toutes ses super-classes doivent définir la méthode clone() ou hériter de la classe parent sinon la chaîne super.clone() échouera.
  6. Object.clone() ne prend en charge que la copie superficielle, de sorte que les champs de référence de notre objet nouvellement cloné conservent les objets conservés par les champs de notre objet d'origine. Pour résoudre ce problème, nous devons implémenter clone() dans chaque classe référencée par notre classe, puis les cloner séparément dans notre méthode clone(), comme dans l'exemple ci-dessous.
  7. Nous ne pouvons pas manipuler les champs finaux dans Object.clone() car les champs finaux ne peuvent être modifiés que par le biais de constructeurs. Dans notre cas, si nous voulons que chaque objet Person soit unique par son identifiant, nous obtiendrons l’objet dupliqué si nous utilisons Object.clone() car Object.clone() n’appellera pas le constructeur et le champ final id ne pourra pas être modifié à partir de Person.clone().

Les constructeurs de copie sont meilleurs que Object.clone() car ils

  1. Ne nous obligez pas à mettre en œuvre une interface ou à lever une exception, mais nous pouvons le faire si cela est nécessaire.
  2. Ne nécessite aucun moulage.
  3. Ne nous obligez pas à dépendre d’un mécanisme de création d’objet inconnu.
  4. Ne demandez pas à la classe parent de suivre un contrat ou d’implémenter quoi que ce soit.
  5. Permettez-nous de modifier les champs finaux.
  6. Permettez-nous d'avoir un contrôle complet sur la création d'objet, nous pouvons y écrire notre logique d'initialisation.

Lire plus sur Java Cloning - Constructeur de copie versus clonage

2
Naresh Joshi

Un modèle qui peut fonctionner pour vous est la copie au niveau bean. Fondamentalement, vous utilisez un constructeur sans argument et appelez différents setters pour fournir les données. Vous pouvez même utiliser les différentes bibliothèques de propriétés de beans pour définir les propriétés relativement facilement. Ce n'est pas la même chose que de faire un clone (), mais pour de nombreuses raisons pratiques, ça va.

Si l'on n'est pas au courant à 100% de toutes les bizarreries de clone(), je conseillerais alors de rester à l'écart. Je ne dirais pas que clone() cassé. Je dirais: utilisez-le uniquement lorsque vous êtes tout à fait sûr que c'est votre meilleure option . il ne copie que ce que vous voulez copier, et copie comme vous voulez que les choses soient copiées. Vous pouvez le couper à vos besoins exacts.

De plus, il est facile de déboguer ce qui se passe lorsque vous appelez votre méthode constructeur/copie.

Et clone() ne crée pas une copie "complète" de votre objet en sortie de boîte, si vous entendez par là que non seulement les références (par exemple, une Collection) sont copiées. Mais en savoir plus sur deep and shallow ici: Copie profonde, copie superficielle, clone

0
sorrymissjackson

L’interface Cloneable est cassée, en ce sens qu’elle est inutile mais que cloner fonctionne bien et peut améliorer les performances pour les gros objets - 8 champs et plus, mais l’échec de l’analyse d’échappement échouera. Il est donc préférable d’utiliser le constructeur de copie la plupart du temps . L’utilisation du clone sur un tableau est plus rapide que celle de Arrays.copyOf, car sa longueur est garantie.

plus de détails ici https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html

0
user3996996

En général, clone () fonctionne en tandem avec un constructeur de copie protégé. Ceci est fait car clone (), contrairement à un constructeur, peut être virtuel.

Dans un corps de classe dérivé d'une super classe, nous avons

class Derived extends Base {
}

Donc, dans sa forme la plus simpliste, vous ajouteriez à cela un constructeur de copie virtuelle avec clone (). (En C++, Joshi recommande le clonage en tant que constructeur de copie virtuelle.)

protected Derived() {
    super();
}

protected Object clone() throws CloneNotSupportedException {
    return new Derived();
}

Cela devient plus compliqué si vous voulez appeler super.clone () comme recommandé et que vous devez ajouter ces membres à la classe, vous pouvez essayer ceci

final String name;
Address address;

/// This protected copy constructor - only constructs the object from super-class and
/// sets the final in the object for the derived class.
protected Derived(Base base, String name) {
   super(base);
   this.name = name;
}

protected Object clone() throws CloneNotSupportedException {
    Derived that = new Derived(super.clone(), this.name);
    that.address = (Address) this.address.clone();
}

Maintenant, si une exécution, vous avez 

Base base = (Base) new Derived("name");

et vous avez ensuite fait

Base clone = (Base) base.clone();

cela invoquerait clone () dans la classe dérivée (celle ci-dessus), cela invoquerait super.clone () - qui peut ou non être implémenté, mais il vous est conseillé de l'appeler. L'implémentation passe ensuite la sortie de super.clone () à un constructeur de copie protégé qui prend une base et vous transmettez tous les membres finaux.

Ce constructeur de copie appelle ensuite le constructeur de copie de la super-classe (si vous savez qu'il en a un) et définit les finales.

Lorsque vous revenez à la méthode clone (), vous définissez les membres non finaux.

Les lecteurs avisés remarqueront que si vous avez un constructeur de copie dans la base, il sera appelé par super.clone () - et sera appelé à nouveau lorsque vous appelez le super-constructeur dans le constructeur protégé. super copieur-constructeur deux fois. Espérons que s'il verrouille des ressources, il le saura.

0
Bonaparte