Como copiar um objeto em Java?

794

Considere o código abaixo:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

Então, eu quero copiar o dumpara dumtwoe alterar dumsem afetar o dumtwo. Mas o código acima não está fazendo isso. Quando troco algo dum, a mesma mudança também está acontecendo dumtwo.

Acho que, quando digo dumtwo = dum, Java copia apenas a referência . Então, existe alguma maneira de criar uma nova cópia dume atribuí-la dumtwo?

Veera
fonte

Respostas:

611

Crie um construtor de cópia:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

Todo objeto também possui um método clone que pode ser usado para copiar o objeto, mas não o utiliza. É muito fácil criar uma classe e fazer um método de clone inadequado. Se você fizer isso, leia pelo menos o que Joshua Bloch tem a dizer sobre isso em Java eficaz .

egaga
fonte
45
Mas então ele teria que mudar seu código para DummyBean two = new DummyBean (one); Direita?
Chris K
12
Esse método efetivamente realiza a mesma coisa que uma cópia profunda?
Matthew Piziak
124
@MatthewPiziak, para mim - este não seria um clone profundo, pois qualquer objeto aninhado ainda faria referência à instância original de origem, não uma duplicata, a menos que cada objeto de referência (sem valor) forneça o mesmo modelo de construtor que acima.
SliverNinja - MSFT
17
@ Timmmm: Sim, eles farão referência à mesma String, mas porque é imutável, está tudo bem. O mesmo vale para os primitivos. Para os não-primitivos, basta copiar a chamada do contratador recursivamente. por exemplo, se o DummyBean referenciou o FooBar, o FooBar deveria ter o contrator FooBar (FooBar outro), e o dummy deveria chamar this.foobar = new FooBar (another.foobar)
egaga
7
@ChristianVielma: Não, não será "johndoe". Como Timmmm disse, a corda em si é imutável. Com um, setDummy (..), você define a referência em um para apontar para "johndoe", mas não o em um.
keuleJ
404

Básico: cópia de objeto em Java.

Vamos supor uma object- obj1, que contém dois objetos, containedObj1 e containedObj2 .
insira a descrição da imagem aqui

cópia superficial: a cópia
superficial cria um novo instanceda mesma classe e copia todos os campos para a nova instância e o retorna. A classe Object fornece um clonemétodo e fornece suporte para a cópia superficial.
insira a descrição da imagem aqui

Cópia profunda:
uma cópia profunda ocorre quando um objeto é copiado junto com os objetos aos quais se refere . A imagem abaixo é exibida obj1após uma cópia profunda ter sido realizada nela. Não apenas obj1foi copiado , mas os objetos contidos nele também foram copiados. Podemos usar Java Object Serializationpara fazer uma cópia profunda. Infelizmente, essa abordagem também tem alguns problemas ( exemplos detalhados ).
insira a descrição da imagem aqui

Possíveis problemas:
clone é difícil de implementar corretamente.
É melhor usar cópias defensivas , construtores de cópias (como resposta do @egaga) ou métodos estáticos de fábrica .

  1. Se você possui um objeto que você conhece que possui um clone()método público , mas não sabe o tipo do objeto no momento da compilação, então você tem um problema. Java tem uma interface chamada Cloneable. Na prática, devemos implementar essa interface se quisermos criar um objeto Cloneable. Object.cloneestá protegido , então devemos substituí- lo por um método público para que ele seja acessível.
  2. Outro problema surge quando tentamos copiar profundamente um objeto complexo . Suponha que o clone()método de todas as variáveis ​​de objeto membro também faça cópias profundas, isso é muito arriscado. Você deve controlar o código em todas as classes.

Por exemplo, org.apache.commons.lang.SerializationUtils terá um método para o clone Profundo usando serialização ( Origem ). Se precisarmos clonar o Bean, existem alguns métodos de utilitário em org.apache.commons.beanutils ( Origem ).

  • cloneBean Clonará um bean com base nos getters e setters de propriedades disponíveis, mesmo que a própria classe de bean não implemente Cloneable.
  • copyProperties Copiará os valores da propriedade do bean de origem para o bean de destino em todos os casos em que os nomes das propriedades sejam os mesmos.
Chandra Sekhar
fonte
1
Você pode, por favor, explicar qual é o objeto contido em outro?
Freakyuser
1
@Chandra Sekhar "a cópia superficial cria uma nova instância da mesma classe e copia todos os campos para a nova instância e a retorna" é errado mencionar todos os campos, os objetos bz não são copiados, apenas as referências são copiadas, o que aponta para o mesmo objeto que o antigo (original) estava apontando.
JAVA
4
@sunny - A descrição de Chandra está correta. E assim é a sua descrição do que acontece; Estou dizendo que você tem um entendimento incorreto do significado de "copia todos os campos". O campo é a referência, não é o objeto que está sendo referido. "copiar todos os campos" significa "copiar todas essas referências". É bom que você tenha apontado o que exatamente isso significa, para qualquer pessoa que tenha a mesma má interpretação que você, da declaração "copiando todos os campos". :)
ToolmakerSteve
2
... se pensarmos em termos de alguma linguagem OO de nível inferior, com "ponteiros" para objetos, esse campo conterá o endereço na memória (como "0x70FF1234") no qual os dados do objeto são encontrados. Esse endereço é o "valor do campo" que está sendo copiado (atribuído). Você está certo de que o resultado final é que ambos os objetos têm campos que se referem ao (apontar para) o mesmo objeto.
ToolmakerSteve
127

No pacote import org.apache.commons.lang.SerializationUtils;existe um método:

SerializationUtils.clone(Object);

Exemplo:

this.myObjectCloned = SerializationUtils.clone(this.object);
pacheco
fonte
59
Desde que o objeto seja implementadoSerializable
Androiderson
2
Nesse caso, o objeto clonado não faz referência ao original, se o último for estático.
Dante
8
Uma biblioteca de terceiros apenas para clonar objetos!
Khan
2
@ Khan, "uma biblioteca de terceiros apenas para" é uma discussão totalmente separada! : D
Charles Wood
103

Basta seguir como abaixo:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

e onde você quiser obter outro objeto, faça uma clonagem simples. por exemplo:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object
Bhasker Tiwari
fonte
1
Você testou isso? Eu poderia usar isso no meu projeto e é importante estar correto.
enevoada
2
@ misty eu testei. Funciona perfeitamente no meu aplicativo de produção
Andrii Kovalchuk
Após a clonagem, quando você modifica o objeto original, ele também está modificando o clone.
Sibish
4
Isso está errado, pois não foi solicitada uma cópia em profundidade.
Bluehorn
1
Esse método clona o ponteiro que aponta para o objeto clonável, mas todas as propriedades em ambos os objetos são iguais. Portanto, há um novo objeto criado na memória, mas os dados dentro de cada objeto são os mesmos da memória
Omar HossamEldin
40

Por que não há resposta para o uso da API de reflexão?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

É realmente simples.

EDIT: Incluir objeto filho via recursão

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }
WillingLearner
fonte
Isso parece muito melhor, mas você só precisa considerar os campos finais, pois setAccessible (true) pode falhar; portanto, talvez você precise manipular separadamente a exceção IllegalAccessException lançada ao chamar field.set (clone, field.get (obj)) separadamente.
Max
1
Eu gostei muito, mas você pode refatorá-lo para usar genéricos? <T> estático privado <T> T cloneObject (T obj) {....}
Adelin
2
Eu acho que é problema quando temos referência de propriedades para que os pais: Class A { B child; } Class B{ A parent; }
nhthai
Ele falha mesmo nessa situação, precisa ser tratado, vou brincar com isso amanhã. class car { car car = new car(); }
Ján Яabčan
2
Isso é propenso a erros. Não sei como ele vai lidar com coleções
ACV
31

Eu uso a biblioteca JSON do Google para serializá-la e, em seguida, criar uma nova instância do objeto serializado. Ele faz cópias profundas com algumas restrições:

  • não pode haver referências recursivas

  • não copiará matrizes de tipos diferentes

  • matrizes e listas devem ser digitadas ou não encontrarão a classe para instanciar

  • pode ser necessário encapsular seqüências de caracteres em uma classe que você se declara

Eu também uso essa classe para salvar preferências do usuário, janelas e outros itens a serem recarregados em tempo de execução. É muito fácil de usar e eficaz.

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}
Pedro
fonte
Isso funciona muito bem. Mas tome cuidado se você tentar clonar algo como List <Integer>. Será buggy, meus números inteiros foram transformados em duplas, 100.0. Levei muito tempo para entender por que eles são assim. A solução foi clonar os números inteiros um por um e adicionar à lista em um ciclo.
paakjis
24

Sim, você está apenas fazendo uma referência ao objeto. Você pode clonar o objeto se ele for implementado Cloneable.

Confira este artigo da wiki sobre cópia de objetos.

Consulte aqui: Cópia de objeto

Chrisb
fonte
14

Adicione Cloneablee abaixo do código à sua turma

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Usa isto clonedObject = (YourClass) yourClassObject.clone();

Teja Maridu
fonte
12

Sim. Você precisa copiar profundamente seu objeto.

bruno conde
fonte
1
Como é, nem sequer é uma cópia.
Michael Myers
Esta é provavelmente a resposta menos útil que eu já vi no stackoverflow.
Cyril
12

Isso também funciona. Assumindo modelo

class UserAccount{
   public int id;
   public String name;
}

Primeiro, adicione compile 'com.google.code.gson:gson:2.8.1'ao seu aplicativo> classificação e sincronização. Então

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

Você pode excluir o uso de um campo usando a transientpalavra-chave após o modificador de acesso.

Nota: Esta é uma prática ruim. Também não recomendamos o uso Cloneableou JavaSerializationestá lento e quebrado. Escreva um construtor de cópias para obter o melhor desempenho ref .

Algo como

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

Estatísticas de teste da iteração 90000:
Linha UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);leva 808ms

A linha UserAccount clone = new UserAccount(aO);leva menos de 1 ms

Conclusão: use gson se seu chefe estiver louco e você preferir velocidade. Use o construtor de segunda cópia, se preferir qualidade.

Você também pode usar o plug-in do gerador de código do construtor de cópia no Android Studio.

Qamar
fonte
Por que você sugeriu se é uma prática ruim?
Parth Mehrotra 22/09
Obrigado @ParthMehrotra agora melhorado
Qamar
9

Use um utilitário de clonagem profunda:

SomeObjectType copy = new Cloner().deepClone(someObject);

Isso irá copiar profundamente qualquer objeto java, confira em https://github.com/kostaskougios/cloning

Cojones
fonte
1
não funcionou para mim usando uma classe personalizada. recebendo a seguinte exceção: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker
9

A Deep Cloning é sua resposta, que requer a implementação da Cloneableinterface e a substituição do clone()método.

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

Você vai chamar assim DummyBean dumtwo = dum.clone();

abbas
fonte
2
dummy, Um String, é imutável, você não precisa copiá-lo
Steve Kuo
7

Para fazer isso, você precisa clonar o objeto de alguma forma. Embora o Java tenha um mecanismo de clonagem, não o use se não for necessário. Crie um método de cópia que a cópia funcione para você e faça:

dumtwo = dum.copy();

Aqui estão mais alguns conselhos sobre diferentes técnicas para realizar uma cópia.

Yishai
fonte
6

Além de copiar explicitamente, outra abordagem é tornar o objeto imutável (nenhum setou outro método de mutação). Dessa maneira, a questão nunca surge. A imutabilidade se torna mais difícil com objetos maiores, mas o outro lado disso é que ele o leva na direção da divisão em objetos pequenos e compostos coerentes.

Tom Hawtin - linha de orientação
fonte
5

Alternativa ao método construtor de cópia do egaga . Você provavelmente já possui um POJO; portanto, basta adicionar outro método copy()que retorne uma cópia do objeto inicializado.

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

Se você já possui DummyBeane deseja uma cópia:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

Resultado:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Mas ambos funcionam bem, em última análise, cabe a você ...

Pierre
fonte
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Mahdi Abdi
fonte
3

Você pode copiar em profundidade automaticamente automaticamente com o XStream, em http://x-stream.github.io/ :

XStream é uma biblioteca simples para serializar objetos para XML e vice-versa.

Adicione-o ao seu projeto (se estiver usando o maven)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

Então

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

Com isso, você tem uma cópia sem a necessidade de implementar nenhuma interface de clonagem.

Jaime Hablutzel
fonte
29
A conversão de / para XML não parece muito ... elegante. Para dizer o mínimo!
Timmmm 27/01/12
Veja também uma java.beans.XMLEncoderAPI Java padrão que serializa para XML (embora não exatamente para fins de cópia em profundidade).
Jaime Hablutzel
1
você percebe o quão pesado isso é?
Mahieddine
1
Na minha opinião, há muita sobrecarga, já que você precisa adicionar uma biblioteca de terceiros e fazer a serialização de objetos, o que provavelmente tem um enorme impacto no desempenho.
NiThDi
2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

e no seu código:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
Amir Hossein Ghasemi
fonte
2
Não há nenhum ponto no conjunto "lança CloneNotSupportedException" na declaração se você tentar capturar a exceção e não for exibido. Então, você pode simplesmente removê-lo.
Christian
2

Passe o objeto que você deseja copiar e obtenha o objeto que deseja:

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

Agora analise o objDestobjeto desejado.

Feliz codificação!

A-Droid Tech
fonte
1

Você pode tentar implementar Cloneablee usar o clone()método; no entanto, se você usar o método clone, sempre deverá substituir Objecto public Object clone()método SEMPRE por padrão .


fonte
1

Se você pode adicionar uma anotação ao arquivo de origem, um processador de anotação ou gerador de código como este pode ser usado.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Uma classe DummyBeanBuildersserá gerada, que possui um método estático dummyBeanUpdaterpara criar cópias rasas, da mesma maneira que você faria manualmente.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();
Lars Bohl
fonte
0

Use gsonpara duplicar um objeto.

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

Suponha que eu tenha um objeto person.

Person copyPerson = copyObject(person);
tuhin47
fonte