Como você faz uma cópia profunda de um objeto?

301

É um pouco difícil implementar uma função de cópia de objeto profundo. Quais etapas você toma para garantir que o objeto original e o clonado não compartilhem nenhuma referência?

Andrei Savu
fonte
4
O Kryo possui suporte interno para cópia / clonagem . Isso é cópia direta de objeto para objeto, não objeto-> bytes-> objeto.
NateS
1
Aqui está uma pergunta relacionada que foi questionado mais tarde: Profundo recomendação utilidade clone
Brad Cupit
Usar a biblioteca de clonagem salvou o dia para mim! github.com/kostaskougios/cloning
gaurav

Respostas:

168

Uma maneira segura é serializar o objeto e desserializar. Isso garante que tudo seja uma nova referência.

Aqui está um artigo sobre como fazer isso com eficiência.

Advertências: É possível que as classes substituam a serialização, de modo que novas instâncias não sejam criadas, por exemplo, para singletons. Além disso, é claro que isso não funciona se suas aulas não forem serializáveis.

Jason Cohen
fonte
6
Esteja ciente de que a implementação FastByteArrayOutputStream fornecida no artigo pode ser mais eficiente. Ele usa uma expansão no estilo ArrayList quando o buffer é preenchido, mas é melhor usar uma abordagem de expansão no estilo LinkedList. Em vez de criar um novo buffer 2x e copiar o buffer atual com excesso de memória, mantenha uma lista vinculada de buffers, adicionando um novo quando a corrente ficar cheia. Se você receber uma solicitação para gravar mais dados do que caberia no tamanho padrão do buffer, crie um nó de buffer que seja exatamente tão grande quanto a solicitação; os nós não precisam ter o mesmo tamanho.
26711 Brian Harris
Um bom artigo, que explica a cópia profunda através de serialização: javaworld.com/article/2077578/learn-java/...
Ad Infinitum
A lista vinculada @BrianHarris não é mais eficiente que a matriz dinâmica. Inserção de elementos em uma matriz dinâmica é amortizado complexidade constante, durante a inserção numa lista ligada é complexidade linear
Norill Tempest
Quanto serializar e desserializar mais lentamente que a abordagem do construtor de cópias?
Woland 12/06
75

Algumas pessoas mencionaram o uso ou substituição Object.clone(). Não faça isso. Object.clone()apresenta alguns problemas importantes e seu uso é desencorajado na maioria dos casos. Consulte o Item 11, do " Java Efetivo ", de Joshua Bloch, para obter uma resposta completa. Eu acredito que você pode usar com segurança Object.clone()em matrizes de tipo primitivo, mas, além disso, você precisa ser criterioso ao usar e substituir o clone corretamente.

Os esquemas que dependem da serialização (XML ou não) são kludgy.

Não há uma resposta fácil aqui. Se você deseja copiar um objeto em profundidade, precisará percorrer o gráfico do objeto e copiar cada objeto filho explicitamente através do construtor de cópia do objeto ou de um método estático de fábrica que, por sua vez, copia profundamente o objeto filho. Imutáveis ​​(por exemplo, Strings) não precisam ser copiados. Como um aparte, você deve favorecer a imutabilidade por esse motivo.

Julien Chastang
fonte
58

Você pode fazer uma cópia profunda com serialização sem criar arquivos.

Seu objeto que você deseja copiar em profundidade precisará implement serializable. Se a classe não for final ou não puder ser modificada, estenda a classe e implemente serializável.

Converta sua classe em um fluxo de bytes:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

Restaure sua classe a partir de um fluxo de bytes:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
Thargor
fonte
4
Se a aula for final, como você a estenderia?
Kumar Manish
1
@KumarManish classe MyContainer implementa Serializable {instância MyFinalClass; ...}
Matteo T.
Acho essa uma ótima resposta. O clone é uma confusão
blackbird014
@MatteoT. como a propriedade de classe não serializável será serializada, não serializável instancenesse caso?
Farid
40

Você pode criar um clone profundo baseado em serialização usando o org.apache.commons.lang3.SerializationUtils.clone(T)Apache Commons Lang, mas tenha cuidado - o desempenho é péssimo.

Em geral, é uma prática recomendada escrever seus próprios métodos de clone para cada classe de um objeto no gráfico de objetos que precisam de clonagem.

user8690
fonte
Ele também está disponível emorg.apache.commons.lang.SerializationUtils
Pino
25

Uma maneira de implementar a cópia profunda é adicionar construtores de cópia a cada classe associada. Um construtor de cópia pega uma instância de 'this' como seu argumento único e copia todos os valores dele. Bastante trabalho, mas bem direto e seguro.

EDIT: observe que você não precisa usar métodos de acesso para ler os campos. Você pode acessar todos os campos diretamente porque a instância de origem é sempre do mesmo tipo que a instância com o construtor de cópia. Óbvio, mas pode ser esquecido.

Exemplo:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Editar: Observe que, ao usar construtores de cópia, você precisa conhecer o tipo de tempo de execução do objeto que está copiando. Com a abordagem acima, você não pode copiar facilmente uma lista mista (poderá fazê-lo com algum código de reflexão).

Adriaan Koster
fonte
1
Apenas interessado no caso em que o que você está copiando é uma subclasse, mas está sendo referenciado pelo pai. É possível substituir o construtor de cópia?
Bunny 'n' Bunny
Por que sua classe pai se refere à sua subclasse? Você pode dar um exemplo?
Adriaan Koster
1
classe pública Car estende Vehicle E depois se refere ao carro como veículo. originaList = new ArrayList <Veículo>; copyList = new ArrayList <Veículo>; originalList.add (novo carro ()); for (Veículo veículo: vehicleList) {copyList.add (veículo novo (veículo)); }
Pork 'n' Bunny
@AdriaanKoster: se a lista original contiver um Toyota, seu código colocará um Carna lista de destinos. A clonagem adequada geralmente exige que a classe forneça um método de fábrica virtual cujo contrato declare que ele retornará um novo objeto de sua própria classe; o próprio contratante de cópia deve protectedgarantir que ele seja usado apenas para construir objetos cujo tipo preciso corresponda ao do objeto que está sendo copiado).
Supercat
Então, se eu entendi sua sugestão corretamente, o método de fábrica chamaria o construtor de cópia privada? Como o construtor de cópias de uma subclasse garantiria que os campos da superclasse fossem inicializados? Você pode dar um exemplo?
Adriaan Koster
20

Você pode usar uma biblioteca que possui uma API simples e executa uma clonagem relativamente rápida com reflexão (deve ser mais rápida que os métodos de serialização).

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
CorayThan
fonte
19

O Apache commons oferece uma maneira rápida de clonar profundamente um objeto.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
TheByeByeMan
fonte
1
Isso funciona apenas para o objeto que implementa Serializable e também para todos os campos nele.
WinDay #
11

O XStream é realmente útil nesses casos. Aqui está um código simples para fazer clonagem

private static final XStream XSTREAM = new XStream();
...

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
sankara
fonte
1
Nããão, você não precisa da sobrecarga de xml-ing do objeto.
egelev
@egeleve Você percebe que está respondendo a um comentário de 08, certo? Não uso mais o Java e provavelmente existem ferramentas melhores agora. No entanto, naquela época, serializar para um formato diferente e depois serializar de volta parecia um bom truque - era definitivamente ineficiente.
Sankara
10

Para usuários do Spring Framework . Usando classe org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
Igor Rybak
fonte
9

Para objetos complicados e quando o desempenho não é significativo, eu uso uma biblioteca json, como gson para serializar o objeto em texto json, depois desserializa o texto para obter um novo objeto.

O gson, que com base na reflexão, funciona na maioria dos casos, exceto que os transientcampos não serão copiados e os objetos com referência circular com causa StackOverflowError.

public static <T> T copy(T anObject, Class<T> classInfo) {
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(anObject);
    T newObject = gson.fromJson(text, classInfo);
    return newObject;
}
public static void main(String[] args) {
    String originalObject = "hello";
    String copiedObject = copy(originalObject, String.class);
}
tiboo
fonte
3
Siga as convenções de nomenclatura Java para você e para o nosso bem.
Patrick Bergner 12/12
8

Use o XStream ( http://x-stream.github.io/ ). Você pode controlar quais propriedades você pode ignorar por meio de anotações ou especificar explicitamente o nome da propriedade para a classe XStream. Além disso, você não precisa implementar interface clonável.

Adisesha
fonte
7

A cópia em profundidade só pode ser feita com o consentimento de cada classe. Se você tiver controle sobre a hierarquia de classes, poderá implementar a interface clonável e o método Clone. Caso contrário, é impossível fazer uma cópia detalhada com segurança, porque o objeto também pode estar compartilhando recursos que não são de dados (por exemplo, conexões com o banco de dados). Em geral, porém, a cópia profunda é considerada uma prática ruim no ambiente Java e deve ser evitada por meio das práticas de design apropriadas.

Orion Adrian
fonte
2
Você poderia descrever as "práticas de design apropriadas"?
Fklappan
6
import com.thoughtworks.xstream.XStream;

public class deepCopy {
    private static  XStream xstream = new XStream();

    //serialize with Xstream them deserialize ...
    public static Object deepCopy(Object obj){
        return xstream.fromXML(xstream.toXML(obj));
    }
}
Eric Leschinski
fonte
5

Eu usei o Dozer para clonar objetos java e é ótimo nisso, a biblioteca Kryo é outra ótima alternativa.

Super Nova
fonte
2

O BeanUtils faz um ótimo trabalho de clonagem profunda de beans.

BeanUtils.cloneBean(obj);
Alfergon
fonte
4
Faz clonagem superficial.
Peter Šály
2

1)

public static Object deepClone(Object object) {
   try {
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     ObjectOutputStream oos = new ObjectOutputStream(baos);
     oos.writeObject(object);
     ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
     ObjectInputStream ois = new ObjectInputStream(bais);
     return ois.readObject();
   }
   catch (Exception e) {
     e.printStackTrace();
     return null;
   }
 }

2)

    // (1) create a MyPerson object named Al
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
    MyPerson al = new MyPerson("Al", "Arun", address);

    // (2) make a deep clone of Al
    MyPerson neighbor = (MyPerson)deepClone(al);

Aqui, sua classe MyPerson e MyAddress deve implementar uma interface serilazable

Uma corrida
fonte
2

Usando Jackson para serializar e desserializar o objeto. Esta implementação não requer que o objeto implemente a classe Serializable.

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

  }
Karthik Rao
fonte