¿Cómo clonar ArrayList y también clonar su contenido?


273

¿Cómo puedo clonar ArrayListy también clonar sus elementos en Java?

Por ejemplo tengo:

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

Y esperaría que los objetos en clonedListno sean los mismos que en la lista de perros.


Ya se discutió en el clon profundo recomendación utilidad pregunta
Swiety

Respuestas:


199

Deberá iterar sobre los elementos y clonarlos uno por uno, colocando los clones en su matriz de resultados a medida que avanza.

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;
}

Para que eso funcione, obviamente, tendrá que hacer que su Dogclase implemente la Cloneableinterfaz y anule el clone()método.


19
Sin embargo, no puedes hacerlo genéricamente. clone () no es parte de la interfaz Cloneable.
Michael Myers

13
Pero clone () está protegido en Object, por lo que no puede acceder a él. Intenta compilar ese código.
Michael Myers

55
Todas las clases extienden Object, por lo que pueden anular clone (). ¡Para eso es Cloneable!
Stephan202

2
Esta es una buena respuesta. Cloneable es en realidad una interfaz. Sin embargo, mmyers tiene un punto, ya que el método clone () es un método protegido declarado en la clase Object. Tendría que anular este método en su clase de perro y hacer la copia manual de los campos usted mismo.
Jose

3
Digo, cree una fábrica o un generador, o incluso solo un método estático, que tomará una instancia de Dog, y copiará manualmente los campos a una nueva instancia, y devolverá esa nueva instancia.
Jose

196

Yo, personalmente, agregaría un constructor a Dog:

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

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

Luego solo itera (como se muestra en la respuesta 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;
}

Creo que la ventaja de esto es que no es necesario que juegue con las cosas clonables rotas en Java. También coincide con la forma en que copia las colecciones de Java.

Otra opción podría ser escribir su propia interfaz ICloneable y usarla. De esa manera, podría escribir un método genérico para la clonación.


puede ser más específico con copiar todos los campos de DOG. Realmente no entiendo :(
Dr. aNdRO

¿Es posible escribir esa función para un Objeto indefinido (en lugar de Perro)?
Tobi G.

@Demasiado grande. No entiendo lo que quieres decir. ¿Quieres cloneList(List<Object>)o Dog(Object)?
cdmckay

@cdmckay Una función que funciona para cloneList (List <Object>), cloneList (List <Dog>) y cloneList (List <Cat>). Pero supongo que no se puede llamar a un constructor genérico ...
Tobi G.

@Demasiado grande. ¿Como una función de clonación general entonces? De eso no se trata realmente esta pregunta.
cdmckay

143

Todas las colecciones estándar tienen constructores de copia. Usalos, usalos a ellos.

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

clone()fue diseñado con varios errores (vea esta pregunta ), por lo que es mejor evitarlo.

Desde Java 2nd Edition , artículo 11: anular juiciosamente el clon

Dados todos los problemas asociados con Cloneable, es seguro decir que otras interfaces no deberían extenderlo, y que las clases diseñadas para la herencia (Elemento 17) no deberían implementarlo. Debido a sus muchas deficiencias, algunos programadores expertos simplemente eligen nunca anular el método de clonación y nunca invocarlo, excepto, tal vez, copiar matrices. Si diseña una clase para herencia, tenga en cuenta que si elige no proporcionar un método de clonación protegido con buen comportamiento, será imposible que las subclases implementen Cloneable.

Este libro también describe las muchas ventajas que tienen los constructores de copias sobre Cloneable / clone.

  • No confían en un mecanismo de creación de objetos extralingüístico propenso al riesgo
  • No exigen una adherencia inaplicable a convenciones poco documentadas
  • No entran en conflicto con el uso adecuado de los campos finales.
  • No arrojan excepciones comprobadas innecesarias
  • No requieren moldes.

Considere otro beneficio del uso de constructores de copia: suponga que tiene un HashSet sy desea copiarlo como a TreeSet. El método clone no puede ofrecer esta funcionalidad, pero es fácil con un constructor de conversión: new TreeSet(s).


85
Por lo que sé, los constructores de copias de las colecciones estándar crean una copia superficial , no una copia profunda . La pregunta que se hace aquí busca una respuesta de copia profunda.
Abdull

20
esto simplemente está mal, los constructores de copias hacen una copia superficial - el punto de la pregunta
NimChimpsky

1
Lo correcto de esta respuesta es que si no está mutando los objetos en la lista, agregar o eliminar elementos no los elimina de ambas listas. No es tan superficial como una simple asignación.
Noumenon

42

Java 8 proporciona una nueva forma de llamar al constructor de copias o al método de clonación en los elementos perros de forma elegante y compacta: Streams , lambdas y recopiladores .

Copiar constructor:

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

La expresión Dog::newse llama referencia de método . Crea un objeto de función que llama a un constructor en el Dogque toma otro perro como argumento.

Método de clonación [1]:

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

Obteniendo un ArrayListresultado

O, si tiene que obtener un ArrayListrespaldo (en caso de que quiera modificarlo más adelante):

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

Actualice la lista en su lugar

Si no necesita conservar el contenido original de la dogslista, puede utilizar el replaceAllmétodo y actualizar la lista en su lugar:

dogs.replaceAll(Dog::new);

Todos los ejemplos asumen import static java.util.stream.Collectors.*;.


Coleccionista para ArrayLists

El recopilador del último ejemplo puede convertirse en un método util. Como esto es algo muy común, personalmente me gusta que sea corto y bonito. Me gusta esto:

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] Nota sobre CloneNotSupportedException:

Para que esta solución funcione, el clonemétodo de Dog no debe declarar que arroja CloneNotSupportedException. La razón es que mapno se permite al argumento lanzar ninguna excepción marcada.

Me gusta esto:

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

Sin embargo, esto no debería ser un gran problema, ya que es la mejor práctica de todos modos. ( Effectice Java, por ejemplo, da este consejo).

Gracias a Gustavo por notar esto.


PD:

Si lo encuentra más bonito, puede usar la sintaxis de referencia del método para hacer exactamente lo mismo:

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

¿Ve algún impacto en el rendimiento de hacerlo de esta manera donde Dog (d) es un constructor de copias? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: su versión no es segura para subprocesos y no debe usarse con transmisiones paralelas. Esto se debe a que la parallelllamada clonedDogs.addse llama desde varios subprocesos al mismo tiempo. Las versiones que utiliza collectson seguras para subprocesos. Esta es una de las ventajas del modelo funcional de la biblioteca de flujos, el mismo código puede usarse para flujos paralelos.
Lii

1
@SaurabhJinturkar: Además, la operación de recolección es rápida. Hace más o menos lo mismo que su versión, pero también funciona para flujos paralelos. Puede corregir su versión utilizando, por ejemplo, una cola concurrente en lugar de una lista de matriz, pero estoy casi seguro de que sería mucho más lento.
Lii

Cada vez que trato de usar su solución obtengo un Unhandled exception type CloneNotSupportedExceptionencendido d.clone(). Declarar la excepción o atraparla no la resuelve.
Gustavo

@Gustavo: Eso es casi seguro porque el objeto que está clonando ( Dogen este ejemplo) no es compatible con la clonación. ¿Estás seguro de que implementa la Clonableinterfaz?
Lii

28

Básicamente hay tres formas sin iterar manualmente,

1 Usando constructor

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

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

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

3 Uso del addAll(int index, Collection<? extends E> c)método con intparámetro

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

NB: El comportamiento de estas operaciones será indefinido si la colección especificada se modifica mientras la operación está en progreso.


51
por favor no que todas estas 3 variantes solo creen copias superficiales de las listas
electrobabe

9
Este no es un clon profundo, esas dos listas retienen los mismos objetos, solo copiaron las referencias pero los objetos Dog, una vez que modificó cualquiera de las listas, la siguiente lista tendrá el mismo cambio. donno y tantos votos a favor.
Saorikido

1
@ Neeson.Z Todos los métodos crean una copia profunda de la lista y una copia superficial del elemento de la lista. Si modifica un elemento de la lista, el cambio se reflejará en la otra lista, pero si modifica una de la lista (por ejemplo, eliminando un objeto), la otra lista no cambiará.
Alessandro Teruzzi

17

Creo que la respuesta verde actual es mala , ¿por qué podrías preguntar?

  • Puede requerir agregar mucho código
  • Requiere que enumere todas las listas que se copiarán y haga esto

La forma en que la serialización también es mala, puede que tengas que agregar Serializable por todas partes.

Entonces, cuál es la solución:

Biblioteca Java de clonación profunda La biblioteca de clonación es una pequeña biblioteca Java de código abierto (licencia de Apache) que clona objetos en profundidad. Los objetos no tienen que implementar la interfaz clonable. Efectivamente, esta biblioteca puede clonar CUALQUIER objeto java. Se puede usar, es decir, en implementaciones de caché si no desea que se modifique el objeto en caché o cuando quiera crear una copia profunda de los objetos.

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

Compruébalo en https://github.com/kostaskougios/cloning


8
Una advertencia con este método es que utiliza la reflexión, que puede ser bastante más lenta que la solución de Varkhan.
cdmckay

66
No entiendo el primer punto "requiere mucho código". La biblioteca de la que estás hablando necesitaría más código. Es solo una cuestión de dónde lo colocas. De lo contrario, estoy de acuerdo una biblioteca especial para este tipo de cosas ayuda a ..
nawfal

8

He encontrado una manera, puede usar json para serializar / deserializar la lista. La lista serializada no contiene ninguna referencia al objeto original cuando no está serializada.

Usando 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());

Puede hacerlo usando jackson y cualquier otra biblioteca json también.


1
No sé por qué la gente ha rechazado esta respuesta. Otras respuestas tienen que implementar clone () o cambiar sus dependencias para incluir nuevas bibliotecas. Pero la biblioteca JSon ya habría incluido la mayoría de los proyectos. Voté por esto.
Satish

1
@Satish Sí, esta es la única respuesta que me ayudó, no estoy seguro de qué pasa con los demás, pero no importa lo que hice, clonar o usar el constructor de copias, mi lista original solía actualizarse, pero de esta manera no lo hace. , así que gracias al autor!
Parag Pawar

Bueno, es cierto que no es una respuesta pura de Java por el bien del conocimiento, sino una solución eficiente para abordar este problema rápidamente
MarcRDZ

gran truco, ahorra mucho tiempo
mxml

6

Siempre he usado esta opción:

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

2

Tendrá que clonar a ArrayListmano (iterando sobre él y copiando cada elemento en un nuevo ArrayList), porque clone()no lo hará por usted. La razón de esto es que los objetos contenidos en el ArrayListpueden no implementarse Clonable.

Editar : ... y eso es exactamente lo que hace el código de Varkhan.


1
E incluso si lo hacen, no hay forma de acceder a clone () que no sea la reflexión, y no se garantiza que tenga éxito de todos modos.
Michael Myers

1

Una forma desagradable es hacerlo con reflexión. Algo como esto funcionó para mí.

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<>();
    }
}

Truco limpio y desagradable! Un problema potencial: si originalcontiene objetos de diferentes clases, creo cloneMethod.invokeque fallará con una excepción cuando se invoque con el tipo de objeto incorrecto. Debido a esto, podría ser mejor recuperar un clon específico Methodpara cada objeto. O utilice el método de clonación Object(pero dado que está protegido, puede fallar en más casos).
Lii

Además, creo que sería mejor lanzar una excepción de tiempo de ejecución en la cláusula catch en lugar de devolver una lista vacía.
Lii

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

Esto copiará en profundidad cada perro


0

Los otros carteles son correctos: debe iterar la lista y copiarla en una nueva lista.

Sin embargo ... Si los objetos en la lista son inmutables, no necesita clonarlos. Si su objeto tiene un gráfico de objeto complejo, también deberá ser inmutable.

El otro beneficio de la inmutabilidad es que también son seguros.


0

Aquí hay una solución que usa un tipo de plantilla genérica:

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

Los genéricos son buenos, pero también debe clonar los elementos para responder la pregunta. Ver stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

para ustedes los objetos anulan el método 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;
    }
}

y llame a .clone () para Vector obj u ArraiList obj ....


0

Manera fácil usando commons-lang-2.3.jar esa biblioteca de java para clonar la lista

enlace de descarga commons-lang-2.3.jar

Cómo utilizar

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

Espero que este pueda ser útil.

:RE


1
Solo una nota: ¿por qué una versión tan antigua de Commons Lang? Vea el historial de versiones aquí: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

El paquete import org.apache.commons.lang.SerializationUtils;

Hay un metodo SerializationUtils.clone(Object);

Ejemplo

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

Está un poco anticuado responder esta pregunta. Y muchas otras respuestas en el comentario debajo de la pregunta.
moskito-x


0

Lo siguiente funcionó para mí ...

en 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;
 }

Aquí, la nueva lista que se creó a partir del método createCopy se crea a través de SerializationUtils.clone (). Por lo tanto, cualquier cambio realizado en la nueva lista no afectará la lista original


-1

Creo que encontré una manera realmente fácil de hacer una copia profunda de ArrayList. Suponiendo que desea copiar un String ArrayList arrayA.

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

Avísame si no te funciona.


3
no funciona si usa List <List <JsonObject>> por ejemplo en mi caso
djdance

Las cuerdas son inmutables. La clonación no tiene sentido y en su ejemplo, arrayB y arrayA tienen las mismas referencias de objeto: es una copia superficial.
Christian Fries el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.