Wprowadzenie do serializacji Java

1. Wstęp

Serializacja to konwersja stanu obiektu na strumień bajtów; deserializacja działa odwrotnie. Mówiąc inaczej, serializacja to konwersja obiektu Java na statyczny strumień (sekwencję) bajtów, który można następnie zapisać w bazie danych lub przesłać przez sieć.

2. Serializacja i deserializacja

Proces serializacji jest niezależny od instancji, co oznacza, że ​​obiekty mogą być serializowane na jednej platformie i deserializowane na innej. Klasy kwalifikujące się do serializacji muszą zaimplementować specjalny interfejs znacznika Serializable.

Obie klasy ObjectInputStream i ObjectOutputStream są klasami wysokiego poziomu, które rozszerzają odpowiednio java.io.InputStream i java.io.OutputStream . ObjectOutputStream może zapisywać typy pierwotne i wykresy obiektów do OutputStream jako strumień bajtów. Te strumienie można następnie odczytać za pomocą ObjectInputStream .

Najważniejszą metodą w ObjectOutputStream jest:

public final void writeObject(Object o) throws IOException;

Który przyjmuje obiekt możliwy do serializacji i konwertuje go na sekwencję (strumień) bajtów. Podobnie najważniejszą metodą w ObjectInputStream jest:

public final Object readObject() throws IOException, ClassNotFoundException;

Który może odczytać strumień bajtów i przekonwertować go z powrotem na obiekt Java. Można to następnie przesłać z powrotem do oryginalnego obiektu.

Zilustrujmy serializację za pomocą klasy Person . Zwróć uwagę, że pola statyczne należą do klasy (w przeciwieństwie do obiektu) i nie są serializowane . Zwróć również uwagę, że możemy użyć słowa kluczowego transient, aby zignorować pola klas podczas serializacji:

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

Poniższy test pokazuje przykład zapisywania obiektu typu Person do pliku lokalnego, a następnie wczytaj tę wartość z powrotem w:

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

Użyliśmy ObjectOutputStream do zapisania stanu tego obiektu do pliku przy użyciu FileOutputStream . Plik „yourfile.txt” zostanie utworzony w katalogu projektu. Ten plik jest następnie ładowany za pomocą FileInputStream. ObjectInputStream pobiera ten strumień i konwertuje go na nowy obiekt o nazwie p2 .

Na koniec testujemy stan załadowanego obiektu i pasuje on do stanu oryginalnego obiektu.

Zwróć uwagę, że załadowany obiekt musi być jawnie rzutowany na typ Person .

3. Ostrzeżenia dotyczące serializacji Java

Istnieją pewne zastrzeżenia dotyczące serializacji w Javie.

3.1. Dziedziczenie i skład

Gdy klasa implementuje interfejs java.io.Serializable , wszystkie jej podklasy również są możliwe do serializacji. Wręcz przeciwnie, gdy obiekt ma odwołanie do innego obiektu, te obiekty muszą oddzielnie implementować interfejs Serializable , w przeciwnym razie zostanie zgłoszony wyjątek NotSerializableException :

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

Jeśli jedno z pól w obiekcie możliwym do serializacji składa się z tablicy obiektów, wszystkie te obiekty również muszą być możliwe do serializacji, w przeciwnym razie zostanie zgłoszony wyjątek NotSerializableException .

3.2. UID wersji szeregowej

JVM kojarzy numer wersji ( długi ) z każdą możliwą do serializacji klasą. Służy do weryfikacji, czy zapisane i załadowane obiekty mają te same atrybuty, a tym samym są zgodne z serializacją.

Liczba ta może być generowana automatycznie przez większość IDE i jest oparta na nazwie klasy, jej atrybutach i powiązanych modyfikatorach dostępu. Wszelkie zmiany skutkują inną liczbą i mogą spowodować wyjątek InvalidClassException .

Jeśli klasa możliwa do serializacji nie zadeklaruje serialVersionUID , maszyna JVM wygeneruje go automatycznie w czasie wykonywania. Jednak zdecydowanie zaleca się, aby każda klasa zadeklarowała swój serialVersionUID, ponieważ wygenerowana jest zależna od kompilatora, co może spowodować nieoczekiwane wyjątki InvalidClassExceptions .

3.3. Niestandardowa serializacja w Javie

Java określa domyślny sposób serializacji obiektów. Klasy Java mogą przesłonić to domyślne zachowanie. Niestandardowa serializacja może być szczególnie przydatna podczas próby serializacji obiektu, który ma pewne atrybuty, których nie można serializować. Można to zrobić, udostępniając dwie metody wewnątrz klasy, którą chcemy serializować:

private void writeObject(ObjectOutputStream out) throws IOException;

i

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

Za pomocą tych metod możemy serializować te atrybuty, których nie można serializować, do innych formularzy, które można serializować:

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

Poniższy test jednostkowy testuje tę serializację niestandardową:

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

W tym kodzie widzimy, jak zapisać niektóre atrybuty, których nie można serializować, przez serializację adresu z niestandardową serializacją. Zauważ, że musimy oznaczyć atrybuty, których nie można serializować, jako przejściowe, aby uniknąć NotSerializableException.

4. Wniosek

W tym krótkim samouczku omówiliśmy serializację Java, omówiliśmy ważne kwestie, o których należy pamiętać i pokazaliśmy, jak wykonać niestandardową serializację.

Jak zawsze kod źródłowy użyty w tym samouczku jest dostępny w serwisie GitHub.