2010-04-26 16 views
10

A veces (bastante, en realidad) obtenemos una situación en Java donde dos objetos apuntan a la misma cosa. Ahora, si serializamos estos por separado, es bastante apropiado que los formularios serializados tengan copias separadas del objeto, ya que debería ser posible abrir uno sin el otro. Sin embargo, si ahora los deserializamos a ambos, descubrimos que todavía están separados. ¿Hay alguna forma de vincularlos nuevamente?¿Cómo puedo vincular dos objetos serializados de Java juntos?

Ejemplo a continuación.

public class Example { 

private static class ContainerClass implements java.io.Serializable { 
    private ReferencedClass obj; 
    public ReferencedClass get() { 
    return obj; 
    } 
    public void set(ReferencedClass obj) { 
    this.obj = obj; 
    } 
} 

private static class ReferencedClass implements java.io.Serializable { 
    private int i = 0; 
    public int get() { 
    return i; 
    } 
    public void set(int i) { 
    this.i = i; 
    } 
} 

public static void main(String[] args) throws Exception { 
    //Initialise the classes 
    ContainerClass test1 = new ContainerClass(); 
    ContainerClass test2 = new ContainerClass(); 
    ReferencedClass ref = new ReferencedClass(); 

    //Make both container class point to the same reference 
    test1.set(ref); 
    test2.set(ref); 

    //This does what we expect: setting the integer in one (way of accessing the) referenced class sets it in the other one 
    test1.get().set(1234); 
    System.out.println(Integer.toString(test2.get().get())); 

    //Now serialise the container classes 
    java.io.ObjectOutputStream os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test1.ser")); 
    os.writeObject(test1); 
    os.close(); 
    os = new java.io.ObjectOutputStream(new java.io.FileOutputStream("C:\\Users\\Public\\test2.ser")); 
    os.writeObject(test2); 
    os.close(); 

    //And deserialise them 
    java.io.ObjectInputStream is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test1.ser")); 
    ContainerClass test3 = (ContainerClass)is.readObject(); 
    is.close(); 
    is = new java.io.ObjectInputStream(new java.io.FileInputStream("C:\\Users\\Public\\test2.ser")); 
    ContainerClass test4 = (ContainerClass)is.readObject(); 
    is.close(); 

    //We expect the same thing as before, and would expect a result of 4321, but this doesn't happen as the referenced objects are now separate instances 
    test3.get().set(4321); 
    System.out.println(Integer.toString(test4.get().get())); 
} 

} 
+0

+1 la ruina del objeto deserializante en cualquier idioma –

Respuesta

3

El readResolve() method permite esto (por supuesto, en primer lugar hay que definir cómo se va a decidir qué objetos están destinados a ser "la misma"). Pero mucho más fácil sería serializar ambos objetos en el mismo archivo: ObjectOut/InputStream mantiene un registro de todos los objetos que ha serializado/deserializado y solo almacenará y devolverá referencias a los objetos que ya ha visto.

+0

(y quizás 'writeReplace'.) –

+0

aproximadamente, la razón por la que trato de hacer esto es porque tengo un archivo que contiene datos que es poco probable que cambien en varias sesiones de la aplicación, y uno que contiene datos que son específicos de una sola sesión de la aplicación. Estos tienen referencias cruzadas entre ellos, pero quiero mantenerlos lógicamente separados. – Kidburla

0

He hecho algo así para un servidor de aplicaciones/base de datos de objetos que estoy construyendo. ¿Cuáles son sus requisitos? ¿Por qué necesita hacer eso? Si sus requisitos son algo menos que un servidor de aplicaciones, entonces tal vez algún otro diseño lo resolvería más fácilmente.


Si de todos modos desea continuar, aquí es cómo hacerlo:

En primer lugar es necesario conectar en el proceso de serialización sobreescribiendo los métodos ObjectOutputStream.replaceObject() y ObjectInputStream.resolveObject(). Vea mi ObjectSerializer para un ejemplo.

Cuando serializa los objetos, debe asignar una ID única para cada instancia de objeto para la que desea tener una identidad única: ese tipo de objetos se denominan comúnmente entidades. Cuando el objeto se refiere a otras entidades, debe reemplazar esas otras entidades por un objeto de marcador de posición que contenga la ID de la entidad referida.

Luego, cuando los objetos se deserializan, debe reemplazar cada uno de esos objetos de marcador de posición con el objeto de entidad real que tiene esa ID. Debe realizar un seguimiento de las instancias de objeto de entidad que se han cargado en la memoria y sus ID, de modo que para cada ID solo se crea una sola instancia. Si la entidad aún no está cargada en la memoria, debe cargarla desde donde se guardó. Ver mi EntityManager para un ejemplo.

Si desea realizar una carga diferida, para evitar cargar todo el gráfico de objetos en la memoria cuando no sea necesario, debe hacer algo similar al transparent references. Vea su implementación here. Si está llegando tan lejos, también puede copiar esas partes (los paquetes entities, entities.tref, serial y quizás también context) de mi proyecto (tiene una licencia permisiva) y modificarlas para adaptarlas a sus necesidades (es decir, eliminar cosas que no es necesario).

1

Al igual que en las respuestas anteriores, readResolve es la clave, ya que le permite reemplazar el objeto "duplicado" con el que desea.

Suponiendo que su clase implementa hashCode() y es igual a(), implementa la desduplicación creando un WeakHashMap estático que mantiene todas las referencias a los objetos creados todavía en la memoria. p.ej.

class ReferencedClass implements Serializable 
{ 
    static private Map<ReferencedClass, Reference<ReferencedClass>> map = new WeakHashMap<ReferencedClass, Reference<ReferencedClass>>; 

    static public ReferencedClass findOriginal(ReferencedClass obj) 
    { 
     WeakReference<ReferencedClass> originalRef = map.get(obj); 
     ReferencedClass original = originalRef==null ? null : originalRef.get(); 
     if (original==null) 
     { 
      original = obj; 
      map.put(original, new WeakReference<ReferencedClass>(original)); 
     } 
     return original; 
    } 

    static public ReferencedClass() 
    { 
     findOriginal(this); 
    } 

    private Object readResolve() 
    { 
     return findOriginal(this); 
    } 
} 

Cuando deserializar readResolve(), pide RerencedClass.findOriginal(this) a buscar a la instancia original actual. Si las instancias de esta clase solo se crean por deserialización, esto funcionará como está.Si también está construyendo objetos (utilizando el nuevo operador), sus constructores también deben llamar a findOriginal, pasando esto, para que esos objetos también se agreguen al grupo.

Con estos cambios, ambas instancias ContainerClass señalarán la misma instancia ReferenceClass, aunque se deserializaron de forma independiente.

Cuestiones relacionadas