Я знаю, что этот вопрос действительно старый и на него есть принятый ответ, но, поскольку он всплывает очень высоко в поиске Google, я думал, что взвесу, потому что не предоставленный ответ охватывает три случая, которые я считаю важными - на мой взгляд, основное использование для них методы. Конечно, все предполагают, что на самом деле существует необходимость в настраиваемом формате сериализации.
Взять, к примеру, классы коллекций. Сериализация связанного списка или BST по умолчанию приведет к огромной потере места с очень небольшим увеличением производительности по сравнению с простой сериализацией элементов по порядку. Это еще более верно, если коллекция является проекцией или представлением - хранит ссылку на более крупную структуру, чем она предоставляет своим общедоступным API.
Если в сериализованном объекте есть неизменяемые поля, требующие настраиваемой сериализации, исходного решения writeObject/readObject
недостаточно, поскольку десериализованный объект создается до чтения записанной части потока writeObject
. Возьмем эту минимальную реализацию связанного списка:
public class List<E> extends Serializable {
public final E head;
public final List<E> tail;
public List(E head, List<E> tail) {
if (head==null)
throw new IllegalArgumentException("null as a list element");
this.head = head;
this.tail = tail;
}
//methods follow...
}
Эта структура может быть сериализована путем рекурсивного написания head
поля каждой ссылки, за которым следует null
значение. Однако десериализация такого формата становится невозможной: readObject
невозможно изменить значения полей-членов (теперь фиксированные null
). Вот пара writeReplace
/ readResolve
:
private Object writeReplace() {
return new Serializable() {
private transient List<E> contents = List.this;
private void writeObject(ObjectOutputStream oos) {
List<E> list = contents;
while (list!=null) {
oos.writeObject(list.head);
list = list.tail;
}
oos.writeObject(null);
}
private void readObject(ObjectInputStream ois) {
List<E> tail = null;
E head = ois.readObject();
if (head!=null) {
readObject(ois); //read the tail and assign it to this.contents
this.contents = new List<>(head, this.contents)
}
}
private Object readResolve() {
return this.contents;
}
}
}
Прошу прощения, если приведенный выше пример не компилируется (или не работает), но, надеюсь, этого достаточно, чтобы проиллюстрировать мою точку зрения. Если вы считаете, что это очень надуманный пример, помните, что многие функциональные языки работают на JVM, и в их случае такой подход становится существенным.
Возможно, мы захотим десериализовать объект другого класса, чем мы написали в ObjectOutputStream
. Это может быть в случае с представлениями, такими как java.util.List
реализация списка, которая предоставляет фрагмент из более длинного ArrayList
. Очевидно, сериализация всего списка поддержки - плохая идея, и мы должны записывать только элементы из просматриваемого фрагмента. Однако зачем останавливаться на этом и иметь бесполезный уровень косвенного обращения после десериализации? Мы могли бы просто прочитать элементы из потока в ArrayList
и вернуть их напрямую, вместо того, чтобы заключать их в наш класс представления.
В качестве альтернативы выбор конструкции может быть аналогичным классом делегата, предназначенным для сериализации. Хорошим примером может быть повторное использование нашего кода сериализации. Например, если у нас есть класс построителя (аналогичный StringBuilder для String), мы можем написать делегат сериализации, который сериализует любую коллекцию, записывая в поток пустой построитель, за которым следуют размер коллекции и элементы, возвращаемые итератором коллекции. Десериализация будет включать чтение построителя, добавление всех впоследствии прочитанных элементов и возврат результата final build()
от делегатов readResolve
. В этом случае нам нужно будет реализовать сериализацию только в корневом классе иерархии коллекции, и не потребуется дополнительный код из текущих или будущих реализаций, при условии, что они реализуют абстрактные iterator()
иbuilder()
метод (последний для воссоздания однотипной коллекции, что само по себе было бы очень полезной функцией). Другим примером может быть иерархия классов, код которой мы не полностью контролируем - наши базовые классы из сторонней библиотеки могут иметь любое количество частных полей, о которых мы ничего не знаем и которые могут меняться от одной версии к другой, нарушая наши сериализованные объекты. В этом случае было бы безопаснее записать данные и перестроить объект вручную при десериализации.
String.CaseInsensitiveComparator.readResolve()