Java: solución recomendada para la clonación profunda / copia de una instancia

176

Me pregunto si hay una forma recomendada de hacer clones / copias profundas de instancias en Java.

Tengo 3 soluciones en mente, pero me puedo perder algunas, y me gustaría tener tu opinión

editar: incluya la propuesta de Bohzo y refine la pregunta: se trata más de la clonación profunda que de la clonación superficial.

Hazlo tu mismo:

codifique las propiedades del clon a mano después de las propiedades y compruebe que las instancias mutables también se clonen.
pro:
- control de lo que se realizará
-
contras de ejecución rápida :
- tedioso de escribir y mantener
- propenso a errores (falla de copiar / pegar, propiedad faltante, propiedad mutable reasignada)

Usar reflexión:

Con sus propias herramientas de reflexión o con un ayudante externo (como jakarta common-beans), es fácil escribir un método de copia genérico que haga el trabajo en una línea.
pro:
- fácil de escribir
- sin
contras de mantenimiento :
- menos control de lo que sucede
- propenso a errores con objeto mutable si la herramienta de reflexión tampoco clona subobjetos
- ejecución más lenta

Use el marco de clonación:

Use un marco que lo haga por usted, como:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- igual que la reflexión
- más control sobre lo que se clonará exactamente.
contras:
- cada instancia mutable está completamente clonada, incluso al final de la jerarquía
- podría ser muy lenta de ejecutar

Utilice la instrumentación de bytecode para escribir clon en tiempo de ejecución

Se puede usar javassit , BCEL o cglib para generar un clonador dedicado tan rápido como escribir con una mano. ¿Alguien conoce una biblioteca que usa una de estas herramientas para este propósito?

¿Qué me he perdido aquí?
Cuál recomendarías ?

Gracias.

Guillaume
fuente
1
aparentemente Java Deep Cloning Library se mudó aquí: code.google.com/p/cloning
Mr_and_Mrs_D

Respuestas:

155

Para la clonación profunda (clona toda la jerarquía de objetos):

  • commons-lang SerializationUtils - usando serialización - si todas las clases están bajo su control y puede forzar la implementación Serializable.

  • Biblioteca de clonación profunda de Java , utilizando la reflexión, en los casos en que las clases o los objetos que desea clonar están fuera de su control (una biblioteca de terceros) y no puede hacer que se implementen Serializable, o en los casos en que no desea implementar Serializable.

Para la clonación superficial (clona solo las propiedades de primer nivel):

Deliberadamente omití la opción "hágalo usted mismo": las API anteriores proporcionan un buen control sobre qué hacer y qué no clonar (por ejemplo transient, usando , o String[] ignoreProperties), por lo que no es preferible reinventar la rueda.

Bozho
fuente
Gracias Bozho, eso es valioso. ¡Y estoy de acuerdo contigo sobre la opción de bricolaje! ¿Alguna vez has probado la serialización de bienes comunes y / o la biblioteca de clonación profunda? ¿Qué pasa con los perfs?
Guillaume
sí, he usado todas las opciones anteriores, por las razones anteriores :) solo la biblioteca de clonación tenía algunos problemas cuando los servidores proxy CGLIB estaban involucrados, y perdía algunas funciones deseadas, pero creo que eso debería solucionarse ahora.
Bozho
Oye, si mi entidad está conectada y tengo cosas vagas, ¿SerializationUtils comprueba la base de datos para ver las propiedades vagas? ¡Porque esto es lo que quiero, y no lo hace!
Cosmin Cosmin
si tiene una sesión activa, sí, la tiene.
Bozho
@Bozho Entonces, ¿quiere decir que si todos los objetos dentro del bean están implementando serializable, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) hará una copia profunda?
salta
36

El libro de Joshua Bloch tiene un capítulo completo titulado "Punto 10: Anular el clon juiciosamente" en el que explica por qué anular el clon en su mayor parte es una mala idea porque la especificación de Java crea muchos problemas.

Él ofrece algunas alternativas:

  • Use un patrón de fábrica en lugar de un constructor:

         public static Yum newInstance(Yum yum);
  • Use un constructor de copia:

         public Yum(Yum yum);

Todas las clases de colección en Java son compatibles con el constructor de copia (por ejemplo, new ArrayList (l);)

LeWoody
fuente
1
Convenido. En mi proyecto definí una Copyableinterfaz que contiene un getCopy()método. Simplemente use el patrón prototipo manualmente.
gpampara
Bueno, no estaba preguntando acerca de la interfaz clonable, sino cómo realizar una operación de copia / clonación profunda. Con un constructor o una fábrica, aún necesita crear su nueva instancia desde su fuente.
Guillaume
@Guillaume Creo que debes tener cuidado al usar las palabras clon / copia profunda. Clonar y copiar en Java NO significan lo mismo. La especificación de Java tiene más que decir sobre esto ... Creo que quieres una copia profunda de lo que puedo decir.
LeWoody
OK La especificación de Java es precisa sobre qué es un clon ... Pero también podemos hablar del clon en un significado más común ... Por ejemplo, una de las bibliotecas recomendadas por bohzo se llama 'Java Deep Cloning Library' ...
Guillaume
2
@LWoodyiii este newInstance()método y el Yumconstructor harían una copia profunda o una copia superficial?
Geek
9

Desde la versión 2.07, Kryo admite la clonación superficial / profunda :

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

Kryo es rápido, en y de su página puede encontrar una lista de compañías que lo usan en producción.

Andrey Chaschev
fuente
5

Use XStream toXML / fromXML en la memoria. Extremadamente rápido y ha existido durante mucho tiempo y se está fortaleciendo. Los objetos no necesitan ser serializables y no debe usar la reflexión (aunque XStream sí). XStream puede discernir variables que apuntan al mismo objeto y no hacer accidentalmente dos copias completas de la instancia. Muchos detalles como ese han sido elaborados a lo largo de los años. Lo he usado durante varios años y es una opción. Es tan fácil de usar como te puedas imaginar.

new XStream().toXML(myObj)

o

new XStream().fromXML(myXML)

Para clonar,

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

Más sucintamente:

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

Para objetos complicados y cuando el rendimiento no es significativo, uso gson para serializar el objeto en texto json, luego deserializar el texto para obtener un nuevo objeto.

gson que basado en la reflexión funcionará en la mayoría de los casos, excepto que los transientcampos no se copiarán y los objetos con referencia circular con 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
fuente
2

Depende

Para mayor velocidad, usa bricolaje. Para a prueba de balas, use la reflexión.

Por cierto, la serialización no es lo mismo que refl, ya que algunos objetos pueden proporcionar métodos de serialización anulados (readObject / writeObject) y pueden tener errores.

Yoni Roit
fuente
1
la reflexión no es a prueba de balas: puede conducir en alguna situación en la que su objeto clonado tiene referencia a su fuente ... ¡Si la fuente cambia, el clon también cambiará!
Guillaume
1

Recomendaría la forma de bricolaje que, combinada con un buen método hashCode () y equals () debería ser fácil de probar en una prueba unitaria.

Dominik Sandjaja
fuente
bueno, el vago me critica mucho al crear un código tan falso. Pero parece el camino más sabio ...
Guillaume
2
lo siento, pero el bricolaje es el camino a seguir SOLO si no hay otra solución adecuada para usted ... que casi nunca es
Bozho
1

Sugeriría anular Object.clone (), llamar primero a super.clone () y luego llamar a ref = ref.clone () en todas las referencias que desee copiar en profundidad. Es más o menos el enfoque Hágalo usted mismo , pero necesita un poco menos de codificación.

x4u
fuente
2
Ese es uno de los muchos problemas del método de clonación (roto): en una jerarquía de clases siempre debe llamar a super.clone (), que puede olvidarse fácilmente, por eso preferiría usar un constructor de copias.
helpermethod
0

Para una clonación profunda, implemente Serializable en cada clase que desee clonar de esta manera

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

Y luego usa esta función:

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

Me gusta esto: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
fuente