¿Cómo copio un objeto en Java?


794

Considere el siguiente código:

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'

Por lo tanto, quiero copiar el dumque dumtwoy el cambio dumsin afectar al dumtwo. Pero el código anterior no está haciendo eso. Cuando cambio algo dum, dumtwotambién está ocurriendo el mismo cambio .

Supongo que, cuando digo dumtwo = dum, Java copia solo la referencia . Entonces, ¿hay alguna forma de crear una copia nueva dumy asignarla dumtwo?

Respuestas:


611

Crea un constructor de copia:

class DummyBean {
  private String dummy;

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

Cada objeto también tiene un método de clonación que se puede usar para copiar el objeto, pero no lo use. Es demasiado fácil crear una clase y hacer un método de clonación incorrecto. Si va a hacer eso, lea al menos lo que Joshua Bloch tiene que decir al respecto en Effective Java .


45
Pero luego tendría que cambiar su código a DummyBean two = new DummyBean (uno); ¿Derecha?
Chris K

12
¿Este método logra efectivamente lo mismo que una copia profunda?
Matthew Piziak

124
@MatthewPiziak, para mí, esto no sería un clon profundo ya que los objetos anidados seguirían haciendo referencia a la instancia de origen original, no un duplicado a menos que cada objeto de referencia (tipo sin valor) proporcione la misma plantilla de constructor que la anterior.
SliverNinja - MSFT

17
@Timmmm: Sí, harán referencia a la misma cadena, pero como es inmutable, está bien. Lo mismo vale para los primitivos. Para los no primitivos, simplemente haría una copia contructora de la llamada de forma recursiva. Por ejemplo, si DummyBean hizo referencia a FooBar, entonces FooBar debería tener el constructor FooBar (FooBar otro), y dummy debería llamar this.foobar = new FooBar (another.foobar)
egaga

77
@ChristianVielma: No, no será "johndoe". Como dijo Timmmm, la cadena en sí es inmutable. Con uno, setDummy (..) establece la referencia en uno para que apunte a "johndoe", pero no el uno en uno.
keuleJ

404

Básico: Copia de objetos en Java.

Supongamos que un objeto obj1, que contiene dos objetos, contieneObj1 y contenidoObj2 .
ingrese la descripción de la imagen aquí

copia superficial: la copia
superficial crea una nueva instanceclase de la misma clase y copia todos los campos a la nueva instancia y la devuelve. La clase de objeto proporciona un clonemétodo y proporciona soporte para la copia superficial.
ingrese la descripción de la imagen aquí


Copia profunda : una copia profunda ocurre cuando un objeto se copia junto con los objetos a los que se refiere . La imagen a continuación se muestra obj1después de que se haya realizado una copia profunda. No solo se obj1ha copiado , sino que también se han copiado los objetos que contiene. Podemos usar Java Object Serializationpara hacer una copia profunda. Desafortunadamente, este enfoque también tiene algunos problemas ( ejemplos detallados ).
ingrese la descripción de la imagen aquí

Posibles problemas:
clone es difícil de implementar correctamente.
Es mejor usar copias defensivas , constructores de copias (como @egaga reply) o métodos estáticos de fábrica .

  1. Si tiene un objeto, sabe que tiene un clone()método público , pero no conoce el tipo de objeto en tiempo de compilación, entonces tiene un problema. Java tiene una interfaz llamada Cloneable. En la práctica, debemos implementar esta interfaz si queremos hacer un objeto Cloneable. Object.cloneestá protegido , por lo que debemos anularlo con un método público para que sea accesible.
  2. Otro problema surge cuando intentamos copiar en profundidad un objeto complejo . Suponga que el clone()método de todas las variables de objeto miembro también hace una copia profunda, esto es demasiado arriesgado de suponer. Debe controlar el código en todas las clases.

Por ejemplo, org.apache.commons.lang.SerializationUtils tendrá un método para clonar en profundidad usando la serialización ( Fuente ). Si necesitamos clonar Bean, entonces hay un par de métodos de utilidad en org.apache.commons.beanutils ( Fuente ).

  • cloneBean clonará un bean en función de los captadores y establecedores de propiedades disponibles, incluso si la clase de bean no implementa Cloneable.
  • copyProperties copiará los valores de propiedad del bean de origen al bean de destino para todos los casos en que los nombres de propiedad sean los mismos.

1
¿Puede explicar qué es el objeto contenido dentro de otro?
Freakyuser

1
@Chandra Sekhar "la copia superficial crea una nueva instancia de la misma clase y copia todos los campos a la nueva instancia y la devuelve" está mal mencionar todos los campos, los objetos bcz no se copian solo las referencias que se copian a las que apunta el mismo objeto al que apuntaba el anterior (original).
JAVA

44
@sunny: la descripción de Chandra es correcta. Y también lo es tu descripción de lo que sucede; Estoy diciendo que tiene una comprensión incorrecta del significado de "copia todos los campos". El campo es la referencia, no es el objeto al que se hace referencia. "copiar todos los campos" significa "copiar todas esas referencias". Es bueno que haya señalado qué significa exactamente esto, para cualquier otra persona que tenga la misma interpretación errónea que usted, de la declaración "copiar todos los campos". :)
ToolmakerSteve

2
... si pensamos en términos de un lenguaje OO de nivel inferior, con "punteros" a los objetos, dicho campo contendría la dirección en la memoria (como "0x70FF1234") en la que se encuentran los datos del objeto. Esa dirección es el "valor de campo" que se está copiando (asignado). Tiene razón en que el resultado final es que ambos objetos tienen campos que hacen referencia (apuntan) al mismo objeto.
ToolmakerSteve

127

En el paquete import org.apache.commons.lang.SerializationUtils;hay un método:

SerializationUtils.clone(Object);

Ejemplo:

this.myObjectCloned = SerializationUtils.clone(this.object);

59
Mientras el objeto se implementeSerializable
Androiderson

2
En este caso, el objeto clonado no tiene referencia al original, si el último es estático.
Dante

8
¡Una biblioteca de terceros solo para clonar objetos!
Khan

2
@Khan, "una biblioteca de terceros solo para" es una discusión completamente separada. : D
Charles Wood

103

Solo sigue lo siguiente:

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

y donde quiera obtener otro objeto, simplemente realice la clonación. p.ej:

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

1
¿Probaste esto? Podría usar esto para mi proyecto y es importante ser correcto.
misty

2
@ misty lo he probado. Funciona perfectamente en mi aplicación de producción
Andrii Kovalchuk

Después de la clonación, cuando modifica el objeto original, también está modificando el clon.
Sibish

44
Esto es incorrecto, ya que es no una copia profunda que se pidió.
Bluehorn el

1
Este método clona el puntero que apunta al objeto que se puede clonar, pero todas las propiedades dentro de ambos objetos son iguales, por lo que hay un nuevo objeto creado en la memoria, pero los datos dentro de cada objeto son los mismos datos de la memoria
Omar HossamEldin

40

¿Por qué no hay una respuesta para usar Reflection API?

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

Es muy simple

EDITAR: Incluir objeto hijo a través de recursión

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

Esto se ve mucho mejor, pero solo necesita considerar los campos finales ya que setAccessible (true) puede fallar, por lo que tal vez necesite manejar por separado la excepción IllegalAccessException lanzada al llamar a field.set (clone, field.get (obj)) por separado.
Max

1
Me gustó mucho, pero ¿puedes refactorizarlo para usar genéricos? Private static <T> T cloneObject (T obj) {....}
Adelin

2
Creo que es problema cuando tenemos referencia de las propiedades a los padres que: Class A { B child; } Class B{ A parent; }
nhthai

Falla incluso en esta situación, necesita ser manejado, jugaré con él mañana. class car { car car = new car(); }
Ján Яabčan

2
Esto es propenso a errores. No estoy seguro de cómo manejará las colecciones
ACV

31

Utilizo la biblioteca JSON de Google para serializarla y luego crear una nueva instancia del objeto serializado. Hace una copia profunda con algunas restricciones:

  • no puede haber referencias recursivas

  • no copiará matrices de tipos dispares

  • las matrices y las listas deben escribirse o no encontrará la clase para instanciar

  • es posible que necesite encapsular cadenas en una clase que declare

También uso esta clase para guardar las preferencias del usuario, las ventanas y otras cosas que se deben volver a cargar en tiempo de ejecución. Es muy fácil de usar y efectivo.

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

Esto funciona muy bien. Pero ten cuidado si intentas clonar algo como List <Integer>. Será con errores, mis números enteros se convirtieron en dobles, 100.0. Me llevó mucho tiempo entender por qué son así. La solución fue clonar números enteros uno por uno y agregarlos a la lista en un ciclo.
paakjis


14

Agrega Cloneabley debajo del código a tu clase

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

Utilizar este clonedObject = (YourClass) yourClassObject.clone();



12

Esto tambien funciona. Asumiendo modelo

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

Primero agregue compile 'com.google.code.gson:gson:2.8.1'a su aplicación> gradle & sync. Entonces

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

Puede excluir el uso de un campo utilizando la transientpalabra clave después del modificador de acceso.

Nota: esta es una mala práctica. Tampoco recomiendo usar Cloneableo JavaSerializationes lento y roto. Escriba el constructor de copias para obtener el mejor rendimiento 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);
        }
    }

Estadísticas de prueba de iteración 90000: la
línea UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);tarda 808 ms

La línea UserAccount clone = new UserAccount(aO);tarda menos de 1 ms

Conclusión: usa gson si tu jefe está loco y prefieres la velocidad. Use el segundo constructor de copias si prefiere la calidad.

También puede usar el complemento generador de código de constructor de copia en Android Studio.


¿Por qué lo sugirió si es una mala práctica?
Parth Mehrotra

Gracias @ParthMehrotra ahora mejorado
Qamar


9

Use una utilidad de clonación profunda:

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

Esto copiará en profundidad cualquier objeto Java, échale un vistazo en https://github.com/kostaskougios/cloning


1
no funcionó para mí usando una clase personalizada. obteniendo la siguiente excepción: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker

9

Deep Cloning es su respuesta, que requiere implementar la Cloneableinterfaz y anular el 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;
   }
}

Lo llamarás así DummyBean dumtwo = dum.clone();


2
dummy, a String, es inmutable, no necesita copiarlo
Steve Kuo

7

Para hacer eso tienes que clonar el objeto de alguna manera. Aunque Java tiene un mecanismo de clonación, no lo use si no es necesario. Cree un método de copia que haga la copia por usted y luego haga:

dumtwo = dum.copy();

Aquí hay más consejos sobre diferentes técnicas para realizar una copia.


6

Además de copiar explícitamente, otro enfoque es hacer que el objeto sea inmutable (ningún setmétodo mutante u otro). De esta manera, la pregunta nunca surge. La inmutabilidad se vuelve más difícil con objetos más grandes, pero ese otro lado de eso es que te empuja en la dirección de dividirte en pequeños objetos coherentes y compuestos.


5

Alternativa al método de copia del constructor de egaga . Probablemente ya tenga un POJO, así que simplemente agregue otro método copy()que devuelva una copia del 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
}

Si ya tiene una DummyBeany quiere una copia:

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

Salida:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Pero ambos funcionan bien, en última instancia depende de usted ...


3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}

3

Puede copiar en profundidad automáticamente con XStream, desde http://x-stream.github.io/ :

XStream es una biblioteca simple para serializar objetos a XML y viceversa.

Agréguelo a su proyecto (si usa maven)

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

Entonces

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

Con esto, tiene una copia sin la necesidad de implementar ninguna interfaz de clonación.


29
Convertir a / desde XML no parece muy ... elegante. ¡Por decirlo suavemente!
Timmmm

Eche un vistazo a java.beans.XMLEncoderuna API Java estándar que también serialice a XML (aunque no precisamente para propósitos de copia profunda).
Jaime Hablutzel

1
¿Te das cuenta de lo pesado que es esto?
mahieddine

1
En mi opinión, es una gran carga, ya que necesita agregar una biblioteca de terceros y hacer la serialización de objetos que probablemente tenga un gran impacto en el rendimiento.
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();
   }

  }
}

y en tu código:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
No tiene sentido en el conjunto "arroja CloneNotSupportedException" en la declaración si intenta detectar la excepción y no se produce. Entonces, puedes eliminarlo.
Christian

2

Pase el objeto que desea copiar y obtenga el objeto que desea:

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;

    }

Ahora analiza el objDest objeto deseado.

¡Feliz codificación!


1

Puede intentar implementar Cloneabley usar el clone()método; sin embargo, si usa el método de clonación, debería, por norma, SIEMPRE anular Objectel public Object clone()método.


1

Si puede agregar una anotación al archivo fuente, se puede usar un procesador de anotación o un generador de código como este .

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Se DummyBeanBuildersgenerará una clase , que tiene un método estático dummyBeanUpdaterpara crear copias superficiales, de la misma manera que lo haría manualmente.

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

0

Úselo gsonpara duplicar un 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());
}

Supongamos que tengo un objeto person.

Person copyPerson = copyObject(person);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.