Wiem, że to pytanie jest naprawdę stare i ma akceptowaną odpowiedź, ale ponieważ pojawia się bardzo wysoko w wyszukiwarce Google, pomyślałem, że zważę, ponieważ żadna udzielona odpowiedź nie obejmuje trzech przypadków, które uważam za ważne - moim zdaniem ich podstawowe zastosowanie metody. Oczywiście wszyscy zakładają, że w rzeczywistości istnieje potrzeba niestandardowego formatu serializacji.
Weźmy na przykład klasy kolekcji. Domyślna serializacja połączonej listy lub BST spowodowałaby ogromną utratę miejsca z bardzo małym wzrostem wydajności w porównaniu do zwykłej serializacji elementów w kolejności. Jest to jeszcze bardziej prawdziwe, jeśli kolekcja jest projekcją lub widokiem - zachowuje odwołanie do większej struktury niż ujawnia jej publiczny interfejs API.
Jeśli serializowany obiekt ma niezmienne pola, które wymagają niestandardowej serializacji, oryginalne rozwiązanie writeObject/readObject
jest niewystarczające, ponieważ obiekt deserializowany jest tworzony przed odczytaniem części strumienia zapisanego w writeObject
. Weź tę minimalną implementację połączonej listy:
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;
}
}
Ta struktura może być serializowana przez rekursywne zapisywanie head
pola każdego linku, po którym następuje null
wartość. Deserializacja takiego formatu staje się jednak niemożliwa: readObject
nie można zmienić wartości pól składowych (teraz naprawiono null
). Oto writeReplace
/ readResolve
para:
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);
this.contents = new List<>(head, this.contents)
}
}
private Object readResolve() {
return this.contents;
}
}
}
Przepraszam, jeśli powyższy przykład nie kompiluje się (lub nie działa), ale mam nadzieję, że wystarczy, aby zilustrować mój punkt widzenia. Jeśli uważasz, że jest to bardzo daleko idący przykład, pamiętaj, że w JVM działa wiele języków funkcjonalnych i takie podejście staje się w ich przypadku niezbędne.
Możemy chcieć faktycznie deserializować obiekt innej klasy niż napisaliśmy do ObjectOutputStream
. Byłoby tak w przypadku widoków, takich jak java.util.List
implementacja listy, która eksponuje wycinek z dłuższego ArrayList
. Oczywiście serializacja całej listy zapasowej jest złym pomysłem i powinniśmy zapisywać tylko elementy z oglądanego wycinka. Po co jednak na tym poprzestać i mieć bezużyteczny poziom pośrednictwa po deserializacji? Moglibyśmy po prostu wczytać elementy ze strumienia do pliku ArrayList
i zwrócić go bezpośrednio, zamiast zawijać go w naszej klasie widoku.
Alternatywnie posiadanie podobnej klasy delegata poświęconej serializacji może być wyborem projektu. Dobrym przykładem byłoby ponowne użycie naszego kodu serializacji. Na przykład, jeśli mamy klasę konstruktora (podobną do StringBuilder for String), możemy napisać delegata serializacji, który serializuje dowolną kolekcję, pisząc do strumienia pustego konstruktora, po którym następuje rozmiar kolekcji i elementy zwracane przez iterator kolekcji. Deserializacja obejmowałaby odczytanie konstruktora, dołączenie wszystkich później odczytanych elementów i zwrócenie wyniku końcowego build()
z delegatów readResolve
. W takim przypadku musielibyśmy zaimplementować serializację tylko w klasie głównej hierarchii kolekcji i nie byłby potrzebny żaden dodatkowy kod z obecnych lub przyszłych implementacji, pod warunkiem, że implementują abstrakcyjne iterator()
ibuilder()
metoda (ta ostatnia służy do odtwarzania kolekcji tego samego typu - co samo w sobie byłoby bardzo przydatną funkcją). Innym przykładem może być hierarchia klas, której kod nie w pełni kontrolujemy - nasze klasy bazowe z biblioteki zewnętrznej mogą mieć dowolną liczbę pól prywatnych, o których nic nie wiemy i które mogą zmieniać się z jednej wersji na drugą, powodując nasze zserializowane obiekty. W takim przypadku bezpieczniej byłoby zapisać dane i odbudować obiekt ręcznie podczas deserializacji.
String.CaseInsensitiveComparator.readResolve()