Książka Efektywna Java i inne źródła dostarczają całkiem dobrego wyjaśnienia, jak i kiedy używać metody readObject () podczas pracy z serializowalnymi klasami Java. Z drugiej strony metoda readResolve () pozostaje nieco tajemnicą. Zasadniczo wszystkie dokumenty, które znalazłem, albo wspominają tylko o jednym z nich, albo wspominają o obu tylko indywidualnie.
Pytania, które pozostają bez odpowiedzi, to:
- Jaka jest różnica między tymi dwiema metodami?
- Kiedy należy wdrożyć jaką metodę?
- Jak powinno się używać readResolve (), zwłaszcza jeśli chodzi o zwracanie czego?
Mam nadzieję, że możesz rzucić trochę światła na tę sprawę.
java
serialization
singleton
Pasza
źródło
źródło
String.CaseInsensitiveComparator.readResolve()
Odpowiedzi:
readResolve
służy do zamiany obiektu odczytanego ze strumienia. Jedynym zastosowaniem, jakie kiedykolwiek widziałem, jest wymuszanie singletonów; kiedy obiekt jest odczytywany, zamień go na pojedynczą instancję. Gwarantuje to, że nikt nie może utworzyć innego wystąpienia przez serializację i deserializację singletona.źródło
transient
polach.readResolve
służy do rozwiązywania obiektu po jego przeczytaniu. Przykładowym zastosowaniem jest prawdopodobnie obiekt przechowujący pamięć podręczną, którą można odtworzyć na podstawie istniejących danych i nie trzeba go serializować; dane z pamięci podręcznej można zadeklarowaćtransient
ireadResolve()
odbudować po deserializacji. Do takich rzeczy służy ta metoda.Serializable
: jest napisane, że "Klasy, które muszą wyznaczyć zamiennik, gdy ich instancja jest odczytywana ze strumienia, powinny implementować tę [readResolve
] specjalną metodę ...".Pozycja 90, Effective Java, 3rd Ed obejmuje
readResolve
iwriteReplace
dla serwerów proxy szeregowych - ich główne zastosowanie. W przykładach nie są zapisywanereadObject
iwriteObject
metody, ponieważ używają one domyślnej serializacji do odczytu i zapisu pól.readResolve
jest wywoływana poreadObject
powrocie (odwrotniewriteReplace
jest wywoływana przedwriteObject
i prawdopodobnie na innym obiekcie). Obiekt zwracany przez metodę zastępujethis
obiekt zwrócony do użytkownikaObjectInputStream.readObject
oraz wszelkie dalsze odwołania wsteczne do obiektu w strumieniu. ObiereadResolve
iwriteReplace
mogą zwracać obiekty tego samego lub różnych typów. Zwracanie tego samego typu jest przydatne w niektórych przypadkach, gdy pola muszą byćfinal
i albo wymagana jest zgodność z poprzednimi wersjami, albo wartości muszą zostać skopiowane i / lub sprawdzone.Użycie
readResolve
nie wymusza własności singleton.źródło
readResolve może służyć do zmiany danych, które są serializowane za pomocą metody readObject. Na przykład xstream API używa tej funkcji do inicjalizacji niektórych atrybutów, których nie ma w XML do deserializacji.
http://x-stream.github.io/faq.html#Serialization
źródło
readObject () jest metodą istniejącą w klasie ObjectInputStream. podczas odczytu obiektu w czasie deserializacji metoda readObject wewnętrznie sprawdza, czy obiekt klasy, który jest deserializowany z metodą readResolve, czy nie, jeśli istnieje metoda readResolve, to wywoła metodę readResolve i zwróci to samo instancja.
Tak więc intencja pisania metody readResolve jest dobrą praktyką w celu uzyskania czystego wzorca projektowania singleton, w którym nikt nie może uzyskać innego wystąpienia przez serializację / deserializację.
źródło
readResolve służy do sytuacji, gdy może być konieczne zwrócenie istniejącego obiektu, np. ponieważ sprawdzasz, czy nie ma zduplikowanych danych wejściowych, które powinny zostać scalone, lub (np. w ostatecznie spójnych systemach rozproszonych), ponieważ jest to aktualizacja, która może pojawić się, zanim się o tym dowiesz wszelkie starsze wersje.
źródło
Funkcja readResolve () zapewni pojedynczy kontrakt podczas serializacji.
Proszę odnieść się
źródło
Gdy serializacja jest używana do konwersji obiektu, aby można go było zapisać w pliku, możemy wyzwolić metodę readResolve (). Metoda jest prywatna i jest przechowywana w tej samej klasie, której obiekt jest pobierany podczas deserializacji. Zapewnia, że po deserializacji zwracany obiekt jest taki sam, jak został serializowany. To jest,
instanceSer.hashCode() == instanceDeSer.hashCode()
Metoda readResolve () nie jest metodą statyczną. After
in.readObject()
jest wywoływane podczas deserializacji, po prostu upewnia się, że zwrócony obiekt jest taki sam jak ten, który został serializowany jak poniżej whileout.writeObject(instanceSer)
.. ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser")); out.writeObject(instanceSer); out.close();
W ten sposób pomaga również w implementacji pojedynczego wzorca projektowego , ponieważ za każdym razem zwracana jest ta sama instancja.
public static ABCSingleton getInstance(){ return ABCSingleton.instance; //instance is static }
źródło
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 wwriteObject
. 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; } //methods follow... }
Ta struktura może być serializowana przez rekursywne zapisywanie
head
pola każdego linku, po którym następujenull
wartość. Deserializacja takiego formatu staje się jednak niemożliwa:readObject
nie można zmienić wartości pól składowych (teraz naprawiononull
). OtowriteReplace
/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); //read the tail and assign it to this.contents 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 jakjava.util.List
implementacja listy, która eksponuje wycinek z dłuższegoArrayList
. 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 plikuArrayList
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ówreadResolve
. 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ą abstrakcyjneiterator()
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.źródło
Metoda readResolve
W przypadku klas Serializable i Externalizable metoda readResolve umożliwia klasie zastąpienie / rozwiązanie obiektu odczytanego ze strumienia przed zwróceniem go do obiektu wywołującego. Implementując metodę readResolve, klasa może bezpośrednio kontrolować typy i wystąpienia swoich własnych wystąpień, które są deserializowane. Metoda jest zdefiniowana w następujący sposób:
ANY-ACCESS-MODIFIER Obiekt readResolve () zgłasza wyjątek ObjectStreamException;
Metoda readResolve jest wywoływana, gdy ObjectInputStream odczytał obiekt ze strumienia i przygotowuje się do zwrócenia go do obiektu wywołującego. ObjectInputStream sprawdza, czy klasa obiektu definiuje metodę readResolve. Jeśli metoda jest zdefiniowana, wywoływana jest metoda readResolve, aby umożliwić obiektowi w strumieniu wyznaczenie obiektu do zwrócenia. Zwrócony obiekt powinien być typu zgodnego ze wszystkimi zastosowaniami. Jeśli nie jest zgodny, ClassCastException zostanie zgłoszony po wykryciu niezgodności typów.
Na przykład można utworzyć klasę Symbol, dla której tylko jedno wystąpienie każdego powiązania symbolu istniało w maszynie wirtualnej. Metoda readResolve zostanie zaimplementowana w celu określenia, czy ten symbol został już zdefiniowany i zastąpi istniejący wcześniej równoważny obiekt Symbol, aby zachować ograniczenie tożsamości. W ten sposób unikatowość obiektów Symbol może zostać zachowana w całej serializacji.
źródło
Jak już odpowiedziałem,
readResolve
jest to metoda prywatna używana w ObjectInputStream podczas deserializacji obiektu. Jest to wywoływane tuż przed zwróceniem rzeczywistej instancji. W przypadku Singletona możemy wymusić zwrócenie już istniejącego odwołania do instancji singletona zamiast deserializowanego odwołania do instancji. Podobnie jak wwriteReplace
przypadku ObjectOutputStream.Przykład dla
readResolve
:import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SingletonWithSerializable implements Serializable { private static final long serialVersionUID = 1L; public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable(); private SingletonWithSerializable() { if (INSTANCE != null) throw new RuntimeException("Singleton instance already exists!"); } private Object readResolve() { return INSTANCE; } public void leaveTheBuilding() { System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called..."); } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE; System.out.println("Before serialization: " + instance); try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) { out.writeObject(instance); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) { SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject(); System.out.println("After deserialization: " + readObject); } }
}
Wynik:
Before serialization: com.ej.item3.SingletonWithSerializable@7852e922 After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
źródło