Вступ до серіалізації Java

1. Вступ

Серіалізація - це перетворення стану об’єкта в байтовий потік; десериалізація робить навпаки. По-іншому, серіалізація - це перетворення об'єкта Java у статичний потік (послідовність) байтів, який потім можна зберегти в базі даних або передати через мережу.

2. Серіалізація та десеріалізація

Процес серіалізації не залежить від екземпляра, тобто об’єкти можуть бути серіалізовані на одній платформі, а десеріалізовані на іншій. Класи, які придатні для серіалізації, повинні застосовувати спеціальний інтерфейс маркера, який можна серіалізувати.

Обидва ObjectInputStream і ObjectOutputStream класи високого рівня , які розширюють java.io.InputStream і java.io.OutputStream відповідно. ObjectOutputStream може писати примітивні типи та графіки об’єктів у OutputStream як потік байтів. Ці потоки згодом можна прочитати за допомогою ObjectInputStream .

Найважливішим методом у ObjectOutputStream є:

public final void writeObject(Object o) throws IOException;

Який приймає об’єкт, який можна серіалізувати, і перетворює його в послідовність (потік) байтів. Аналогічним чином, найважливішим методом в ObjectInputStream є:

public final Object readObject() throws IOException, ClassNotFoundException;

Який може прочитати потік байтів і перетворити його назад в об'єкт Java. Потім його можна повернути до початкового об'єкта.

Давайте проілюструємо серіалізацію класом Person . Зверніть увагу, що статичні поля належать до класу (на відміну від об’єкта) і не є серіалізованими . Також зверніть увагу, що ми можемо використовувати ключове слово перехідний, щоб ігнорувати поля класу під час серіалізації:

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 }

Наведений нижче тест показує приклад збереження об’єкта типу Person до локального файлу, після чого зчитайте це значення назад у:

@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())); }

Ми використовували ObjectOutputStream для збереження стану цього об’єкта у файл за допомогою FileOutputStream . Файл “yourfile.txt” створюється в каталозі проекту. Потім цей файл завантажується за допомогою FileInputStream. ObjectInputStream піднімає цей потік і перетворює його в новий об'єкт під назвою p2 .

Нарешті, ми перевіряємо стан завантаженого об'єкта, і він відповідає стану вихідного об'єкта.

Зверніть увагу, що завантажений об'єкт повинен бути явно переданий типу Person .

3. Застереження щодо серіалізації Java

Є деякі застереження, що стосуються серіалізації в Java.

3.1. Спадщина та склад

Коли клас реалізує інтерфейс java.io.Serializable , усі його підкласи також можна серіалізувати. Навпаки, коли об’єкт має посилання на інший об’єкт, ці об’єкти повинні реалізовувати інтерфейс Serializable окремо, інакше буде викинуто NotSerializableException :

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

Якщо одне з полів об’єкта, що серіалізується, складається з масиву об’єктів, то всі ці об’єкти також повинні бути серіалізувальними, інакше буде викинуто NotSerializableException .

3.2. UID серійної версії

JVM пов'язує номер версії ( long ) з кожним серіалізованим класом. Він використовується для перевірки того, що збережені та завантажені об'єкти мають однакові атрибути і, отже, сумісні при серіалізації.

Більшість IDE може генерувати це число автоматично і базується на назві класу, його атрибутах та пов’язаних модифікаторах доступу. Будь-які зміни спричиняють іншу кількість і можуть спричинити InvalidClassException .

Якщо серіалізуваний клас не оголошує serialVersionUID , JVM автоматично генерує його під час виконання. Однак настійно рекомендується, щоб кожен клас оголосив свій serialVersionUID, оскільки згенерований залежить від компілятора, і, отже, може спричинити несподівані InvalidClassExceptions .

3.3. Спеціальна серіалізація в Java

Java визначає спосіб за замовчуванням, при якому об'єкти можуть бути серіалізовані. Класи Java можуть замінити цю поведінку за замовчуванням. Спеціальна серіалізація може бути особливо корисною при спробі серіалізувати об'єкт, який має деякі несеріалізувані атрибути. Це можна зробити, надавши два методи всередині класу, які ми хочемо серіалізувати:

private void writeObject(ObjectOutputStream out) throws IOException;

і

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

За допомогою цих методів ми можемо серіалізувати ці несеризуються атрибути в інші форми, які можна серіалізувати:

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 }

Наступний модульний тест перевіряє цю спеціальну серіалізацію:

@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()); }

У цьому коді ми бачимо, як зберегти деякі несеризуються атрибути, серіалізуючи Адресу за допомогою користувацької серіалізації. Зверніть увагу, що ми повинні позначати несеріалізувані атрибути як перехідні, щоб уникнути NotSerializableException.

4. Висновок

У цьому короткому навчальному посібнику ми розглянули серіалізацію Java, обговорили важливі речі, про які слід пам’ятати, та продемонстрували, як робити користувацьку серіалізацію.

Як завжди, вихідний код, використаний у цьому посібнику, доступний на GitHub.