2008-09-15 10 views
226

En Java es un poco difícil implementar una función de copia profunda de objetos. ¿Qué pasos debe seguir para garantizar que el objeto original y el clonado no compartan ninguna referencia?¿Cómo se hace una copia profunda de un objeto en Java?

+4

Kryo ha incorporado soporte para [copiar/clonación] (https://code.google.com/p/kryo/#Copying/cloning). Esto es copia directa de objeto a objeto, no objeto-> bytes-> objeto. – NateS

+1

Aquí hay una pregunta relacionada que se hizo más tarde: [Recomendación de Deep Clone] (http://stackoverflow.com/q/665860/152061) –

Respuesta

135

Una forma segura es serializar el objeto, luego deserializarlo. Esto asegura que todo sea una referencia nueva.

Here's an article acerca de cómo hacer esto de manera eficiente.

Advertencias: Es posible que las clases anulen la serialización de modo que las instancias nuevas sean no creadas, p. para singletons También esto, por supuesto, no funciona si tus clases no son serializables.

+5

Tenga en cuenta que la implementación FastByteArrayOutputStream proporcionada en el artículo podría ser más eficiente. Utiliza una expansión de estilo ArrayList cuando el búfer se llena, pero es mejor usar un enfoque de expansión estilo LinkedList. En lugar de crear un nuevo búfer 2x y de eliminar el búfer actual, mantenga una lista vinculada de búferes y agregue uno nuevo cuando la corriente se llene. Si recibe una solicitud para escribir más datos que los que caben en su tamaño de búfer predeterminado, cree un nodo de búfer que sea exactamente tan grande como la solicitud; los nodos no necesitan ser del mismo tamaño. –

+0

Simplemente use kryo: https://github.com/EsotericSoftware/kryo#copyingcloning benchmark http://www.slideshare.net/AlexTumanoff/serialization-and-performance – zengr

+0

Un buen artículo, que explica la copia profunda a través de la serialización: http : //www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html –

6

La copia profunda solo se puede hacer con el consentimiento de cada clase. Si tiene control sobre la jerarquía de clases, entonces puede implementar la interfaz clonable e implementar el método Clone. De lo contrario, es imposible realizar una copia en profundidad porque el objeto también puede estar compartiendo recursos que no son de datos (por ejemplo, conexiones de bases de datos). En general, sin embargo, la copia profunda se considera una mala práctica en el entorno de Java y debe evitarse mediante las prácticas de diseño apropiadas.

+1

¿Podría describir las "prácticas de diseño apropiadas"? – eftokay83

32

Puede hacer una clonación profunda basada en la serialización usando org.apache.commons.lang3.SerializationUtils.clone(T) en Commons Lang, pero tenga cuidado: el rendimiento es abismal.

En general, es una buena práctica escribir sus propios métodos de clonación para cada clase de un objeto en el gráfico de objetos que necesita clonar.

8

Utilice XStream (http://x-stream.github.io/). Incluso puede controlar qué propiedades puede ignorar a través de anotaciones o especificando explícitamente el nombre de propiedad a la clase XStream. Además, no es necesario implementar una interfaz clonable.

11

XStream es realmente útil en tales casos. Este es un código simple de hacer la clonación

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

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj)); 
+0

Nooo, no necesita la sobrecarga de xml-ing el objeto. – egelev

+0

@egeleve ¿Te das cuenta de que estás respondiendo a un comentario del '08, verdad? Ya no uso Java y probablemente haya mejores herramientas ahora. Sin embargo, en ese momento, serializar a un formato diferente y luego volver a serializar parecía un buen truco, sin duda era ineficiente. – sankara

57

Algunas personas han mencionado el uso o anulación Object.clone(). No lo hagas Object.clone() tiene algunos problemas importantes, y su uso se desaconseja en la mayoría de los casos. Consulte el artículo 11, de "Effective Java" por Joshua Bloch para obtener una respuesta completa. Creo que puedes usar Object.clone() de forma segura en matrices de tipo primitivo, pero aparte de eso, debes ser prudente sobre el uso y sobreescritura del clon.

Los esquemas que se basan en la serialización (XML u otros) son kludgy.

No hay una respuesta fácil aquí. Si quiere copiar profundamente un objeto, tendrá que recorrer el gráfico del objeto y copiar cada objeto secundario explícitamente a través del constructor de copia del objeto o un método de fábrica estático que a su vez copia profundamente el objeto secundario. Los elementos inmutables (por ejemplo, String s) no necesitan copiarse. Como un aparte, debes favorecer la inmutabilidad por este motivo.

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

Puede hacer una copia profunda con la serialización sin crear archivos.

Su objeto que desea copiar profundamente necesitará implement serializable. Si la clase no es definitiva o no se puede modificar, amplíe la clase e implemente serializable.

Convierte tu clase a un flujo de bytes:

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

restaurar su clase a partir de un flujo de bytes:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData); 
(Object) object = (Object) new ObjectInputStream(bais).readObject(); 
+4

Si la clase es final, ¿cómo la extendería? –

+1

@KumarManish class MyContainer implementa Serializable {MyFinalClass instance; ...} –

+0

Me parece una gran respuesta. Clonar es un desastre – blackbird014

22

Una forma de implementar copia profunda es añadir constructores de copia a cada uno asociado clase. Un constructor de copia toma una instancia de 'this' como su único argumento y copia todos los valores de la misma. Un poco de trabajo, pero bastante sencillo y seguro.

EDITAR: tenga en cuenta que no necesita utilizar métodos de acceso para leer los campos. Puede acceder a todos los campos directamente porque la instancia de origen siempre es del mismo tipo que la instancia con el constructor de copias. Obvio, pero podría ser pasado por alto.

Ejemplo:

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: Tenga en cuenta que al utilizar constructores de copia que necesita saber el tipo de ejecución del objeto que va a copiar. Con el enfoque anterior, no puede copiar fácilmente una lista mixta (es posible que pueda hacerlo con un código de reflexión).

+1

Solo estoy interesado en el caso de que lo que está copiando es una subclase, pero el padre lo está haciendo referencia. ¿Es posible anular el constructor de copia? –

+0

¿Por qué su clase principal se refiere a su subclase? ¿Puede dar un ejemplo? –

+1

clase pública El coche se extiende Vehículo Y luego se refiere al automóvil como un vehículo. originaList = new ArrayList ; copyList = new ArrayList ; originalList.add (coche nuevo()); para (vehículo vehículo: vehicleList) { copyList.add (vehículo nuevo (vehículo)); } –

12

Puede use a library que tiene una API sencilla, y lleva a cabo la clonación relativamente rápido con la reflexión (debe ser más rápido que los métodos de serialización).

Cloner cloner = new Cloner(); 

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

que utilizan Dozer para la clonación de objetos de Java y es genial en el que, Kryo biblioteca es otra gran alternativa.

14

Apache commons ofrece una forma rápida de clonar un objeto.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1); 
+0

upvote para sugerir una biblioteca probada y estable – katzenhut

2

BeanUtils hace un muy buen trabajo habas de clonación profundas.

BeanUtils.cloneBean(obj); 
+2

Hace la clonación superficial. –

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

Aquí su MyPerson y la clase MiDirección deben implementar la interfaz serilazable

3

Para objetos complicados y cuando el rendimiento no es significativo i utilizar una biblioteca JSON, como gson para serializar el objeto de json text, luego deserializar el texto para obtener un nuevo objeto.

gson que, en función de la reflexión, funcionará en la mayoría de los casos, excepto que los campos transient no se copiarán y los objetos con referencia circular con 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); 

} 
+1

Siga las convenciones de nomenclatura de Java por usted y por nuestro bien. –

4

Para Resorte del marco usuarios.El uso de la clase org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked") 
public static <T extends Serializable> T clone(T object) { 
    return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object)); 
} 
Cuestiones relacionadas