2011-03-01 9 views
5

Estoy escribiendo un método de clonación personalizado para cada entidad. para una copia profunda, hay una manera de detectar referencias circulares o tengo que resolverlo manualmente y restringir la clonación para que sea unidireccional en lugar de bidireccional.Java detectar referencias circulares durante la clonación personalizada

Por ejemplo, usamos hibernate y, por lo tanto, un objeto User tiene una referencia a Address and Address tiene una referencia al usuario. Intentando ver si es posible hacer una copia profunda de la dirección y del usuario sin tener que ejecutar los problemas de referencia circular

+0

Para manejar referencias circulares se puede usar un IdentityMap. Esto hace un seguimiento de cada objeto que encuentra y cuando serializa o copia datos, puede usar esto para asegurarse de que maneja los objetos repetidos correctamente. p.ej. es posible que tenga el mismo objeto muchas veces en una estructura y no desee convertirlos en objetos diferentes. –

Respuesta

7

Para implementar esto, necesita un Mapa de referencias a los objetos ya clonados. Implementamos profunda clon algo como esto:

En nuestra clase base entidad:

public void deepClone() { 
    Map<EntityBase,EntityBase> alreadyCloned = 
     new IdentityHashMap<EntityBase,EntityBase>(); 
    return deepClone(this,alreadyCloned); 
} 

private static EntityBase deepClone(EntityBase entity, 
            Map<EntityBase,EntityBase> alreadyCloned) { 
    EntityBase clone = alreadyCloned.get(entity); 
    if(clone != null) { 
     return alreadyClonedEntity; 
    } 
    clone = newInstance(entity.getClass); 
    alreadyCloned.put(this,clone); 
    // fill clone's attributes from original entity. Call 
    // deepClone(entity,alreadyCloned) 
    // recursively for each entity valued object. 
    ... 
} 
+1

HashMap es una idea terrible para usar en tal caso (ambos pueden ser falsos y es lento). IdentityHashMap es mucho mejor. – bestsss

+0

+1 Gran comentario. Actualicé mi respuesta y arreglé mi propio código :) Pero tengo que admitir que HashMap no es lento. En mi humilde opinión, es increíblemente rápido. – Daniel

+0

La diferencia de lentitud entre HashMap e IdentityHashMap depende de las implementaciones .hashCode y .equals de las claves. Si estos son malos, HashMap se vuelve lento (y en este algoritmo puede obtener resultados incorrectos, como fusionar diferentes objetos porque son iguales). –

1

@ Daniel: Muchas gracias por esta gran respuesta!

Acabo de utilizar su código y lo modifiqué un poco en función de mis necesidades, lo que hace que sea más fácil de usar con subclases. Tal vez alguien está interesado en que también así que aquí está mi código para la clase base:

/** 
* Perform a deep clone. 
* 
* @return Deep Clone. 
* @throws CloneNotSupportedException 
*/ 
@SuppressWarnings("unchecked") 
public VersionedEntityImpl deepClone() throws CloneNotSupportedException { 
    Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned = new IdentityHashMap<VersionedEntityImpl, VersionedEntityImpl>(); 
    return deepClone(this, alreadyCloned); 
} 

/** 
* Perform a deep clone. 
* 
* @param entity 
* @param alreadyCloned 
* @return Deep Clone. 
* @throws CloneNotSupportedException 
*/ 
@SuppressWarnings("unchecked") 
protected VersionedEntityImpl deepClone(VersionedEntityImpl entity, 
     Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException { 
    if (entity != null) { 
     VersionedEntityImpl clone = alreadyCloned.get(entity); 
     if (clone != null) { 
      return clone; 
     } 
     clone = entity.clone(); 
     alreadyCloned.put(entity, clone); 
     return entity.deepCloneEntity(clone, alreadyCloned); 
    } 
    return null; 
} 

/** 
* Method performing a deep clone of an entity (circles are eliminated automatically). Calls 
* deepClone(entity,alreadyCloned) recursively for each entity valued object. 
* 
* @param clone 
* @param alreadyCloned 
* @return clone 
* @throws CloneNotSupportedException 
*/ 
protected abstract VersionedEntityImpl deepCloneEntity(VersionedEntityImpl clone, 
     Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException; 

Lo que hay que poner en las subclases:

@SuppressWarnings("unchecked") 
@Override 
protected VersionedEntityImpl deepCloneEntity(VersionedEntityImpl clone, 
     Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException { 
    // fill clone's attributes from original entity. Call 
    // deepClone(entity,alreadyCloned) 
    // recursively for each entity valued object. 
    if (this.associatedItems != null) { 
     List<SomeClass> listClone = new LinkedList<SomeClass>(); 
     for (SomeClass someClass: this.associatedItems) { 
      listClone.add((SomeClass) super.deepClone(someClass, alreadyCloned)); 
     } 
     ((SomeOtherClass) clone).setAssociatedItems(listClone); 
    } 
    ((SomeOtherClass) clone).setYetAnotherItem((YetAnotherClass) super.deepClone(this.yai, alreadyCloned)); 

    return clone; 
} 

no es perfecta hasta el momento pero se hace el trabajo hecho muy bien por el momento :)

Cuestiones relacionadas