1. Введение
В Java сериализация работает только с теми объектами, которые явно разрешили это делать. Для этого класс должен реализовать специальный интерфейс — java.io.Serializable.
import java.io.Serializable;
public class Person implements Serializable {
// Поля, конструкторы, методы
}
Serializable — это маркерный интерфейс: у него нет методов, он просто сообщает JVM — «этот класс можно сериализовать, не бойтесь!». Если вы попробуете сериализовать объект класса, который не реализует Serializable, получите исключение NotSerializableException. Даже если хотя бы одно поле (или вложенный объект) не сериализуемо — сериализация не сработает.
ObjectOutputStream и ObjectInputStream
- ObjectOutputStream — класс, который записывает объекты в поток (например, в файл или по сети).
- ObjectInputStream — класс, который читает объекты из потока.
Они работают в паре: один сериализует объект, другой — десериализует.
Основные методы
- writeObject(Object obj) — сериализует объект и записывает его в поток.
- readObject() — читает объект из потока, десериализует и возвращает его.
Важно: оба класса работают поверх обычных потоков ввода-вывода (OutputStream и InputStream). Чаще всего их используют вместе с файловыми потоками — FileOutputStream и FileInputStream, но можно применять и с сетевыми потоками.
2. Пример сериализации
Давайте напишем простой пример: сериализуем и десериализуем объект класса Person.
Шаг 1. Описываем класс
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// transient-поле не будет сериализовано
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
Шаг 2. Сериализация объекта в файл
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// Создаём поток для записи в файл
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Сохраняем объект
out.writeObject(person);
// Закрываем потоки
out.close();
fileOut.close();
System.out.println("Объект сериализован в файл person.bin");
}
}
Шаг 3. Десериализация объекта из файла
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// Открываем поток для чтения из файла
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Восстанавливаем объект
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Объект десериализован: " + person);
}
}
Ожидаемый вывод
Объект сериализован в файл person.bin
Объект десериализован: Person{name='Alice', age=30, secret='null'}
Внимание! Поле transient не сериализуется. После десериализации оно будет равно null. Это важно для временных или чувствительных данных.
3. Ограничения и особенности
Все поля должны быть сериализуемыми
Если у класса есть поля, которые сами не реализуют Serializable (или содержат такие объекты), сериализация завершится с ошибкой. Например, поля типа Thread или Socket сделать «просто так» сериализуемыми нельзя.
Статические и transient-поля
- Статические поля (static) не сериализуются: они принадлежат классу, а не конкретному объекту.
- transient-поля — помеченные как transient явно исключаются из сериализации и после восстановления получают значения по умолчанию (null, 0 и т. п.).
Исключения
- Попытка сериализовать объект, который не реализует Serializable, вызывает NotSerializableException.
- При десериализации возможны ошибки: файл не найден, несовпадение классов, повреждённые данные и др.
Версии классов
Если вы измените структуру класса после сериализации (например, добавите/удалите поля), при десериализации может возникнуть InvalidClassException. Для контроля версий используется специальное поле serialVersionUID (о нём подробнее в одной из следующих лекций).
4. Практика: сериализация и десериализация объекта в файл
Допустим, у нас есть класс Person, и мы хотим сохранять список людей в файл и читать его обратно.
Класс Person (сериализуемый)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // не будет сериализовано
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
Сериализация списка людей
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Сохраняем список
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("Список людей сериализован.");
}
}
Десериализация списка людей
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Восстанавливаем список
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
Результат:
Alice (30), secret: null
Bob (25), secret: null
5. Типичные ошибки
Ошибка №1: Класс не реализует Serializable. Если забыть добавить implements Serializable, при попытке сериализации получите NotSerializableException. Это самая частая и простая ошибка.
Ошибка №2: Несериализуемое поле. Если объект содержит поле, которое не сериализуемо (например, Thread, Socket или любой другой тип без Serializable), сериализация «упадёт». Пометьте такие поля как transient или сделайте их сериализуемыми.
Ошибка №3: Изменение структуры класса. Если объект сериализован, а позже класс изменён (добавили/удалили поля), при чтении возможен InvalidClassException. Указывайте serialVersionUID для стабильного контроля версии.
Ошибка №4: Попытка сериализовать статические поля. Поля с модификатором static не сериализуются. После десериализации их значения будут такими, как в классе по умолчанию, а не теми, что были при сериализации.
Ошибка №5: Не закрыты потоки. Если не закрывать потоки после работы, можно получить повреждённый файл или утечку ресурсов. Используйте try-with-resources или явно закрывайте потоки.
Ошибка №6: Несовпадение классов. Если класс был переименован или перемещён в другой пакет, десериализация не сработает — требуется точное совпадение имени и пакета класса, сохранённого в потоке.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ