Como clonar ArrayList e também clonar seu conteúdo?

273

Como posso clonar um ArrayListe também clonar seus itens em Java?

Por exemplo, eu tenho:

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

E eu esperaria que os objetos clonedListnão sejam os mesmos da lista de cães.

palig
fonte
Ele já foi discutido em clone profundo utilitário recomendação questão
Swiety

Respostas:

199

Você precisará iterar nos itens e cloná-los um por um, colocando os clones em sua matriz de resultados à medida que avança.

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 isso funcione, obviamente, você precisará que sua Dogclasse implemente a Cloneableinterface e substitua o clone()método.

Varkhan
fonte
19
Você não pode fazer isso genericamente, no entanto. clone () não faz parte da interface Cloneable.
Michael Myers
13
Mas clone () está protegido em Object, então você não pode acessá-lo. Tente compilar esse código.
Michael Myers
5
Todas as classes estendem Object, para que possam substituir clone (). É para isso que serve o Cloneable!
Stephan202
2
Esta é uma boa resposta. Clonável é na verdade uma interface. No entanto, mmyers tem razão, pois o método clone () é um método protegido declarado na classe Object. Você precisaria substituir esse método na sua classe Dog e fazer a cópia manual dos campos.
314 Jose
3
Digo, crie uma fábrica ou construtor, ou mesmo apenas um método estático, que leve uma instância do Dog, copie manualmente os campos para uma nova instância e retorne a nova instância.
313 Jose
196

Eu, pessoalmente, adicionaria um construtor ao Dog:

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

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

Em seguida, basta iterar (como mostrado na resposta 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;
}

Acho que a vantagem disso é que você não precisa se meter com o material Cloneable quebrado em Java. Também corresponde à maneira como você copia as coleções Java.

Outra opção poderia ser escrever sua própria interface ICloneable e usá-la. Dessa forma, você pode escrever um método genérico para clonagem.

cdmckay
fonte
você pode ser mais específico com copiar todos os campos do DOG. Eu realmente não estou entendendo :(
Dr. aNdRO
É possível escrever essa função para um objeto indefinido (em vez de Dog)?
Tobi G.
@TobiG. Eu não entendo o que você quer dizer. Você quer cloneList(List<Object>)ou Dog(Object)?
Cdmckay
@cdmckay Uma função que funciona para cloneList (List <Object>), cloneList (List <Dog>) e cloneList (List <Cat>). Mas você não pode chamar um construtor genérico, eu acho ...?
Tobi G.Fev
@TobiG. Como uma função de clonagem geral, então? Não é exatamente disso que se trata esta questão.
Cdmckay
143

Todas as coleções padrão têm construtores de cópia. Usa-os.

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

clone()foi projetado com vários erros (consulte esta pergunta ), por isso é melhor evitá-lo.

Do Effective Java 2nd Edition , item 11: Substituir clone criteriosamente

Dado todos os problemas associados ao Cloneable, é seguro dizer que outras interfaces não devem estendê-lo e que as classes projetadas para herança (Item 17) não devem implementá-lo. Por causa de suas muitas deficiências, alguns programadores especializados simplesmente optam por nunca substituir o método clone e nunca invocá-lo, exceto, talvez, para copiar matrizes. Se você projetar uma classe para herança, esteja ciente de que, se optar por não fornecer um método de clone protegido bem comportado, será impossível para as subclasses implementar o Cloneable.

Este livro também descreve as muitas vantagens que os construtores de cópias têm sobre o Cloneable / clone.

  • Eles não contam com um mecanismo de criação de objeto extralinguístico que apresenta riscos
  • Eles não exigem adesão inexequível a convenções pouco documentadas
  • Eles não entram em conflito com o uso adequado dos campos finais
  • Eles não lançam exceções verificadas desnecessárias
  • Eles não exigem lançamentos.

Considere outro benefício do uso de construtores de cópia: suponha que você tenha um HashSet se deseje copiá-lo como a TreeSet. O método clone não pode oferecer essa funcionalidade, mas é fácil com um construtor de conversão: new TreeSet(s).

Rose Perrone
fonte
85
Tanto quanto sei, os construtores de cópia das coleções padrão criam uma cópia superficial , não uma cópia profunda . A pergunta feita aqui procura uma resposta detalhada.
Abdull
20
isso simplesmente errado, copiar constuctors fazer uma cópia superficial - a ePoint retr de te pergunta
Nim Chimpsky
1
O que é certo nesta resposta é que, se você não estiver mutando os objetos na lista, adicionar ou remover itens não os removerá das duas listas. Não é tão superficial quanto uma tarefa simples.
Noumenon
42

O Java 8 fornece uma nova maneira de chamar o método construtor de cópia ou clone no elemento dog de maneira elegante e compacta: Streams , lambdas e coletores .

Copiar construtor:

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

A expressão Dog::newé chamada de referência de método . Ele cria um objeto de função que chama um construtor no Dogqual leva outro cão como argumento.

Método de clone [1]:

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

Obtendo um ArrayListcomo resultado

Ou, se você precisar receber uma ArrayListresposta (caso queira modificá-lo posteriormente):

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

Atualize a lista no local

Se você não precisar manter o conteúdo original da dogslista, poderá usar o replaceAllmétodo e atualizar a lista:

dogs.replaceAll(Dog::new);

Todos os exemplos assumem import static java.util.stream.Collectors.*;.


Coletor para ArrayLists

O coletor do último exemplo pode ser transformado em um método util. Como isso é algo comum, eu pessoalmente gosto de ser curto e bonito. Como isso:

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 solução funcione, o clonemétodo de Dog não deve declarar que é lançado CloneNotSupportedException. O motivo é que o argumento para mapnão tem permissão para lançar nenhuma exceção verificada.

Como isso:

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

No entanto, esse não deve ser um grande problema, pois, de qualquer maneira, essa é a melhor prática. (O Effectice Java, por exemplo, dá esse conselho.)

Obrigado a Gustavo por observar isso.


PS:

Se você achar mais bonito, poderá usar a sintaxe de referência do método para fazer exatamente a mesma coisa:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());
Lii
fonte
Você vê algum impacto no desempenho dessa maneira em que Dog (d) é construtor de cópias? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar
1
@SaurabhJinturkar: Sua versão não é segura para threads e não deve ser usada com fluxos paralelos. Isso ocorre porque a parallelchamada faz com que clonedDogs.addsejam chamados de vários threads ao mesmo tempo. As versões utilizadas collectsão seguras para threads. Essa é uma das vantagens do modelo funcional da biblioteca de fluxos, o mesmo código pode ser usado para fluxos paralelos.
Lii 29/07
1
@SaurabhJinturkar: Além disso, a operação de coleta é rápida. Faz praticamente a mesma coisa que a sua versão, mas também funciona para fluxos paralelos. Você pode corrigir sua versão usando, por exemplo, uma fila simultânea em vez de uma lista de matrizes, mas tenho quase certeza de que seria muito mais lento.
Lii 29/07
Sempre que eu tento usar a sua solução eu recebo um Unhandled exception type CloneNotSupportedExceptionno d.clone(). Declarar a exceção ou capturá-la não a resolve.
23316 Gustavo
@Gustavo: Isso é quase certamente porque o objeto que você está clonando ( Dogneste exemplo) não suporta a clonagem. Tem certeza de que implementa a Clonableinterface?
Lii
28

Basicamente, existem três maneiras sem iterar manualmente,

1 Usando o construtor

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 Usando o addAll(int index, Collection<? extends E> c)método com o intparâmetro

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

Nota: O comportamento dessas operações será indefinido se a coleção especificada for modificada enquanto a operação estiver em andamento.

javatar
fonte
50
por favor, não que todos estes 3 apenas variantes criar cópias rasas das listas
electrobabe
9
Este não é um clone profundo, essas duas listas retêm os mesmos objetos, apenas copiam as referências, mas os objetos Dog, uma vez que você modificou uma das listas, a próxima lista terá a mesma alteração. Não há tantos votos.
Saorikido 12/10
1
@ Neeson.Z Todos os métodos criam uma cópia profunda da lista e uma cópia superficial do elemento da lista. Se você modificar um elemento da lista, a alteração será refletida pela outra lista, mas se você modificar uma da lista (por exemplo, remover um objeto), a outra lista não será alterada.
Alessandro Teruzzi
17

Eu acho que a resposta verde atual é ruim , por que você pode perguntar?

  • Pode ser necessário adicionar muito código
  • Requer que você liste todas as listas a serem copiadas e faça isso

A maneira como a serialização também é ruim, talvez você precise adicionar Serializable em todo o lugar.

Então qual é a solução:

Biblioteca de Deep Clonagem Java A biblioteca de clonagem é uma pequena biblioteca java de código aberto (licença apache) que clona profundamente objetos. Os objetos não precisam implementar a interface Cloneable. De fato, essa biblioteca pode clonar QUALQUER objeto java. Pode ser usado, por exemplo, em implementações de cache, se você não quiser que o objeto em cache seja modificado ou sempre que desejar criar uma cópia profunda dos objetos.

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

Confira em https://github.com/kostaskougios/cloning

Cojones
fonte
8
Uma ressalva desse método é que ele usa reflexão, que pode ser um pouco mais lenta que a solução de Varkhan.
Cdmckay
6
Eu não entendo o primeiro ponto "requer muito código". A biblioteca da qual você está falando precisaria de mais código. É apenas uma questão de onde você o coloca. Caso contrário, eu concordo uma biblioteca especial para este tipo de coisa ajuda ..
Nawfal
8

Eu encontrei uma maneira, você pode usar o json para serializar / desserializar a lista. A lista serializada não contém referência ao objeto original quando não serializada.

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

Você pode fazer isso usando jackson e qualquer outra biblioteca json também.

sagits
fonte
1
Não sei por que as pessoas votaram negativamente nesta resposta. Outras respostas precisam implementar clone () ou alterar suas dependências para incluir novas bibliotecas. Mas a biblioteca JSon, a maioria dos projetos já teria incluído. Eu votei para isso.
Satish 22/03
1
@Satish Sim, esta é a única resposta que me ajudou, não sei o que há de errado com os outros, mas não importa o que eu fiz, clone ou use o construtor de cópias, minha lista original costumava ser atualizada, mas dessa forma não , então obrigado ao autor!
Parag Pawar
Bem, é verdade que não é uma resposta Java puro por causa knowledge's mas uma solução eficiente para lidar com este problema rapidamente
marcRDZ
grande hack, economiza tempo
mxml
6

Eu sempre usei essa opção:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);
besartm
fonte
2

Você precisará clonar ArrayListmanualmente (iterando sobre ele e copiando cada elemento para um novo ArrayList), porque clone()não fará isso por você. A razão para isso é que os objetos contidos no ArrayListpodem não Clonablese implementar .

Edit : ... e é exatamente isso que o código de Varkhan faz.

Stephan202
fonte
1
E mesmo se o fizerem, não há como acessar o clone () além da reflexão, e não há garantia de sucesso.
Michael Myers
1

Uma maneira desagradável é fazê-lo com reflexão. Algo assim funcionou para mim.

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<>();
    }
}
milosmns
fonte
Truque limpo e desagradável! Um problema em potencial: se houver originalobjetos de classes diferentes, acho cloneMethod.invokeque falhará com uma exceção quando for invocado com o tipo errado de objeto. Por isso, pode ser melhor recuperar um clone específico Methodpara cada objeto. Ou use o método clone em Object(mas, como esse está protegido, pode falhar em mais casos).
Lii
Além disso, acho que seria melhor lançar uma exceção de tempo de execução na cláusula catch, em vez de retornar uma lista vazia.
Lii
1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

Isso irá copiar profundamente cada cão

Raju K
fonte
0

Os outros pôsteres estão corretos: você precisa iterar a lista e copiar em uma nova lista.

No entanto ... Se os objetos na lista são imutáveis ​​- você não precisa cloná-los. Se o seu objeto tiver um gráfico complexo, ele também precisará ser imutável.

O outro benefício da imutabilidade é que eles também são seguros para threads.

Fortyrunner
fonte
0

Aqui está uma solução usando um tipo de modelo genérico:

public static <T> List<T> copyList(List<T> source) {
    List<T> dest = new ArrayList<T>();
    for (T item : source) { dest.add(item); }
    return dest;
}
Andrew Coyte
fonte
Os genéricos são bons, mas você também precisa clonar os itens para responder à pergunta. Veja stackoverflow.com/a/715660/80425
David Snabel-Caunt
0

para você, os objetos substituem o 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;
    }
}

e chame .clone () para Vector obj ou ArraiList obj ....

RN3KK Nick
fonte
0

Maneira fácil usando commons-lang-2.3.jar que biblioteca de java para clonar lista

link para download commons-lang-2.3.jar

Como usar

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

Espero que este possa ser útil.

: D

sonida
fonte
1
Apenas uma observação: por que uma versão tão antiga do Commons Lang? Veja o histórico de versões aqui: commons.apache.org/proper/commons-lang/release-history.html
informatik01
0

O pacote import org.apache.commons.lang.SerializationUtils;

Existe um método SerializationUtils.clone(Object);

Exemplo

this.myObjectCloned = SerializationUtils.clone(this.object);
pacheco
fonte
está um pouco desatualizado para responder a essa pergunta. E muitas outras respostas no comentário abaixo da pergunta.
Moskito-x
0

Acabei de desenvolver uma lib capaz de clonar um objeto de entidade e um objeto java.util.List. Faça o download do jar em https://drive.google.com/open?id=0B69Sui5ah93EUTloSktFUkctN0U e use o método estático cloneListObject (lista de lista). Este método não apenas clona a lista, mas também todos os elementos da entidade.

Eduardo de Melo
fonte
0

O abaixo funcionou para mim ..

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

Aqui, a nova lista criada a partir do método createCopy é criada por meio de SerializationUtils.clone (). Portanto, qualquer alteração feita na nova lista não afetará a lista original

gayu312
fonte
-1

Acho que encontrei uma maneira muito fácil de fazer uma cópia profunda do ArrayList. Supondo que você deseja copiar um String ArrayList arrayA.

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

Deixe-me saber se não funcionar para você.

jordanrh
fonte
3
não funciona se você usar List <List <JSONObject >> por exemplo no meu caso
djdance
Strings são imutáveis. A clonagem não faz sentido e, no seu exemplo, arrayB e arrayA têm as mesmas referências de objeto - é uma cópia superficial.
Christian Fries