ObjectOutputStream.writeObject: примеры (JAVA)
ObjectOutputStream.writeObject(Object obj): voidОписание метода ObjectOutputStream.writeObject
Метод writeObject класса java.io.ObjectOutputStream служит для последовательной записи объектов в поток в формате Java-serializable. С его помощью создаётся сериализованное представление объекта (и связанных с ним объектов графа), которое затем может быть восстановлено с помощью ObjectInputStream.readObject().
Сигнатура
public final void writeObject(Object obj) throws IOException
Параметры
obj- любой объект Java. Объект должен быть сериализуемым: реализовать интерфейсjava.io.Serializableилиjava.io.Externalizable, либо быть заменён через механизмwriteReplace(). Поля, объявленные какtransientилиstatic, не включаются в стандартную сериализацию.
Возвращаемое значение
- Метод ничего не возвращает (void).
Поведение и особенности
- Записывается весь граф объектов, достижимый из переданного объекта, с сохранением ссылочной целостности: одинаковые объекты записываются один раз, циклические ссылки обрабатываются.
- Если один и тот же объект записывается повторно без вызова
reset(), повторные записи заменяются на контролируемые «хендлы» (handles), то есть при десериализации восстанавливаются ссылки на один и тот же объект. - Для записи без разделяемых хендлов применяется
writeUnshared(Object obj), который гарантирует, что при последующих чтениях будет создана отдельная копия. - Если класс реализует приватные методы
writeObject(ObjectOutputStream out)иreadObject(ObjectInputStream in), эти методы будут вызваны вместо стандартной сериализации для контроля процесса. - Можно реализовать
writeReplace()иreadResolve()для замены объекта при записи/чтении. - Наличие поля
serialVersionUIDвлияет на совместимость версий; при несоответствии возникаетInvalidClassException. - Серийный поток записывает служебный заголовок при создании
ObjectOutputStream. При повторном подключении к тому же потоку для записи новых объектов иногда применяетсяreset()или создание нового потока с подавлением заголовка (через подклассирование), но это - особая техника.
Исключения
NotSerializableException- если объект (или один из объектов в графе) не реализуетSerializableи не заменяется черезwriteReplace.InvalidClassException- при несоответствии версии класса и сериализованных данных.IOException- общие ошибки ввода/вывода.SecurityException- при ограничениях безопасности/политиках.
Когда используется
Метод применяется для долгосрочного сохранения состояния объектов, передачи объектов по сети (в клиент-серверных приложениях), реализации кешей/репликации объектов и других задач, где требуется восстановление состояния объектов Java в исходном виде. В современных приложениях рекомендуется оценивать риск безопасности и рассматривать альтернативы для межсервисного обмена.
Короткие примеры использования
1. Простая сериализация в файл
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
int age;
Person(String n, int a){name=n; age=a;}
}
public class SerSimple {
public static void main(String[] args) throws Exception{
Person p = new Person("Иван", 30);
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.bin"))){
oos.writeObject(p);
}
System.out.println("Файл создан: person.bin");
}
}
Файл создан: person.bin
2. Два раза записать один объект: shared handles
import java.io.*;
import java.nio.file.*;
public class SharedDemo{
public static void main(String[] args) throws Exception{
StringBuilder sb = new StringBuilder("data");
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("shared.bin"))){
oos.writeObject(sb);
oos.writeObject(sb); // будет ссылка, а не полное дублирование
}
byte[] b = Files.readAllBytes(Paths.get("shared.bin"));
System.out.println("Размер: " + b.length);
}
}
Размер: 76
3. writeUnshared - отдельные копии при каждом write
// отличия видны при десериализации (код аналогичен предыдущему),
// здесь только запись:
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("unshared.bin"))){
StringBuilder sb = new StringBuilder("data");
oos.writeUnshared(sb);
oos.writeUnshared(sb);
}
System.out.println("Запись с writeUnshared завершена");
Запись с writeUnshared завершена
4. Ошибка при попытке сериализовать несериализуемый класс
class NotSer { String s = "x"; }
// в main:
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bad.bin"))){
oos.writeObject(new NotSer());
} catch (Exception e){
e.printStackTrace();
}
java.io.NotSerializableException: NotSer at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) ... (стек вызовов)
Похожие механизмы в Java и их особенности
- writeUnshared(Object obj)
- Externalizable
- java.beans.XMLEncoder / XMLDecoder
- Custom marshal через DataOutput / DataInput
- Библиотеки сторонних форматов (JSON, protobuf, Kryo)
Гарантирует, что при последующих чтениях объект будет восстановлен как отдельная копия, даже если тот же объект записан ранее.
Интерфейс даёт полный контроль над сериализацией: требуется реализовать writeExternal и readExternal. Подходит, когда нужно явно управлять форматом записи.
Сериализация в человекочитаемый XML для JavaBean-объектов. Удобно для конфигураций, но ограничения по типам и совместимости.
Ручная сериализация с помощью примитивных методов (writeInt, writeUTF и т.д.). Подходит для простых, компактных форматов и межплатформенной совместимости.
Часто предпочтительнее для межсервисной передачи данных, производительности или компактности. Kryo обеспечивает быстрые бинарные потоки, protobuf - сжатую и версионируемую схему.
Альтернативы в других языках и отличия
PHP: serialize()/unserialize()
$a = ['name'=>'Иван', 'age'=>30];
$s = serialize($a);
file_put_contents('a.bin', $s);
echo $s;
a:2:{s:4:"name";s:8:"Иван";s:3:"age";i:30;}
JavaScript: JSON.stringify / structuredClone
const obj = { name: 'Иван', age: 30 };
const s = JSON.stringify(obj);
console.log(s);
{"name":"Иван","age":30}
JSON подходит для простых структур, не сохраняет методы и прототипы. Для копирования объектов с сохранением ссылочной целостности и более сложных типов используются другие подходы.
Python: pickle / json
import pickle
obj = {'name':'Иван', 'age':30}
with open('p.bin','wb') as f:
pickle.dump(obj, f)
print('ok')
ok
pickle сохраняет практически любые объекты Python, но имеет проблемы безопасности при загрузке из недоверенных источников, как и Java-сериализация.
C#: BinaryFormatter (устаревший) и System.Text.Json
// BinaryFormatter (устаревший) - раньше использовался для бинарной сериализации
// Современная рекомендация - использовать JSON или protobuf
(нет вывода)
Go: encoding/gob и encoding/json
package main
import (
"bytes"
"encoding/gob"
"fmt"
)
func main(){
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
v := map[string]int{"a":1}
enc.Encode(v)
fmt.Println("gob bytes:", buf.Len())
}
gob bytes: 39
gob похож на Java-serializable, но специфичен для Go. Для кроссплатформенной совместимости обычно используется JSON или protobuf.
Kotlin
Kotlin может использовать встроенную Java-сериализацию (тот же ObjectOutputStream) или библиотеку kotlinx.serialization, дающую схему и контроль версий.
Отличия от Java ObjectOutputStream
- Многие языки предлагают собственные механизмы с разными гарантиями совместимости и безопасностью.
- JSON ориентирован на человекочитаемость и кроссплатформенность, бинарные форматы (gob, protobuf, Kryo) - на производительность и компактность.
- Во всех языках при восстановлении объектов из недоверенных источников рекомендуется применять фильтрацию и проверку целостности.
Типичные ошибки и их проявления
1. NotSerializableException
import java.io.*;
class A { B b = new B(); }
class B { int x = 1; }
// в main:
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("err.bin"))){
oos.writeObject(new A());
} catch (Exception e){
e.printStackTrace();
}
java.io.NotSerializableException: A at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) ... (стек вызовов)
Причина: класс A или вложенный класс не реализует Serializable.
2. InvalidClassException (несовместимость serialVersionUID)
// сериализация с одним serialVersionUID, изменение класса без обновления UID
// при десериализации может возникнуть InvalidClassException
java.io.InvalidClassException: com.example.Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
3. WriteAbortedException при ошибке во время writeObject приватного метода writeObject класса
private void writeObject(ObjectOutputStream out) throws IOException{
throw new IOException("fail");
}
// при записи получим:
java.io.WriteAbortedException: writing aborted; java.io.IOException: fail at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) ... (стек)
4. StreamCorruptedException
Возникает при попытке десериализовать повреждённые или несоответствующие данные (например, нарушение формата потока).
5. Неправильное использование handle table: ожидание обновления объекта после изменения полей
Если объект изменяется между двумя вызовами writeObject без вызова reset(), при десериализации две записи могут ссылаться на один объект с состоянием на момент первой записи. Для получения актуального состояния потребуется reset() или writeUnshared.
Изменения и рекомендации в последних версиях Java
- Начиная с Java 9 добавлен API
ObjectInputFilterдля фильтрации входных сериализованных данных и уменьшения рисков при десериализации. - В последних выпусках платформы усиливается внимание к безопасности Java-сериализации: рекомендуется избегать десериализации из недоверенных источников и применять фильтры, либо переходить на альтернативные форматы (JSON, protobuf, CBOR и т.д.).
- Сам метод
writeObjectAPI не претерпел крупных изменений, но экосистема и практика использования смещаются в сторону явных, безопасных форматов и библиотек.
Расширенные и редко встречающиеся примеры
1. Пользовательская сериализация через приватный writeObject/readObject
import java.io.*;
class SecureData implements Serializable{
private static final long serialVersionUID = 1L;
private transient String secret; // не сохраняется по умолчанию
SecureData(String s){ secret = s; }
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
// минимальная «маскировка» перед записью
out.writeUTF(secret == null ? "" : new StringBuilder(secret).reverse().toString());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
String rev = in.readUTF();
secret = new StringBuilder(rev).reverse().toString();
}
public String toString(){ return "secret="+secret; }
}
public class CustSer{
public static void main(String[] args) throws Exception{
SecureData sd = new SecureData("topsecret");
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sdat.bin"))){
oos.writeObject(sd);
}
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("sdat.bin"))){
System.out.println(ois.readObject());
}
}
}
secret=topsecret
2. Использование writeReplace / readResolve для прокси-сериализации
import java.io.*;
class Singleton implements Serializable{
private static final long serialVersionUID = 1L;
static final Singleton INST = new Singleton();
private Object readResolve(){ return INST; }
}
// при десериализации всегда будет возвращён Singleton.INST
(при десериализации возвращается ссылка на Singleton.INST)
3. Сброс таблицы хендлов для повторной сериализации (reset)
// Если нужно повторно записать текущую версию объекта как новый объект в том же потоке
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.bin"));
MyObj o = new MyObj(1);
oos.writeObject(o);
o.setX(2);
oos.reset(); // очищает хендлы
oos.writeObject(o); // теперь при десериализации будет два отдельных состояния
oos.close();
(поток содержит две независимые записи объекта)
4. Сжатая и зашифрованная сериализация
// Запись в GZIP и затем в файл
try(OutputStream fos = new FileOutputStream("c.gz");
BufferedOutputStream bos = new BufferedOutputStream(fos);
java.util.zip.GZIPOutputStream gz = new java.util.zip.GZIPOutputStream(bos);
ObjectOutputStream oos = new ObjectOutputStream(gz)){
oos.writeObject(new Person("Анна",25));
}
System.out.println("Записано в c.gz");
Записано в c.gz
5. Подкласс ObjectOutputStream для подавления заголовка потока
// Когда нужно продолжать писать в тот же файл несколькими запусками без второго заголовка
class AppendedObjectOutputStream extends ObjectOutputStream{
AppendedObjectOutputStream(OutputStream out) throws IOException{ super(out); }
protected void writeStreamHeader() throws IOException{ /* пусто */ }
}
// Первый запуск создаёт поток обычным ООС, последующие используют подкласс
(позволяет добавлять объекты в файл без второго заголовка)
6. Сетевой пример: отправка и получение объектов по сокету
// Сервер
ServerSocket ss = new ServerSocket(12345);
Socket s = ss.accept();
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeObject(new Person("Клиент", 40));
oos.close(); s.close(); ss.close();
// Клиент использует ObjectInputStream и readObject
(объект доставлен клиенту и десериализован)
Каждый пример иллюстрирует практические приемы: контроль формата через writeObject/readObject, управление ссылочной целостностью через reset/writeUnshared, интеграция с потоками сжатия/шифрования и адаптация поведения создания заголовка через подклассирование.
джава ObjectOutputStream.writeObject function comments
- джава ObjectOutputStream.writeObject - аргументы и возвращаемое значение
- Функция java ObjectOutputStream.writeObject - описание
- ObjectOutputStream.writeObject - примеры
- ObjectOutputStream.writeObject - похожие методы на java
- ObjectOutputStream.writeObject на javascript, c#, python, php
- ObjectOutputStream.writeObject изменения java
- Примеры ObjectOutputStream.writeObject на джава