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 .