Java: solução recomendada para clonagem / cópia profunda de uma instância

176

Eu estou querendo saber se existe uma maneira recomendada de fazer clone / cópia profunda da instância em java.

Tenho 3 soluções em mente, mas posso sentir falta de algumas e gostaria de ter sua opinião

editar: inclua a proposta de Bohzo e refine a pergunta: trata-se mais de clonagem profunda do que de clonagem superficial.

Faça Você Mesmo:

codifique o clone manualmente, após as propriedades, e verifique se as instâncias mutáveis ​​também são clonadas.
pro:
- controle do que será executado
-
contras de execução rápida :
- tedioso para escrever e manter
- propenso a erros (falha de copiar / colar, falta de propriedade, propriedade mutável reatribuída)

Use reflexão:

Com suas próprias ferramentas de reflexão ou com um auxiliar externo (como jakarta common-beans), é fácil escrever um método genérico de cópia que fará o trabalho em uma linha.
pro:
- fácil de escrever
- sem
contras de manutenção :
- menos controle do que acontece
- propenso a erros com objetos mutáveis ​​se a ferramenta de reflexão também não clonar sub-objetos
- execução mais lenta

Use a estrutura do clone:

Use uma estrutura que faça isso por você, como:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- o mesmo que reflexão
- mais controle sobre o que será exatamente clonado.
contras:
- toda instância mutável é totalmente clonada, mesmo no final da hierarquia
- pode ser muito lenta para executar

Use a instrumentação bytecode para gravar clone em tempo de execução

javassit , BCEL ou cglib podem ser usados ​​para gerar um clonador dedicado tão rápido quanto uma mão escreveu. Alguém conhece uma biblioteca usando uma dessas ferramentas para esse fim?

O que eu perdi aqui?
Qual desses você recomendaria ?

Obrigado.

Guillaume
fonte
1
aparentemente a Java Deep Cloning Library mudou-se para aqui: code.google.com/p/cloning
Mr_and_Mrs_D

Respostas:

155

Para clonagem profunda (clona toda a hierarquia de objetos):

  • commons-lang SerializationUtils - usando serialização - se todas as classes estiverem sob seu controle e você puder forçar a implementação Serializable.

  • Biblioteca Java Deep Cloning - usando reflexão - nos casos em que as classes ou os objetos que você deseja clonar estão fora de seu controle (uma biblioteca de terceiros) e você não pode implementá-los Serializable, ou nos casos em que não deseja implementar Serializable.

Para clonagem superficial (clona apenas as propriedades de primeiro nível):

Omiti deliberadamente a opção "faça você mesmo" - as APIs acima fornecem um bom controle sobre o que fazer e o que não clonar (por exemplo transient, usando ou String[] ignoreProperties), portanto, não é preferível reinventar a roda.

Bozho
fonte
Obrigado Bozho, isso é valioso. E eu concordo com você sobre a opção DIY! Você já tentou a serialização de commons e / ou a lib de clonagem profunda? E os perfs?
Guillaume
Sim, usei todas as opções acima, pelos motivos acima :) Somente a biblioteca de clonagem teve alguns problemas quando os proxies CGLIB estavam envolvidos e perdeu a funcionalidade desejada, mas acho que isso deve ser corrigido agora.
Bozho 28/01/10
Ei, se minha Entidade estiver anexada e eu tiver coisas preguiçosas, o SerializationUtils verifica o banco de dados quanto às propriedades preguiçosas? Porque é isso que eu quero, e não!
Cosmin Cosmin
se você tem uma sessão ativa - sim, tem.
Bozho 12/09
@Bozho Então, você quer dizer que se todos os objetos no bean estiverem implementando serializáveis, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) fará uma cópia profunda?
hop
36

O livro de Joshua Bloch tem um capítulo inteiro intitulado "Item 10: Substituir judiciosamente o clone", no qual ele explica por que substituir o clone geralmente é uma má idéia, porque a especificação Java para ele cria muitos problemas.

Ele fornece algumas alternativas:

  • Use um padrão de fábrica no lugar de um construtor:

         public static Yum newInstance(Yum yum);
  • Use um construtor de cópia:

         public Yum(Yum yum);

Todas as classes de coleção em Java suportam o construtor de cópias (por exemplo, novo ArrayList (l);)

LeWoody
fonte
1
Acordado. No meu projeto, defini uma Copyableinterface que contém um getCopy()método. Basta usar o padrão de protótipo manualmente.
Gpampara
Bem, eu não estava perguntando sobre a interface clonável, mas como executar uma operação profunda de clone / cópia. Com um construtor ou uma fábrica, você ainda precisa criar sua nova instância a partir da sua fonte.
Guillaume
@ Guillaume Eu acho que você precisa ter cuidado ao usar as palavras clone / cópia profunda. Clonar e copiar em java NÃO significa a mesma coisa. A especificação Java tem mais a dizer sobre isso ... Eu acho que você quer uma cópia profunda do que eu posso dizer.
LeWoody 02/02/10
OK As especificações Java são precisas sobre o que é um clone ... Mas também podemos falar do clone em um significado mais comum ... Por exemplo, uma das bibliotecas recomendadas pelo bohzo é denominada 'Java Deep Cloning Library' ...
Guillaume
2
@LWoodyiii esse newInstance()método e o Yumconstrutor faria uma cópia profunda ou superficial?
28413 Geek
9

Desde a versão 2.07, o Kryo suporta clonagem superficial / profunda :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

O Kryo é rápido, na e na página deles você pode encontrar uma lista de empresas que o utilizam na produção.

Andrey Chaschev
fonte
5

Use XStream toXML / fromXML na memória. Extremamente rápido e existe há muito tempo e está forte. Os objetos não precisam ser serializáveis ​​e você não precisa usar reflexão (embora o XStream o faça). O XStream pode discernir variáveis ​​que apontam para o mesmo objeto e não acidentalmente fazem duas cópias completas da instância. Muitos detalhes como esse foram detalhados ao longo dos anos. Eu o uso há vários anos e é uma opção. É tão fácil de usar quanto você pode imaginar.

new XStream().toXML(myObj)

ou

new XStream().fromXML(myXML)

Clonar,

new XStream().fromXML(new XStream().toXML(myObj))

Mais sucintamente:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
fonte
3

Para objetos complicados e quando o desempenho não é significativo, uso o gson para serializar o objeto para o texto json e, em seguida, desserialize 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 <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}
tiboo
fonte
2

Depende.

Para velocidade, use DIY. Para à prova de balas, use reflexão.

BTW, serialização não é a mesma que refl, pois alguns objetos podem fornecer métodos de serialização substituídos (readObject / writeObject) e podem ser com erros

Yoni Roit
fonte
1
O reflexo não é à prova de balas: pode levar a uma situação em que seu objeto clonado faz referência à sua fonte ... Se a fonte mudar, o clone também muda!
Guillaume
1

Eu recomendaria a maneira DIY que, combinada com um bom método hashCode () e equals (), deve ser fácil de ser testada em um teste de unidade.

Dominik Sandjaja
fonte
bem, o preguiçoso me diverte muito ao criar um código fictício. Mas está parecendo o caminho mais sábio ...
Guillaume
2
desculpe, mas DIY é o caminho a percorrer apenas se não houver outra solução é apropriada para you..which quase nunca é
Bozho
1

Eu sugiro que substitua Object.clone (), chame super.clone () primeiro e depois chame ref = ref.clone () em todas as referências que você deseja copiar profundamente. É mais ou menos a abordagem Faça você mesmo , mas precisa de um pouco menos de codificação.

x4u
fonte
2
Esse é um dos muitos problemas do método clone (quebrado): em uma hierarquia de classes, você sempre deve chamar super.clone (), que pode ser facilmente esquecido, é por isso que eu prefiro usar um construtor de cópias.
precisa saber é o seguinte
0

Para clonagem profunda, implemente Serializable em todas as classes que você deseja clonar assim

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

E então use esta função:

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

como isso: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
fonte