Como copiar a lista Java Collections

141

Eu tenho um ArrayListe quero copiá-lo exatamente. Eu uso classes de utilidade quando possível, supondo que alguém tenha passado algum tempo corrigindo. Então, naturalmente, acabo com a Collectionsclasse que contém um método de cópia.

Suponha que eu tenha o seguinte:

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

Isso falha porque basicamente acha que bnão é grande o suficiente para aguentar a. Sim, eu sei que btem tamanho 0, mas agora deve ser grande o suficiente, não deveria? Se eu tiver que preencher bprimeiro, isso Collections.copy()se tornará uma função completamente inútil em minha mente. Então, exceto para programar uma função de cópia (que vou fazer agora), existe uma maneira adequada de fazer isso?

Jasper Floor
fonte
O documento para Collections.copy () diz "A lista de destinos deve ter pelo menos o tamanho da lista de fontes".
#
21
Eu não acho que a resposta aceita é correta
Bozho
3
Você aceitou uma resposta incorreta, Jasper Floor. Espero sinceramente que você não tenha usado as informações incorretas no seu código!
Malcolm

Respostas:

115

Chamando

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

cria uma cópia superficial de adentro b. Todos os elementos existirão dentro bexatamente na mesma ordem em que estavam a(assumindo que havia uma ordem).

Da mesma forma, chamar

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

também cria uma cópia superficial de adentro b. Se o primeiro parâmetro,b ,, não tiver capacidade suficiente (sem tamanho) para conter todos aos elementos de, ele lançará um IndexOutOfBoundsException. A expectativa é que não sejam necessárias alocações Collections.copypara o trabalho e, se houver, isso gera uma exceção. É uma otimização exigir que a coleção copiada seja pré-alocada ( b), mas geralmente não acho que o recurso valha a pena devido às verificações necessárias, dadas as alternativas baseadas em construtores, como a mostrada acima, que não têm efeitos colaterais estranhos.

Para criar uma cópia profunda, o Listmecanismo, por qualquer um dos mecanismos, teria que ter um conhecimento intrincado do tipo subjacente. No caso de Strings, que são imutáveis ​​em Java (e .NET, nesse caso), você nem precisa de uma cópia profunda. No caso de MySpecialObject, você precisa saber como fazer uma cópia profunda e isso não é uma operação genérica.


Nota: a resposta originalmente aceita foi o principal resultado do Collections.copyGoogle e foi totalmente incorreta, conforme indicado nos comentários.

Stephen Katulka
fonte
1
@ncasas Sim, sim. Estou lamentando o fato de não haver uma função genérica de "cópia" em Java. Na prática, quase sempre acho que outros autores não implementaram clone () para suas classes; deixa um sem a capacidade de fazer qualquer tipo de cópia de um objeto. Ou, pior ainda, vejo um método de clone implementado com documentação inexistente ou ruim, o que torna a função do clone inutilizável (em um sentido confiável e prático de "saber o que está acontecendo").
31512 Malcolm
133

btem uma capacidade de 3, mas um tamanho de 0. O fato de ArrayListter algum tipo de capacidade de buffer é um detalhe de implementação - não faz parte da Listinterface, portanto Collections.copy(List, List)não a usa. Seria feio para um caso especial ArrayList.

Como MrWiggles indicou, usar o construtor ArrayList que utiliza uma coleção é o caminho indicado no exemplo fornecido.

Para cenários mais complicados (que podem incluir seu código real), você pode achar úteis as coleções no Guava .

Jon Skeet
fonte
58

Apenas faça:

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

ArrayList tem um construtor que aceitará outra coleção para copiar os elementos de

tddmonkey
fonte
7
como alguém abaixo comenta, esta é uma cópia superficial. Caso contrário, isso teria sido uma ótima resposta. Suponho que deveria ter especificado isso. Deixa pra lá, eu segui em frente de qualquer maneira.
Jasper Floor
11
Para uma lista de strings, a cópia profunda não é importante, pois os Stringobjetos são imutáveis.
Drek Mahar
17

A resposta de Stephen Katulka (resposta aceita) está errada (a segunda parte). Explica que Collections.copy(b, a);faz uma cópia profunda, o que não faz. Ambos new ArrayList(a);eCollections.copy(b, a); apenas fazem uma cópia superficial. A diferença é que o construtor aloca nova memória, e copy(...)não o faz, o que a torna adequada nos casos em que você pode reutilizar matrizes, pois ela possui uma vantagem de desempenho.

A API padrão Java tenta desencorajar o uso de cópias profundas, pois seria ruim se novos codificadores usassem isso regularmente, o que também pode ser um dos motivos pelos quais clone() não é público por padrão.

O código fonte Collections.copy(...)pode ser visto na linha 552 em: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Se você precisar de uma cópia profunda, precisará iterar os itens manualmente, usando um loop for e clone () em cada objeto.

hoijui
fonte
12

a maneira mais simples de copiar uma lista é transmiti-la ao construtor da nova lista:

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

b será uma cópia rasa de a

Olhando a fonte Collections.copy(List,List)(eu nunca a tinha visto antes), parece ser para lidar com os elementos índice por índice. usar List.set(int,E)assim o elemento 0 substituirá o elemento 0 na lista de destino, etc. etc. Não é particularmente claro nos javadocs que eu teria que admitir.

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
fonte
por que você diz cópia 'superficial'? - me java noob #
283 Marlark
4
Por "cópia superficial", ele quer dizer que, após a cópia, os objetos em b são os mesmos que em a, e não cópias deles.
#
1
O javadoc para Collections.copy () diz "A lista de destinos deve ter pelo menos o tamanho da lista de fontes".
#
Eu acho que eu só quero dizer que me levou um par de olhares para ver o que a função realmente fez e eu posso ver como o questionador ficou um pouco confuso com exatamente o que ele faz
Gareth Davis
não tenho certeza se isso importa? Como String é imutável, apenas as referências não são as mesmas. no entanto, mesmo se u tentar transformar um elemento em qualquer lista, nunca transforma o mesmo elemento na outra lista
David T.
9
List b = new ArrayList(a.size())

não define o tamanho. Ele define a capacidade inicial (sendo quantos elementos pode caber antes de precisar redimensionar). Uma maneira mais simples de copiar neste caso é:

List b = new ArrayList(a);
cleto
fonte
8

Como Hoijui menciona. A resposta selecionada de Stephen Katulka contém um comentário sobre Collections.copy que está incorreto. O autor provavelmente aceitou porque a primeira linha de código estava fazendo a cópia que ele queria. A chamada adicional para Collections.copy apenas copia novamente. (Resultando na cópia acontecendo duas vezes).

Aqui está o código para provar isso.

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
fonte
5

A maioria das respostas aqui não percebem o problema, o usuário deseja copiar os elementos da primeira lista para a segunda lista, os elementos da lista de destino são novos objetos e não fazem referência aos elementos da lista original. (significa alterar um elemento da segunda lista não deve alterar valores para o elemento correspondente da lista de origem.) Para os objetos mutáveis, não podemos usar o construtor ArrayList (Collection) porque ele se refere simplesmente ao elemento da lista original e não é copiado. Você precisa ter um clonador de lista para cada objeto ao copiar.

yasirmcs
fonte
5

Por que você simplesmente não usa o addAllmétodo:

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

    List b = b.addAll(listA);

//b will be 1, abc

mesmo se você possui itens em b ou deseja pendurar alguns elementos depois dele, 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
fonte
3

Se você deseja copiar um ArrayList, copie-o usando:

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

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

As strings podem ser copiadas com profundidade

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

porque eles são imutáveis. Todos os outros Objetos não -> você precisa iterar e fazer uma cópia sozinho.

felix
fonte
8
Ainda é uma cópia superficial porque cada elemento da matriz baponta para o mesmo Stringobjeto correspondente em a. No entanto, isso não é importante porque, como você aponta, os Stringobjetos são imutáveis.
Drek 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
fonte
4
Por favor, adicione algumas explicações à sua resposta
Sampada
1
Embora esse código possa responder à pergunta, fornecer um contexto adicional sobre como e / ou por que resolve o problema melhoraria o valor a longo prazo da resposta.
Michael Parker
1

Todos os outros Objetos não -> você precisa iterar e fazer uma cópia sozinho.

Para evitar esse implemento 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
fonte
2
Não deveria ser "userList2.add ((User) u.clone ());" ?
precisa saber é o seguinte
1

A saída a seguir ilustra os resultados do uso do construtor de cópia e 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)

A fonte do programa completo está aqui: cópia da lista Java . Mas a saída é suficiente para ver como o java.util.Collections.copy () se comporta.

pwojnowski
fonte
1

E se você estiver usando o google goiaba, a solução de uma linha seria

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

Isso cria uma instância de lista de matriz mutável.

vsingh
fonte
1

Com o Java 8 sendo nulo, você pode usar o seguinte código.

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

Ou usando um colecionador

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

Copiar não é inútil se você imaginar o caso de uso para copiar alguns valores em uma coleção existente. Ou seja, você deseja substituir os elementos existentes em vez de inserir.

Um exemplo: a = [1,2,3,4,5] b = [2,2,2,2,3,3,3,3,3,4,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,4,4,4]

No entanto, eu esperaria um método de cópia que usasse parâmetros adicionais para o índice inicial da coleção de origem e destino, bem como um parâmetro para count.

Consulte Java BUG 6350752

ordnungswidrig
fonte
-1

Para entender por que Collections.copy () lança uma IndexOutOfBoundsException apesar de ter feito a matriz apoio da grande o suficiente lista de destinos (através da chamada size () na ListaDeOrigem), ver a resposta por Abhay Yadav nesta questão relacionada: Como copie um java.util.List para outro java.util.List

volkerk
fonte