Перейти к основному содержимому

Введение в сериализацию Java

· 5 мин. чтения

1. Введение

Сериализация — это преобразование состояния объекта в поток байтов; десериализация делает обратное. Другими словами, сериализация — это преобразование объекта Java в статический поток (последовательность) байтов, который затем можно сохранить в базе данных или передать по сети.

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

Процесс сериализации не зависит от экземпляра; например, мы можем сериализовать объекты на одной платформе и десериализовать их на другой. Классы, подходящие для сериализации, должны реализовать специальный интерфейс маркера Serializable.

И 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;

This method can read a stream of bytes and convert it back into a Java object. It can then be cast back to the original object.

Let's illustrate serialization with a Person class. Note that static fields belong to a class (as opposed to an object) and are not serialized . Also, note that we can use the keyword transient to ignore class fields during serialization:

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() == person.getAge());
assertTrue(p2.getName().equals(person.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());
}

В этом коде мы видим, как сохранить некоторые несериализуемые атрибуты, сериализуя Address с помощью пользовательской сериализации. Обратите внимание, что мы должны пометить несериализуемые атрибуты как временные , чтобы избежать исключения NotSerializableException.

4. Вывод

В этой краткой статье мы рассмотрели сериализацию Java, обсудили предостережения и узнали, как выполнять пользовательскую сериализацию.

Как всегда, исходный код, использованный в этой статье, доступен на GitHub .