Comment cloner ArrayList et cloner également son contenu?


273

Comment puis-je cloner un ArrayListet également cloner ses éléments en Java?

Par exemple, j'ai:

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....

Et je m'attendrais à ce que les objets clonedListne soient pas les mêmes que dans la liste des chiens.


Il a déjà été discuté dans la question de recommandation de l'utilitaire Deep clone
Swiety

Réponses:


199

Vous devrez parcourir les éléments et les cloner un par un, en plaçant les clones dans votre tableau de résultats au fur et à mesure.

public static List<Dog> cloneList(List<Dog> list) {
    List<Dog> clone = new ArrayList<Dog>(list.size());
    for (Dog item : list) clone.add(item.clone());
    return clone;
}

Pour que cela fonctionne, évidemment, vous devrez demander à votre Dogclasse d'implémenter l' Cloneableinterface et de remplacer la clone()méthode.


19
Mais vous ne pouvez pas le faire de manière générique. clone () ne fait pas partie de l'interface Cloneable.
Michael Myers

13
Mais clone () est protégé dans Object, vous ne pouvez donc pas y accéder. Essayez de compiler ce code.
Michael Myers

5
Toutes les classes étendent Object, afin qu'elles puissent remplacer clone (). C'est à ça que sert Cloneable!
Stephan202

2
C'est une bonne réponse. Cloneable est en fait une interface. Cependant, mmyers a un point, en ce que la méthode clone () est une méthode protégée déclarée dans la classe Object. Vous devrez remplacer cette méthode dans votre classe Dog et effectuer vous-même la copie manuelle des champs.
Jose

3
Je dis, créez une usine ou un constructeur, ou même juste une méthode statique, qui prendra une instance de Dog, et copiera manuellement les champs dans une nouvelle instance, et retournera cette nouvelle instance.
Jose

196

Personnellement, j'ajouterais un constructeur à Dog:

class Dog
{
    public Dog()
    { ... } // Regular constructor

    public Dog(Dog dog) {
        // Copy all the fields of Dog.
    }
}

Ensuite, répétez (comme indiqué dans la réponse de Varkhan):

public static List<Dog> cloneList(List<Dog> dogList) {
    List<Dog> clonedList = new ArrayList<Dog>(dogList.size());
    for (Dog dog : dogList) {
        clonedList.add(new Dog(dog));
    }
    return clonedList;
}

Je trouve que l'avantage est que vous n'avez pas besoin de faire le tour avec les trucs clonables cassés en Java. Il correspond également à la façon dont vous copiez les collections Java.

Une autre option pourrait être d'écrire votre propre interface ICloneable et de l'utiliser. De cette façon, vous pourriez écrire une méthode générique pour le clonage.


pouvez-vous être plus précis avec copier tous les champs de CHIEN. Je ne comprends vraiment pas :(
Dr. aNdRO

Est-il possible d'écrire cette fonction pour un objet non défini (au lieu de Dog)?
Tobi G.

@TobiG. Je ne comprends pas ce que tu veux dire. Voulez-vous cloneList(List<Object>)ou Dog(Object)?
cdmckay

@cdmckay Une fonction qui fonctionne pour cloneList (List <Object>), cloneList (List <Dog>) et cloneList (List <Cat>). Mais vous ne pouvez pas appeler un constructeur générique, je suppose ...?
Tobi G.

@TobiG. Comme une fonction de clonage générale alors? Ce n'est pas vraiment de cela qu'il s'agit.
cdmckay

143

Toutes les collections standard ont des constructeurs de copie. Utilise les.

List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy

clone()a été conçu avec plusieurs erreurs (voir cette question ), il est donc préférable de l'éviter.

Extrait de Effective Java 2nd Edition , Item 11: Override clone judiciously

Compte tenu de tous les problèmes associés à Cloneable, il est sûr de dire que d'autres interfaces ne devraient pas l'étendre et que les classes conçues pour l'héritage (article 17) ne devraient pas l'implémenter. En raison de ses nombreuses lacunes, certains programmeurs experts choisissent simplement de ne jamais remplacer la méthode de clonage et de ne jamais l'invoquer sauf, peut-être, pour copier des tableaux. Si vous concevez une classe pour l'héritage, sachez que si vous choisissez de ne pas fournir une méthode de clone protégé bien comportée, il sera impossible pour les sous-classes d'implémenter Cloneable.

Ce livre décrit également les nombreux avantages des constructeurs de copie par rapport à Cloneable / clone.

  • Ils ne s'appuient pas sur un mécanisme de création d'objets extralinguistiques sujet aux risques
  • Ils n'exigent pas l'adhésion inapplicable à des conventions peu documentées
  • Ils n'entrent pas en conflit avec la bonne utilisation des champs finaux
  • Ils ne lèvent pas d'exceptions vérifiées inutiles
  • Ils ne nécessitent pas de lancers.

Considérez un autre avantage de l'utilisation des constructeurs de copie: supposons que vous ayez un HashSet set que vous souhaitiez le copier en tant que TreeSet. La méthode clone ne peut pas offrir cette fonctionnalité, mais il est facile avec un constructeur de conversion: new TreeSet(s).


85
Autant que je sache, les constructeurs de copie des collections standards créent une faible profondeur copie, pas une profonde copie. La question posée ici cherche une réponse détaillée.
Abdull

20
ceci est tout simplement faux, les constuctors de copie font une copie superficielle - le point entier de la question
NimChimpsky

1
Ce qui est bien dans cette réponse, c'est que si vous ne modifiez pas les objets de la liste, l'ajout ou la suppression d'éléments ne les supprime pas des deux listes. Ce n'est pas aussi superficiel qu'une simple affectation.
Noumenon

42

Java 8 fournit une nouvelle façon d'appeler le constructeur de copie ou la méthode de clonage sur les chiens d'élément de manière élégante et compacte: Streams , lambdas et collectors .

Constructeur de copie:

List<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toList());

L'expression Dog::newest appelée référence de méthode . Il crée un objet fonction qui appelle un constructeur sur Doglequel prend un autre chien comme argument.

Méthode de clonage [1]:

List<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toList());

Obtenir un ArrayListrésultat

Ou, si vous devez obtenir un ArrayListretour (au cas où vous voudriez le modifier plus tard):

ArrayList<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toCollection(ArrayList::new));

Mettre à jour la liste en place

Si vous n'avez pas besoin de conserver le contenu d'origine de la dogsliste, vous pouvez plutôt utiliser la replaceAllméthode et mettre à jour la liste en place:

dogs.replaceAll(Dog::new);

Tous les exemples supposent import static java.util.stream.Collectors.*;.


Collectionneur pour ArrayLists

Le collecteur du dernier exemple peut être transformé en une méthode util. Comme c'est une chose courante à faire, j'aime personnellement que ce soit court et joli. Comme ça:

ArrayList<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

[1] Remarque sur CloneNotSupportedException:

Pour que cette solution fonctionne, la cloneméthode de Dog ne doit pas déclarer qu'elle lance CloneNotSupportedException. La raison en est que l'argument to mapn'est pas autorisé à lever des exceptions vérifiées.

Comme ça:

    // Note: Method is public and returns Dog, not Object
    @Override
    public Dog clone() /* Note: No throws clause here */ { ...

Cela ne devrait cependant pas être un gros problème, car c'est de toute façon la meilleure pratique. ( Effectice Java par exemple donne ce conseil.)

Merci à Gustavo de l'avoir noté.


PS:

Si vous le trouvez plus joli, vous pouvez utiliser la syntaxe de référence de la méthode pour faire exactement la même chose:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());

Voyez-vous un impact sur les performances de cette manière lorsque Dog (d) est le constructeur de copie? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: Votre version n'est pas adaptée aux threads et ne doit pas être utilisée avec des flux parallèles. C'est parce que l' parallelappel fait clonedDogs.addêtre appelé à partir de plusieurs threads en même temps. Les versions qui utilisent collectsont thread-safe. C'est l'un des avantages du modèle fonctionnel de la bibliothèque de flux, le même code peut être utilisé pour les flux parallèles.
Lii

1
@SaurabhJinturkar: En outre, l'opération de collecte est rapide. Il fait à peu près la même chose que votre version, mais fonctionne également pour les flux parallèles. Vous pouvez corriger votre version en utilisant, par exemple, une file d'attente simultanée au lieu d'une liste de tableaux, mais je suis presque certain que ce serait beaucoup plus lent.
Lii

Chaque fois que j'essaie d'utiliser votre solution , je reçois un Unhandled exception type CloneNotSupportedExceptionsur d.clone(). Déclarer l'exception ou l'attraper ne la résout pas.
Gustavo

@ Gustavo: C'est presque certainement parce que l'objet que vous clonez ( Dogdans cet exemple) ne prend pas en charge le clonage. Êtes-vous sûr qu'il implémente l' Clonableinterface?
Lii

28

Fondamentalement, il existe trois façons sans itérer manuellement,

1 Utilisation du constructeur

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);

2 Utilisation addAll(Collection<? extends E> c)

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(dogs);

3 Utilisation d'une addAll(int index, Collection<? extends E> c)méthode avec intparamètre

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);

NB: Le comportement de ces opérations ne sera pas défini si la collection spécifiée est modifiée pendant que l'opération est en cours.


51
veuillez
electrobabe

9
Ce n'est pas un clone profond, ces deux listes conservent les mêmes objets, copient seulement les références mais les objets Dog, une fois que vous avez modifié l'une ou l'autre liste, la liste suivante aura le même changement. donno y tant de votes positifs.
Saorikido

1
@ Neeson.Z Toutes les méthodes créent une copie complète de la liste et des copies superficielles de l'élément de la liste. Si vous modifiez un élément de la liste, le changement sera reflété par l'autre liste, mais si vous modifiez l'un de la liste (par exemple en supprimant un objet), l'autre liste sera inchangée.
Alessandro Teruzzi

17

Je pense que la réponse verte actuelle est mauvaise , pourquoi pourriez-vous demander?

  • Cela peut nécessiter d'ajouter beaucoup de code
  • Il vous oblige à répertorier toutes les listes à copier et à le faire

La façon dont la sérialisation est également mauvaise imo, vous devrez peut-être ajouter Serializable partout.

Alors, quelle est la solution:

Bibliothèque de clonage en profondeur Java La bibliothèque de clonage est une petite bibliothèque java open source (licence Apache) qui clone en profondeur des objets. Les objets n'ont pas à implémenter l'interface Cloneable. Effectivement, cette bibliothèque peut cloner N'IMPORTE QUEL objet java. Il peut être utilisé, par exemple dans les implémentations de cache, si vous ne voulez pas que l'objet mis en cache soit modifié ou chaque fois que vous voulez créer une copie complète des objets.

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);

Découvrez-le sur https://github.com/kostaskougios/cloning


8
Une mise en garde avec cette méthode est qu'elle utilise la réflexion, ce qui peut être un peu plus lent que la solution de Varkhan.
cdmckay

6
Je ne comprends pas le premier point "ça demande beaucoup de code". La bibliothèque dont vous parlez aurait besoin de plus de code. Ce n'est qu'une question de l'endroit où vous le placez. Sinon, je conviens qu'une bibliothèque spéciale pour ce genre de chose aide ..
nawfal

8

J'ai trouvé un moyen, vous pouvez utiliser json pour sérialiser / désérialiser la liste. La liste sérialisée ne contient aucune référence à l'objet d'origine lorsqu'elle n'est pas sérialisée.

Utilisation de gson:

List<CategoryModel> originalList = new ArrayList<>(); // add some items later
String listAsJson = gson.toJson(originalList);
List<CategoryModel> newList = new Gson().fromJson(listAsJson, new TypeToken<List<CategoryModel>>() {}.getType());

Vous pouvez le faire en utilisant également jackson et toute autre bibliothèque json.


1
Je ne sais pas pourquoi les gens ont rejeté cette réponse. D'autres réponses doivent implémenter clone () ou changer leurs dépendances pour inclure de nouvelles bibliothèques. Mais la bibliothèque JSon aurait déjà inclus la plupart des projets. J'ai voté pour cela.
Satish

1
@Satish Oui, c'est la seule réponse qui m'a aidé, je ne sais pas ce qui ne va pas avec les autres, mais peu importe ce que j'ai fait, cloner ou utiliser le constructeur de copie, ma liste d'origine était utilisée pour être mise à jour, mais de cette façon, cela ne fonctionne pas , donc merci à l'auteur!
Parag Pawar

Eh bien, il est vrai que ce n'est pas une pure réponse Java pour la connaissance, mais une solution efficace pour traiter ce problème rapidement
marcRDZ

grand hack, économise beaucoup de temps
mxml

6

J'ai toujours utilisé cette option:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);

2

Vous devrez le cloner à la ArrayListmain (en l'itérant et en copiant chaque élément dans un nouveau ArrayList), car vous clone()ne le ferez pas pour vous. La raison en est que les objets contenus dans le ArrayListpeuvent ne pas s’implémenter Clonable.

Edit : ... et c'est exactement ce que fait le code de Varkhan.


1
Et même s'ils le font, il n'y a aucun moyen d'accéder à clone () autre que la réflexion, et il n'est pas garanti de réussir de toute façon.
Michael Myers

1

Une façon désagréable est de le faire avec réflexion. Quelque chose comme ça a fonctionné pour moi.

public static <T extends Cloneable> List<T> deepCloneList(List<T> original) {
    if (original == null || original.size() < 1) {
        return new ArrayList<>();
    }

    try {
        int originalSize = original.size();
        Method cloneMethod = original.get(0).getClass().getDeclaredMethod("clone");
        List<T> clonedList = new ArrayList<>();

        // noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < originalSize; i++) {
            // noinspection unchecked
            clonedList.add((T) cloneMethod.invoke(original.get(i)));
        }
        return clonedList;
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        System.err.println("Couldn't clone list due to " + e.getMessage());
        return new ArrayList<>();
    }
}

Truc soigné et méchant! Un problème potentiel: si originalcontient des objets de différentes classes, je pense cloneMethod.invokequ'il échouera avec une exception lorsqu'il sera invoqué avec le mauvais type d'objet. Pour cette raison, il peut être préférable de récupérer un clone spécifique Methodpour chaque objet. Ou utilisez la méthode clone sur Object(mais comme celle-ci est protégée, cela peut échouer dans plusieurs cas).
Lii

En outre, je pense qu'il serait préférable de lever une exception d'exécution dans la clause catch au lieu de renvoyer une liste vide.
Lii

1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

Cela copiera en profondeur chaque chien


0

Les autres affiches sont correctes: vous devez itérer la liste et la copier dans une nouvelle liste.

Cependant ... Si les objets de la liste sont immuables - vous n'avez pas besoin de les cloner. Si votre objet possède un graphique d'objet complexe, il devra également être immuable.

L'autre avantage de l'immuabilité est qu'ils sont également threadsafe.


0

Voici une solution utilisant un type de modèle générique:

public static <T> List<T> copyList(List<T> source) {
    List<T> dest = new ArrayList<T>();
    for (T item : source) { dest.add(item); }
    return dest;
}

Les génériques sont bons, mais vous devez également cloner les éléments pour répondre à la question. Voir stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

pour vous, les objets remplacent la méthode clone ()

class You_class {

    int a;

    @Override
    public You_class clone() {
        You_class you_class = new You_class();
        you_class.a = this.a;
        return you_class;
    }
}

et appelez .clone () pour Vector obj ou ArraiList obj ....


0

Un moyen facile en utilisant commons-lang-2.3.jar cette bibliothèque de java pour cloner la liste

lien de téléchargement commons-lang-2.3.jar

Comment utiliser

oldList.........
List<YourObject> newList = new ArrayList<YourObject>();
foreach(YourObject obj : oldList){
   newList.add((YourObject)SerializationUtils.clone(obj));
}

J'espère que celui-ci peut être utile.

:RÉ


1
Juste une note: pourquoi une telle ancienne version de Commons Lang? Voir l'historique des versions ici: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

Le paquet import org.apache.commons.lang.SerializationUtils;

Il existe une méthode SerializationUtils.clone(Object);

Exemple

this.myObjectCloned = SerializationUtils.clone(this.object);

c'est un peu dépassé pour répondre à cette question. Et bien d'autres réponses dans le commentaire sous la question.
moskito-x


0

Ce qui suit a fonctionné pour moi ..

dans Dog.java

public Class Dog{

private String a,b;

public Dog(){} //no args constructor

public Dog(Dog d){ // copy constructor
   this.a=d.a;
   this.b=d.b;
}

}

 -------------------------

 private List<Dog> createCopy(List<Dog> dogs) {
 List<Dog> newDogsList= new ArrayList<>();
 if (CollectionUtils.isNotEmpty(dogs)) {
 dogs.stream().forEach(dog-> newDogsList.add((Dog) SerializationUtils.clone(dog)));
 }
 return newDogsList;
 }

Ici, la nouvelle liste créée à partir de la méthode createCopy est créée via SerializationUtils.clone (). Ainsi, toute modification apportée à la nouvelle liste n'affectera pas la liste d'origine


-1

Je pense avoir trouvé un moyen très simple de créer une copie complète d'ArrayList. En supposant que vous souhaitez copier un tableau String ArrayList arrayA.

ArrayList<String>arrayB = new ArrayList<String>();
arrayB.addAll(arrayA);

Faites-moi savoir si cela ne fonctionne pas pour vous.


3
ne fonctionne pas si vous utilisez List <List <JsonObject>> par exemple dans mon cas
djdance

Les cordes sont immuables. Le clonage n'a pas de sens et dans votre exemple, arrayB et arrayA ont les mêmes références d'objet - c'est une copie superficielle.
Christian Fries
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.