Я знаю, що це запитання справді старе і має прийняту відповідь, але, оскільки воно з'являється дуже високо в пошуку в 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), ми можемо написати делегата серіалізації, який серіалізує будь-яку колекцію, записавши порожній конструктор в потік, а потім розмір колекції та елементи, повернуті ітератором колекції. Десеріалізація передбачає читання будівельника, додавання всіх згодом прочитаних елементів та повернення результату фіналу build()
від делегатів readResolve
. У такому випадку нам знадобиться реалізувати серіалізацію лише в кореневому класі ієрархії колекції, і жоден додатковий код не буде потрібний у поточних чи майбутніх реалізаціях, за умови, що вони реалізують абстрактні iterator()
таbuilder()
метод (останній для відтворення колекції одного типу - що було б самою корисною ознакою). Іншим прикладом може бути ієрархія класів, кодом якого ми не повністю керуємо - наш базовий клас із сторонньої бібліотеки може мати будь-яку кількість приватних полів, про які ми нічого не знаємо і які можуть змінюватися від однієї версії до іншої, порушуючи наші серіалізовані об’єкти. У такому випадку було б безпечніше записати дані та відновити об’єкт вручну після десеріалізації.
String.CaseInsensitiveComparator.readResolve()