Cómo copiar la lista de colecciones de Java

141

Tengo un ArrayListy quiero copiarlo exactamente. Uso clases de utilidad cuando es posible bajo la suposición de que alguien pasó algún tiempo corrigiendo. Entonces, naturalmente, termino con la Collectionsclase que contiene un método de copia.

Supongamos que tengo lo siguiente:

List<String> a = new ArrayList<String>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new ArrayList<String>(a.size());

Collections.copy(b,a);

Esto falla porque básicamente cree bque no es lo suficientemente grande como para aguantar a. Sí, sé que btiene el tamaño 0, pero ahora debería ser lo suficientemente grande, ¿no? Si tengo que llenar bprimero, entonces se Collections.copy()convierte en una función completamente inútil en mi mente. Entonces, a excepción de la programación de una función de copia (que voy a hacer ahora), ¿hay una manera adecuada de hacerlo?

Jasper Floor
fuente
El documento de Collections.copy () dice "La lista de destino debe ser al menos tan larga como la lista de origen".
DJClayworth
21
No creo que la respuesta aceptada sea correcta
Bozho
3
Aceptaste una respuesta incorrecta, Jasper Floor. ¡Espero sinceramente que no haya utilizado la información incorrecta en su código!
Malcolm

Respuestas:

115

Vocación

List<String> b = new ArrayList<String>(a);

crea una copia superficial de adentro b. Todos los elementos existirán dentro bexactamente en el mismo orden en que estaban a(suponiendo que tuviera un orden).

Del mismo modo, llamando

// note: instantiating with a.size() gives `b` enough capacity to hold everything
List<String> b = new ArrayList<String>(a.size());
Collections.copy(b, a);

También crea una copia superficial de adentro b. Si el primer parámetro, bno tiene suficiente capacidad (no tamaño) para contener todos alos elementos, arrojará un IndexOutOfBoundsException. La expectativa es que no se requerirán asignaciones Collections.copypara trabajar, y si las hay, arroja esa excepción. Es una optimización requerir que la colección copiada sea preasignada ( b), pero generalmente no creo que la función valga la pena debido a las verificaciones requeridas dadas las alternativas basadas en el constructor como la que se muestra arriba que no tienen efectos secundarios extraños.

Para crear una copia profunda List, a través de cualquiera de los mecanismos, tendría que tener un conocimiento complejo del tipo subyacente. En el caso de Strings, que son inmutables en Java (y .NET para el caso), ni siquiera necesita una copia profunda. En el caso de MySpecialObject, necesita saber cómo hacer una copia profunda y no es una operación genérica.


Nota: La respuesta originalmente aceptada fue el resultado principal para Collections.copyGoogle, y fue completamente erróneo como se señala en los comentarios.

Stephen Katulka
fuente
1
@ncasas Sí, lo hace. Lamento el hecho de que no haya una función genérica de "copia" en Java. En la práctica, a menudo encuentro que otros autores no han implementado clone () para sus clases; deja a uno sin la capacidad de hacer ningún tipo de copia de un objeto. O, lo que es peor, veo un método de clonación implementado sin documentación o con poca documentación, lo que hace que la función de clonación sea inutilizable (en un sentido confiable y práctico de "saber lo que está sucediendo").
Malcolm
133

btiene una capacidad de 3, pero un tamaño de 0. El hecho de que ArrayListtenga algún tipo de capacidad de almacenamiento intermedio es un detalle de implementación: no es parte de la Listinterfaz, por lo Collections.copy(List, List)que no la usa. Sería feo que fuera un caso especial ArrayList.

Como MrWiggles ha indicado, usar el constructor ArrayList que toma una colección es la forma de hacerlo en el ejemplo proporcionado.

Para escenarios más complicados (que bien pueden incluir su código real), puede encontrar útiles las colecciones dentro de Guava .

Jon Skeet
fuente
58

Solo haz:

List a = new ArrayList(); 
a.add("a"); 
a.add("b"); 
a.add("c"); 
List b = new ArrayList(a);

ArrayList tiene un constructor que aceptará otra Colección para copiar los elementos de

tddmonkey
fuente
77
Como comenta alguien a continuación, esta es una copia superficial. De lo contrario, esta habría sido una buena respuesta. Supongo que debería haber especificado eso. No importa, me he mudado de todos modos.
Jasper Floor
11
Para una lista de cadenas, la copia profunda no es importante ya que los Stringobjetos son inmutables.
Derek Mahar
17

La respuesta de Stephen Katulka (respuesta aceptada) es incorrecta (la segunda parte). Explica que Collections.copy(b, a);hace una copia profunda, que no lo hace. Ambos, new ArrayList(a);y Collections.copy(b, a);solo hacer una copia superficial. La diferencia es que el constructor asigna nueva memoria, y copy(...)no lo hace, lo que la hace adecuada en casos en los que puede reutilizar matrices, ya que tiene una ventaja de rendimiento allí.

La API estándar de Java intenta desalentar el uso de copias profundas, ya que sería malo que los nuevos codificadores lo usen regularmente, lo que también puede ser una de las razones por las clone()que no es público de forma predeterminada.

El código fuente Collections.copy(...)se puede ver en la línea 552 en: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Si necesita una copia profunda, debe iterar sobre los elementos manualmente, utilizando un bucle for y clone () en cada objeto.

hoijui
fuente
12

La forma más sencilla de copiar una lista es pasarla al constructor de la nueva lista:

List<String> b = new ArrayList<>(a);

b será una copia superficial de a

Mirando la fuente de Collections.copy(List,List)(nunca lo había visto antes) parece ser para hacer frente a los elementos índice por índice. usar List.set(int,E)así el elemento 0 sobreescribirá el elemento 0 en la lista de destino, etc.

List<String> a = new ArrayList<>(a);
a.add("foo");
b.add("bar");

List<String> b = new ArrayList<>(a); // shallow copy 'a'

// the following will all hold
assert a.get(0) == b.get(0);
assert a.get(1) == b.get(1);
assert a.equals(b);
assert a != b; // 'a' is not the same object as 'b'
Gareth Davis
fuente
¿por qué dices copia 'superficial'? - me java noob
Martlark
44
Por "copia superficial" quiere decir que después de la copia, los objetos en b son los mismos objetos que en a, no copias de ellos.
DJClayworth
1
El javadoc para Collections.copy () dice "La lista de destino debe ser al menos tan larga como la lista de origen".
DJClayworth
Supongo que solo quiero decir que me tomó un par de miradas para ver qué hacía realmente la función y puedo ver cómo el interlocutor se confundió un poco con exactamente lo que hace
Gareth Davis
No estoy seguro de que importe? como String es inmutable, solo las referencias no son las mismas. sin embargo, incluso si intentas mutar un elemento en cualquiera de las listas, nunca muta el mismo elemento en la otra lista
David T.
9
List b = new ArrayList(a.size())

No establece el tamaño. Establece la capacidad inicial (es decir, cuántos elementos puede caber antes de que necesite cambiar su tamaño). Una forma más simple de copiar en este caso es:

List b = new ArrayList(a);
cletus
fuente
8

Como hoijui menciona. La respuesta seleccionada de Stephen Katulka contiene un comentario sobre Collections.copy que es incorrecto. El autor probablemente lo aceptó porque la primera línea de código estaba haciendo la copia que quería. La llamada adicional a Collections.copy solo se copia nuevamente. (Resultando que la copia ocurre dos veces).

Aquí hay un código para probarlo.

public static void main(String[] args) {

    List<String> a = new ArrayList<String>();
    a.add("a");
    a.add("b");
    a.add("c");
    List<String> b = new ArrayList<String>(a);

    System.out.println("There should be no output after this line.");

    // Note, b is already a shallow copy of a;
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, this was a deep copy."); // Note this is never called.
        }
    }

    // Now use Collections.copy and note that b is still just a shallow copy of a
    Collections.copy(b, a);
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, i was wrong this was a deep copy"); // Note this is never called.
        }
    }

    // Now do a deep copy - requires you to explicitly copy each element
    for (int i = 0; i < a.size(); i++) {
        b.set(i, new String(a.get(i)));
    }

    // Now see that the elements are different in each 
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) == b.get(i)) {
            System.out.println("oops, i was wrong, a shallow copy was done."); // note this is never called.
        }
    }
}
Michael Welch
fuente
5

La mayoría de las respuestas aquí no se dan cuenta del problema, el usuario quiere tener una COPIA de los elementos de la primera lista a la segunda lista, los elementos de la lista de destino son objetos nuevos y no hacen referencia a los elementos de la lista original. (significa que cambiar un elemento de la segunda lista no debería cambiar los valores para el elemento correspondiente de la lista fuente). Para los objetos mutables no podemos usar el constructor ArrayList (Colección) porque simplemente se referirá al elemento original de la lista y no copiará. Debe tener un clonador de lista para cada objeto al copiar.

yasirmcs
fuente
5

¿Por qué no usas el addAllmétodo?

    List a = new ArrayList();
         a.add("1");
         a.add("abc");

    List b = b.addAll(listA);

//b will be 1, abc

incluso si tiene elementos existentes en bo desea colocar algunos elementos después de ellos, como:

List a = new ArrayList();
     a.add("1");
     a.add("abc");

List b = new ArrayList();
     b.add("x");
     b.addAll(listA);
     b.add("Y");

//b will be x, 1, abc, Y
Vin.X
fuente
3

Si desea copiar una ArrayList, cópiela usando:

List b = new ArrayList();
b.add("aa");
b.add("bb");

List a = new ArrayList(b);
Martin C.
fuente
3

Las cadenas se pueden copiar en profundidad con

List<String> b = new ArrayList<String>(a);

porque son inmutables Cualquier otro objeto no -> necesita iterar y hacer una copia usted mismo.

felix
fuente
8
Esto sigue siendo una copia superficial porque cada elemento de la matriz bapunta al mismo Stringobjeto correspondiente en a. Sin embargo, esto no es importante porque, como usted señala, los Stringobjetos son inmutables.
Derek Mahar
3
private List<Item> cloneItemList(final List<Item> items)
    {
        Item[] itemArray = new Item[items.size()];
        itemArray = items.toArray(itemArray);
        return Arrays.asList(itemArray);
    }
Raen K
fuente
44
Agregue alguna explicación a su respuesta
Sampada
1
Si bien este código puede responder la pregunta, proporcionar un contexto adicional sobre cómo y / o por qué resuelve el problema mejoraría el valor a largo plazo de la respuesta.
Michael Parker
1

Cualquier otro objeto no -> necesita iterar y hacer una copia usted mismo.

Para evitar esto, implemente Cloneable.

public class User implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String user;
    private String password;
    ...

    @Override
    public Object clone() {
        Object o = null;
        try {
          o = super.clone();
        } catch(CloneNotSupportedException e) {
        }
        return o;
     }
 }

....

  public static void main(String[] args) {

      List<User> userList1 = new ArrayList<User>();

      User user1 = new User();
      user1.setUser("User1");
      user1.setPassword("pass1");
      ...

      User user2 = new User();
      user2.setUser("User2");
      user2.setPassword("pass2");
      ...

      userList1 .add(user1);
      userList1 .add(user2);

      List<User> userList2 = new ArrayList<User>();


      for(User u: userList1){
          u.add((User)u.clone());
      }

      //With this you can avoid 
      /*
        for(User u: userList1){
            User tmp = new User();
            tmp.setUser(u.getUser);
            tmp.setPassword(u.getPassword);
            ...
            u.add(tmp);               
        }
       */

  }
Juan castillo
fuente
2
¿No debería ser "userList2.add ((User) u.clone ());" ?
KrishPrabakar
1

El siguiente resultado ilustra los resultados del uso de copy constructor y Collections.copy ():

Copy [1, 2, 3] to [1, 2, 3] using copy constructor.

Copy [1, 2, 3] to (smaller) [4, 5]
java.lang.IndexOutOfBoundsException: Source does not fit in dest
        at java.util.Collections.copy(Collections.java:556)
        at com.farenda.java.CollectionsCopy.copySourceToSmallerDest(CollectionsCopy.java:36)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:14)

Copy [1, 2] to (same size) [3, 4]
source: [1, 2]
destination: [1, 2]

Copy [1, 2] to (bigger) [3, 4, 5]
source: [1, 2]
destination: [1, 2, 5]

Copy [1, 2] to (unmodifiable) [4, 5]
java.lang.UnsupportedOperationException
        at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
        at java.util.Collections.copy(Collections.java:561)
        at com.farenda.java.CollectionsCopy.copyToUnmodifiableDest(CollectionsCopy.java:68)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:20)

La fuente del programa completo está aquí: copia de la Lista Java . Pero el resultado es suficiente para ver cómo se comporta java.util.Collections.copy ().

pwojnowski
fuente
1

Y si está utilizando google guava, la solución de una línea sería

List<String> b = Lists.newArrayList(a);

Esto crea una instancia de lista de matriz mutable.

vsingh
fuente
1

Con Java 8 siendo nulo-seguro, puede usar el siguiente código.

List<String> b = Optional.ofNullable(a)
                         .map(list -> (List<String>) new ArrayList<>(list))
                         .orElseGet(Collections::emptyList);

O usando un coleccionista

List<String> b = Optional.ofNullable(a)
                         .map(List::stream)
                         .orElseGet(Stream::empty)
                         .collect(Collectors.toList())
Nicolas Henneaux
fuente
0

Copiar no es inútil si imagina el caso de uso para copiar algunos valores en una colección existente. Es decir, desea sobrescribir elementos existentes en lugar de insertar.

Un ejemplo: a = [1,2,3,4,5] b = [2,2,2,2,3,3,3,3,3,4,4,4,] a.copia (b) = [1,2,3,4,5,3,3,3,3,4,4,4]

Sin embargo, esperaría un método de copia que tomaría parámetros adicionales para el índice de inicio de la colección de origen y destino, así como un parámetro para el recuento.

Ver Java BUG 6350752

ordnungswidrig
fuente
-1

Para comprender por qué Collections.copy () arroja una IndexOutOfBoundsException aunque haya hecho que la matriz de respaldo de la lista de destinos sea lo suficientemente grande (a través de la llamada size () en la lista de origen), vea la respuesta de Abhay Yadav en esta pregunta relacionada: Cómo copie un java.util.List en otro java.util.List

volkerk
fuente