Как рекурсивно сериализовать объект, используя отражение? - PullRequest
4 голосов
/ 12 апреля 2010

Я хочу перейти на N-й уровень объекта и сериализовать его свойства в формате String. Например:

class Animal {
   public String name;
   public int weight;
   public Animal friend;
   public Set<Animal> children = new HashSet<Animal>() ;
}

следует сериализовать так:

{name:"Monkey",
 weight:200,
 friend:{name:"Monkey Friend",weight:300 ,children:{...if has children}},
 children:{name:"MonkeyChild1",weight:100,children:{... recursively nested}}
}

И вы, возможно, заметите, что это похоже на сериализацию объекта в json. Я знаю, что есть много библиотек (Гсон, Джексон ...), которые могут это сделать, можете ли вы дать мне несколько поучительных идей о том, как написать это самостоятельно?

Ответы [ 3 ]

6 голосов
/ 12 апреля 2010

Google Gson может выполнить эту задачу в одной строке:

String json = new Gson().toJson(animal);

И наоборот, так же просто:

Animal animal = new Gson().fromJson(json, Animal.class);

Я еще не видел другого сериализатора JSON с лучшей поддержкой обобщений, коллекций / карт и (вложенных) javabeans.

Обновление: Кстати, вам просто нужно узнать об API отражения. Я рекомендую сначала пройти курс обучения Sun по теме . В двух словах, вы можете использовать Object#getClass() и все методы, предоставляемые java.lang.Class, java.lang.reflect.Method и т. Д., Для определения того и другого. Google Gson с открытым исходным кодом, воспользуйтесь им.

5 голосов
/ 12 апреля 2010

Сериализация в основном глубокое клонирование.

Необходимо отслеживать каждую ссылку на объект для повторных валют (например, с помощью IdentityHashMap ). Каким бы ни был ваш последний метод реализации (если не внешняя библиотека), не забудьте проверить повторяемость объектов, или вы можете оказаться в бесконечном цикле (когда объект A имеет ссылку на объект B, который снова имеет ссылку на объект A или что-то более сложное в граф объектов).

Один из способов - пройти по графу объектов с помощью DFS-подобного алгоритма и построить оттуда клон (сериализованную строку).

Этот псевдокод, надеюсь, объясняет, как:

visited = {}
function visit(node) {
  if node in visited {
    doStuffOnReoccurence(node)
    return
  }
  visited.add(node)
  doStuffBeforeOthers(node)
  for each otherNode in node.expand() visit(otherNode)
  doStuffAfterOthers(node)
}

Посещаемый набор в примере - это то место, где я бы использовал набор идентификаторов (если он был) или IdentityHashMap.

При рефлексивном поиске полей (то есть части node.expand ()) не забудьте также пройти через поля суперкласса.

Отражение не должно использоваться в «нормальном» случае разработки. Reflection обрабатывает код как данные, и вы можете игнорировать все обычные ограничения доступа к объектам. Я использовал этот рефлексивный материал для глубокого копирования только для тестов:

  1. В тесте, который проверял различные виды объектов на предмет глубокого равенства графов объектов

  2. В тесте, который анализировал размер графа объекта и другие свойства

4 голосов
/ 12 апреля 2010

Простой способ добиться этого - использовать шаблон посетитель , чтобы отделить реализацию кодирования от ваших бизнес-объектов . Некоторые люди утверждают, что вы можете просто реализовать интерфейс Externalizable вместе с readExternal / writeExternal, но это имеет следующие проблемы:

  • Ваш протокол встроен в ваш бизнес-объект, то есть он распространяется по всей базе кода, а не в одном месте.
  • Ваше приложение не может поддерживать несколько протоколов (в соответствии с предложением Google Protocol Buffers).

Пример

/**
 * Our visitor definition.  Includes a visit method for each
 * object it is capable of encoding.
 */
public interface Encoder {
  void visitAnimal(Animal a);
  void visitVegetable(Vegetable v);
  void visitMineral(Mineral m);
}

/**
 * Interface to be implemented by each class that can be encoded.
 */
public interface Encodable {
  void applyEncoder(Encoder e);
}

public class Animal implements Encodable {
  public void applyEncoder(Encoder e) {
    // Make call back to encoder to encode this particular Animal.
    // Different encoder implementations can be passed to an Animal
    // *without* it caring.
    e.visitAnimal(this);
  }
}

Как правило, тогда можно определить реализацию Encoder с состоянием, которая будет «толкать» каждый объект к OutputStream при вызове его метода visitXXX; например,

public class EncoderImpl implements Encoder {
  private final DataOutputStream daos;

  public EncoderImpl(File file) throws IOException {
    this.daos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
  }

  public void visitAnimal(Animal a) {        
    daos.writeInt(a.getWeight());
    daos.writeUTF(a.getName());

    // Write the number of children followed by an encoding of each child animal.
    // This allows for easy decoding.
    daos.writeInt(a.getChildren().size());

    for (Animal child : a.getChildren()) {
      visitAnimal(child);
    }
  }

  // TODO: Implement other visitXXX methods.

  /**
   * Called after visiting each object that requires serializing.
   */
  public void done() {
    daos.close();
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...