Как связать два Java-сериализованных объекта вместе?

Иногда (довольно много, на самом деле) мы получаем ситуацию на Java, где два объекта указывают на одно и то же. Теперь, если мы сериализуем их отдельно, вполне уместно, чтобы сериализованные формы имели отдельные копии объекта, так как можно было бы открыть один без другого. Однако, если мы теперь десериализируем их обоих, мы обнаруживаем, что они все еще разделены. Есть ли способ связать их вместе?

Пример.

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

}

Ответ 1

readResolve() method позволяет это (конечно, сначала вы должны определить, как вы собираетесь решать, какие объекты предназначены для быть "тем же" ). Но гораздо проще было бы сериализовать оба объекта в один и тот же файл - ObjectOut/InputStream ведет запись обо всех объектах, которые он сериализовал/десериализовал, и будет хранить и возвращать ссылки на объекты, которые он уже видел.

Ответ 2

Как и в приведенных выше ответах, readResolve - это ключ, так как он позволяет вам заменить объект "duplicate" тем, который вы хотите.

Предполагая, что ваш класс реализует hashCode() и equals(), вы реализуете дедупликацию, создавая статический WeakHashMap, который содержит все ссылки на созданные объекты, все еще находящиеся в памяти. например.

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

При десериализации readResolve() вызывает RerencedClass.findOriginal(this), чтобы извлечь текущий экземпляр оригинала. Если экземпляры этого класса создаются только путем десериализации, то это будет работать как есть. Если вы также создаете объекты (используя новый оператор), то ваши конструкторы также должны вызывать findOriginal, передавая это, чтобы эти объекты также добавлялись в пул.

С учетом этих изменений оба экземпляра ContainerClass будут указывать на тот же экземпляр ReferenceClass, даже если они были десериализованы без изменений.

Ответ 3

Я сделал что-то подобное для базы данных приложений/объектов, которые я создаю. Каковы ваши требования - зачем вам это нужно? Если ваши требования являются чем-то меньшим, чем сервер приложений, то, возможно, какой-то другой дизайн облегчит его.


Если вы все равно хотите продолжить, вот как это сделать:

Сначала вам нужно подключиться к процессу сериализации, переопределив ObjectOutputStream.replaceObject() и ObjectInputStream.resolveObject(). См. Мой ObjectSerializer для примера.

Когда вы сериализуете объекты, вы должны назначить уникальный идентификатор для каждого экземпляра объекта, для которого требуется уникальная идентификация, - такие объекты обычно называются объектами. Когда объект ссылается на другие объекты, вы должны заменить эти другие объекты объектом-заполнителем, который содержит идентификатор указанного объекта.

Затем, когда объекты десериализованы, вы должны заменить каждый из этих объектов-заполнителей реальным объектом объекта, который имеет этот идентификатор. Вам нужно отслеживать экземпляры объектов сущности, которые были загружены в память и их идентификаторы, так что для каждого идентификатора только один экземпляр когда-либо создается. Если объект еще не загружен в память, вы должны загрузить его с того места, где он был сохранен. См. Мой EntityManager для примера.

Если вы хотите делать ленивую загрузку, чтобы не загружать весь графический объект в память, когда он не нужен, вы должны сделать что-то похожее на прозрачные ссылки. См. Их реализацию здесь. Если вы доберетесь до этого, вы можете также скопировать эти части (пакеты entities, entities.tref, serial и, возможно, context) из моего проекта - он имеет разрешительную лицензию и модифицирует их в соответствии с вашими потребностями (т.е. удаляет ненужные вещи).