Альтернативы Java Native Serialization
Java Native Serialization — не единственный и далеко не лучший выбор для большинства задач. Современные системы используют специализированные форматы, которые превосходят стандартный механизм по скорости, размеру данных и совместимости.
Текстовые форматы
JSON (JavaScript Object Notation) — текстовый формат, ставший де-факто стандартом для веб-API. Он человекочитаем, поддерживается всеми языками, но избыточен: ключи повторяются в каждом объекте, числа хранятся как строки, нет типизации.
XML (eXtensible Markup Language) — строго типизированный текстовый формат с поддержкой схем (XSD). Используется в enterprise-системах, SOAP-сервисах, конфигурациях. Избыточен по размеру и медленнее JSON в парсинге.
YAML (YAML Ain't Markup Language) — текстовый формат, ориентированный на читаемость человеком. Широко используется для конфигураций (Kubernetes, Docker Compose, Spring Boot
Бинарные форматы
Protocol Buffers (protobuf) — бинарный формат сериализации от Google. Основан на схемах (
Apache Avro — бинарный формат от Apache Hadoop ecosystem. Отличается от protobuf тем, что схема передаётся вместе с данными (или хранится в реестре схем, Schema Registry). Это делает Avro идеальным для потоковой обработки (Kafka), где потребители могут не знать заранее точную версию схемы. Avro использует схему для записи (writer schema) и схему для чтения (reader schema), разрешение совместимости происходит на лету.
Schema Registry — это сервис (например, Confluent Schema Registry), который хранит версии схем Avro/Protobuf/JSON Schema. Производители регистрируют схему и получают ID. Потребители по ID получают схему и десериализуют данные. Это централизованное управление эволюцией форматов.
Kryo — высокопроизводительная библиотека сериализации для Java. Не требует
FlatBuffers — бинарный формат от Google, оптимизированный для десериализации без парсинга. Данные хранятся в виде плоского буфера с прямым доступом к полям по смещению. Не требует выделения объектов при чтении — идеален для игр и embedded-систем с жёсткими ограничениями по памяти и GC.
Сравнение подходов
Java Native Serialization — это механизм, встроенный в JVM, но он имеет серьёзные недостатки: медленный (рефлексия, метаданные классов в потоке), объёмный (полные имена классов, дескрипторы полей), небезопасный (десериализация произвольных классов может выполнить вредоносный код) и жёстко привязан к Java. В современных микросервисных архитектурах его использование считается антипаттерном, за исключением специфических сценариев (RMI-наследие, кэширование сессий в Tomcat).
JSON доминирует в веб-интеграции благодаря читаемости и универсальности. Protobuf и Avro — выбор для высоконагруженных внутренних коммуникаций и потоковой обработки. Kryo — для纯 Java-систем, где нужна максимальная скорость сериализации в памяти.
#Java #для_новичков #beginner #IO #NIO #Serialize
Java Native Serialization — не единственный и далеко не лучший выбор для большинства задач. Современные системы используют специализированные форматы, которые превосходят стандартный механизм по скорости, размеру данных и совместимости.
Текстовые форматы
JSON (JavaScript Object Notation) — текстовый формат, ставший де-факто стандартом для веб-API. Он человекочитаем, поддерживается всеми языками, но избыточен: ключи повторяются в каждом объекте, числа хранятся как строки, нет типизации.
// Jackson — самая популярная библиотека для JSON в Java
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonExample {
private static final ObjectMapper mapper = new ObjectMapper();
public String toJson(User user) throws JsonProcessingException {
return mapper.writeValueAsString(user);
}
public User fromJson(String json) throws JsonProcessingException {
return mapper.readValue(json, User.class);
}
}
XML (eXtensible Markup Language) — строго типизированный текстовый формат с поддержкой схем (XSD). Используется в enterprise-системах, SOAP-сервисах, конфигурациях. Избыточен по размеру и медленнее JSON в парсинге.
YAML (YAML Ain't Markup Language) — текстовый формат, ориентированный на читаемость человеком. Широко используется для конфигураций (Kubernetes, Docker Compose, Spring Boot
application.yml). Поддерживает ссылки (anchors), что позволяет представлять циклические графы.Бинарные форматы
Protocol Buffers (protobuf) — бинарный формат сериализации от Google. Основан на схемах (
.proto файлы), строго типизирован, компактен, быстр. Каждое поле имеет номер (tag), и в потоке хранятся только заполненные поля. Поддерживает обратную и прямую совместимость: можно добавлять поля, не ломая старых клиентов.// user.proto
syntax = "proto3";
message User {
string name = 1;
int32 age = 2;
string email = 3;
}
// Сгенерированный Java-код из .proto
User user = User.newBuilder()
.setName("Alice")
.setAge(30)
.setEmail("alice@example.com")
.build();
byte[] bytes = user.toByteArray();
User parsed = User.parseFrom(bytes);
Apache Avro — бинарный формат от Apache Hadoop ecosystem. Отличается от protobuf тем, что схема передаётся вместе с данными (или хранится в реестре схем, Schema Registry). Это делает Avro идеальным для потоковой обработки (Kafka), где потребители могут не знать заранее точную версию схемы. Avro использует схему для записи (writer schema) и схему для чтения (reader schema), разрешение совместимости происходит на лету.
Schema Registry — это сервис (например, Confluent Schema Registry), который хранит версии схем Avro/Protobuf/JSON Schema. Производители регистрируют схему и получают ID. Потребители по ID получают схему и десериализуют данные. Это централизованное управление эволюцией форматов.
Kryo — высокопроизводительная библиотека сериализации для Java. Не требует
Serializable, работает напрямую с полями объектов через рефлексию. Значительно быстрее Java Native Serialization и компактнее. Используется в игровых движках, Apache Spark, Akka. Недостаток: привязан к Java, не кроссплатформенный.import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
public class KryoExample {
private final Kryo kryo = new Kryo();
public byte[] serialize(Object obj) {
Output output = new Output(1024, -1);
kryo.writeClassAndObject(output, obj);
return output.toBytes();
}
public Object deserialize(byte[] bytes) {
Input input = new Input(bytes);
return kryo.readClassAndObject(input);
}
}
FlatBuffers — бинарный формат от Google, оптимизированный для десериализации без парсинга. Данные хранятся в виде плоского буфера с прямым доступом к полям по смещению. Не требует выделения объектов при чтении — идеален для игр и embedded-систем с жёсткими ограничениями по памяти и GC.
Сравнение подходов
Java Native Serialization — это механизм, встроенный в JVM, но он имеет серьёзные недостатки: медленный (рефлексия, метаданные классов в потоке), объёмный (полные имена классов, дескрипторы полей), небезопасный (десериализация произвольных классов может выполнить вредоносный код) и жёстко привязан к Java. В современных микросервисных архитектурах его использование считается антипаттерном, за исключением специфических сценариев (RMI-наследие, кэширование сессий в Tomcat).
JSON доминирует в веб-интеграции благодаря читаемости и универсальности. Protobuf и Avro — выбор для высоконагруженных внутренних коммуникаций и потоковой обработки. Kryo — для纯 Java-систем, где нужна максимальная скорость сериализации в памяти.
#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
Критерии выбора формата сериализации
Производительность
Производительность измеряется по трём метрикам: скорость сериализации (объект → байты), скорость десериализации (байты → объект) и размер полученного сообщения.
Java Native Serialization медленна из-за рефлексии и записи метаданных класса для каждого объекта. Protobuf и Kryo в 10–100 раз быстрее. JSON медленнее бинарных форматов из-за парсинга текста и boxing'а чисел.
Boxing — это автоматическая упаковка примитивных типов в объектные обёртки. В JSON число 42 парсится как строка, затем преобразуется в int, что требует дополнительных операций по сравнению с прямым чтением 4 байт из бинарного потока.
Читаемость
Текстовые форматы (JSON, XML, YAML) человекочитаемы, что упрощает отладку, логирование и ручное тестирование API. Бинарные форматы требуют специальных инструментов (hex-редакторов, декодеров protobuf) для анализа. В production-системах читаемость часто жертвуется ради производительности, но для конфигураций, API-спецификаций и логов предпочтительны текстовые форматы.
Версионность (эволюция схем)
Версионность — способность формата адаптироваться к изменениям структуры данных без поломки существующих клиентов и серверов.
Java Native Serialization требует ручного управления
Wire-совместимость (wire compatibility) — это свойство формата, при котором изменение схемы не нарушает формат бинарного потока. Например, в protobuf изменение int32 на int64 wire-совместимо, так как оба типа кодируются как varint, но семантически может привести к truncation на старых клиентах.
Безопасность
Безопасность — критичнейший критерий, особенно для десериализации данных из недоверенных источников.
Java Native Serialization имеет печальную репутацию: десериализация
CVE (Common Vulnerabilities and Exposures) — это стандартизированный каталог публично известных уязвимостей информационной безопасности. Каждой уязвимости присваивается уникальный идентификатор, например CVE-2015-4852 (уязвимость десериализации в Oracle WebLogic).
ObjectInputFilter — интерфейс, введённый в Java 9 (JEP 290), позволяющий задавать белые и чёрные списки классов для десериализации, ограничивать глубину графа объектов, количество ссылок и размер данных.
JSON безопаснее, так как парсер создаёт только простые структуры (строки, числа, массивы, объекты), не вызывая произвольных конструкторов Java. Однако и JSON имеет уязвимости: XXE (XML External Entity) при использовании небезопасных XML-парсеров, или DOS-атаки через Billion Laughs в YAML/XML.
XXE (XML External Entity) — тип атаки на XML-парсеры, при котором злоумышленник внедряет внешнюю сущность в XML-документ, заставляя парсер обращаться к внутренним файлам сервера или внешним ресурсам.
Protobuf и Avro безопасны по дизайну: они не поддерживают произвольную загрузку классов, работают только со схемой, строго типизированы и не позволяют встроить исполняемый код в сообщение.
Кроссплатформенность
Если система полиглотная (использует несколько языков программирования), Java Native Serialization и Kryo неприменимы — они привязаны к JVM. JSON, XML, protobuf, Avro, FlatBuffers имеют реализации на десятках языков и являются lingua franca для межсервисного взаимодействия.
Полиглотная система (polyglot system) — это система, построенная с использованием нескольких языков программирования. Например, backend на Java/Go, ML-сервис на Python, frontend на JavaScript. В таких системах формат обмена данных должен быть языконезависимым.
Размер данных
Бинарные форматы значительно компактнее текстовых. Например, объект с тремя полями в JSON может занимать 100 байт из-за повторяющихся ключей и скобок. Тот же объект в protobuf — 10–20 байт. Это критично для мобильных приложений, IoT-устройств и высоконагруженных сетевых протоколов.
#Java #для_новичков #beginner #IO #NIO #Serialize
Производительность
Производительность измеряется по трём метрикам: скорость сериализации (объект → байты), скорость десериализации (байты → объект) и размер полученного сообщения.
Java Native Serialization медленна из-за рефлексии и записи метаданных класса для каждого объекта. Protobuf и Kryo в 10–100 раз быстрее. JSON медленнее бинарных форматов из-за парсинга текста и boxing'а чисел.
Boxing — это автоматическая упаковка примитивных типов в объектные обёртки. В JSON число 42 парсится как строка, затем преобразуется в int, что требует дополнительных операций по сравнению с прямым чтением 4 байт из бинарного потока.
Читаемость
Текстовые форматы (JSON, XML, YAML) человекочитаемы, что упрощает отладку, логирование и ручное тестирование API. Бинарные форматы требуют специальных инструментов (hex-редакторов, декодеров protobuf) для анализа. В production-системах читаемость часто жертвуется ради производительности, но для конфигураций, API-спецификаций и логов предпочтительны текстовые форматы.
Версионность (эволюция схем)
Версионность — способность формата адаптироваться к изменениям структуры данных без поломки существующих клиентов и серверов.
Java Native Serialization требует ручного управления
serialVersionUID и ломается при изменении сигнатуры класса (удаление/переименование полей, изменение типов). Protobuf поддерживает добавление полей (старые клиенты игнорируют неизвестные теги), удаление полей (если они не обязательны), изменение типов в рамках wire-совместимости. Avro поддерживает схемы чтения и записи разных версий с автоматическим разрешением различий.Wire-совместимость (wire compatibility) — это свойство формата, при котором изменение схемы не нарушает формат бинарного потока. Например, в protobuf изменение int32 на int64 wire-совместимо, так как оба типа кодируются как varint, но семантически может привести к truncation на старых клиентах.
Безопасность
Безопасность — критичнейший критерий, особенно для десериализации данных из недоверенных источников.
Java Native Serialization имеет печальную репутацию: десериализация
ObjectInputStream.readObject() может выполнить произвольный код, если в classpath есть классы с уязвимыми конструкторами, методами readObject() или ObjectInputValidation. Это привело к множеству критических CVE (Common Vulnerabilities and Exposures). Начиная с Java 9, добавлен механизм JEP 290 — фильтрация входящих классов через ObjectInputFilter.CVE (Common Vulnerabilities and Exposures) — это стандартизированный каталог публично известных уязвимостей информационной безопасности. Каждой уязвимости присваивается уникальный идентификатор, например CVE-2015-4852 (уязвимость десериализации в Oracle WebLogic).
// JEP 290: фильтрация десериализации с Java 9
public void safeDeserialize(InputStream in) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(in);
// Разрешаем десериализовать только классы из доверенных пакетов
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"maxdepth=5;maxrefs=10000;com.myapp.*;!*"
);
ois.setObjectInputFilter(filter);
Object obj = ois.readObject();
}
ObjectInputFilter — интерфейс, введённый в Java 9 (JEP 290), позволяющий задавать белые и чёрные списки классов для десериализации, ограничивать глубину графа объектов, количество ссылок и размер данных.
JSON безопаснее, так как парсер создаёт только простые структуры (строки, числа, массивы, объекты), не вызывая произвольных конструкторов Java. Однако и JSON имеет уязвимости: XXE (XML External Entity) при использовании небезопасных XML-парсеров, или DOS-атаки через Billion Laughs в YAML/XML.
XXE (XML External Entity) — тип атаки на XML-парсеры, при котором злоумышленник внедряет внешнюю сущность в XML-документ, заставляя парсер обращаться к внутренним файлам сервера или внешним ресурсам.
Protobuf и Avro безопасны по дизайну: они не поддерживают произвольную загрузку классов, работают только со схемой, строго типизированы и не позволяют встроить исполняемый код в сообщение.
Кроссплатформенность
Если система полиглотная (использует несколько языков программирования), Java Native Serialization и Kryo неприменимы — они привязаны к JVM. JSON, XML, protobuf, Avro, FlatBuffers имеют реализации на десятках языков и являются lingua franca для межсервисного взаимодействия.
Полиглотная система (polyglot system) — это система, построенная с использованием нескольких языков программирования. Например, backend на Java/Go, ML-сервис на Python, frontend на JavaScript. В таких системах формат обмена данных должен быть языконезависимым.
Размер данных
Бинарные форматы значительно компактнее текстовых. Например, объект с тремя полями в JSON может занимать 100 байт из-за повторяющихся ключей и скобок. Тот же объект в protobuf — 10–20 байт. Это критично для мобильных приложений, IoT-устройств и высоконагруженных сетевых протоколов.
#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
Путь байтов в памяти JVM при сериализации
Сериализация: от объекта к байтам
Когда вы вызываете
Объект в куче. Объект
Дескриптор класса (полное имя класса,
Типы и значения полей (примитивы записываются как есть, объекты — рекурсивно)
Для каждого нового класса в иерархии — его дескриптор
Обход графа и отслеживание ссылок.
Буферизация.
ByteArrayOutputStream. Если сериализация идёт в память,
Возвращаемый массив.
Работа GC. Все временные объекты — внутренние буферы
ObjectStreamClass — это внутренний класс JVM, представляющий метаданные сериализуемого класса: имя,
Десериализация: от байтов к объекту
При
Байты в куче. Исходный массив байтов или поток находится в куче (если это
Чтение дескрипторов.
Создание объекта без конструктора. JVM выделяет память под объект в Young Generation (Eden) через механизм
Заполнение полей.
Восстановление графа. Если в потоке есть backreference (ссылка на ранее десериализованный объект),
Вызов readObject(). Если класс реализует
Работа GC. Вновь созданные объекты размещаются в Eden. Если десериализуемый граф большой (миллионы объектов), они могут быстро заполнить Young Generation и вызвать Minor GC в процессе десериализации. Если объекты сохраняются в долгоживущую структуру (коллекцию, кэш), они переходят в Old Generation.
Потоковая сериализация и GC
При сериализации в
Буфер
Буфер в куче переиспользуется — новые объекты не создаются при каждом сбросе. Это снижает давление на GC.
Если сериализация выполняется в цикле с высокой частотой (например, в RPC-сервере, обрабатывающем 10 000 запросов в секунду), объекты
Оптимизация: пул переиспользуемых потоков. В высоконагруженных системах
В этом примере
#Java #для_новичков #beginner #IO #NIO #Serialize
Сериализация: от объекта к байтам
Когда вы вызываете
ObjectOutputStream.writeObject(user):Объект в куче. Объект
user и все объекты, на которые он ссылается (глубокий граф), находятся в куче (Heap). ObjectOutputStream обходит граф, начиная с корневого объекта. Для каждого объекта он записывает:Дескриптор класса (полное имя класса,
serialVersionUID, флаги)Типы и значения полей (примитивы записываются как есть, объекты — рекурсивно)
Для каждого нового класса в иерархии — его дескриптор
Обход графа и отслеживание ссылок.
ObjectOutputStream поддерживает таблицу handle — целочисленных идентификаторов для уже записанных объектов. Если в графе есть циклические ссылки (A ссылается на B, B ссылается на A) или повторяющиеся ссылки, второе вхождение объекта записывается не полностью, а как backreference (handle ранее записанного объекта). Это предотвращает бесконечную рекурсию и дублирование.Буферизация.
ObjectOutputStream пишет данные во внутренний буфер byte[] (обычно 512 байт или больше). Этот буфер создаётся в Young Generation, в Eden. Когда буфер заполняется, он сбрасывается (flush) в нижележащий поток: ByteArrayOutputStream, FileOutputStream или SocketOutputStream.ByteArrayOutputStream. Если сериализация идёт в память,
ByteArrayOutputStream накапливает байты в динамически расширяющемся массиве. При превышении ёмкости создаётся новый массив вдвое большего размера, данные копируются, старый массив становится мусором. Это может породить несколько временных массивов в Young Generation, которые собираются Minor GC.Возвращаемый массив.
baos.toByteArray() создаёт новый массив точного размера и копирует в него данные. Исходный внутренний буфер ByteArrayOutputStream остаётся в куче до сборки мусора. Итоговый массив байтов — это объект в куче, который может быть передан по сети, сохранён в файл или закэширован.Работа GC. Все временные объекты — внутренние буферы
ObjectOutputStream, промежуточные массивы ByteArrayOutputStream, объекты ObjectStreamClass (метаданные классов) — создаются в Young Generation. При короткой сериализации они уничтожаются при следующей Minor GC. Если сериализация выполняется в цикле (например, сериализация 10 000 объектов для пакетной отправки), буферы могут пережить несколько циклов Minor GC и мигрировать в Survivor Space, а затем в Old Generation.ObjectStreamClass — это внутренний класс JVM, представляющий метаданные сериализуемого класса: имя,
serialVersionUID, поля, методы writeObject/readObject. Эти объекты кэшируются внутри ObjectOutputStream для повторного использования, но при сериализации множества разных классов они накапливаются.Десериализация: от байтов к объекту
При
ObjectInputStream.readObject():Байты в куче. Исходный массив байтов или поток находится в куче (если это
ByteArrayInputStream) или читается из нативного буфера (если это FileInputStream или SocketInputStream).Чтение дескрипторов.
ObjectInputStream читает заголовок потока (magic number, версия протокола), затем дескриптор класса. Он ищет класс в Metaspace через Class.forName(). Если класс не загружен, ClassLoader загружает его байткод в Metaspace.Создание объекта без конструктора. JVM выделяет память под объект в Young Generation (Eden) через механизм
sun.misc.Unsafe.allocateInstance() (или аналог в новых версиях). Это выделение памяти без вызова конструктора — объект создаётся "сырым", с полями в состоянии по умолчанию (null, 0, false).Заполнение полей.
ObjectInputStream читает значения полей из потока и записывает их напрямую в память объекта (через JNI или Unsafe). Для объектных полей — рекурсивная десериализация. Для transient-полей — значение не читается, остаётся по умолчанию.Восстановление графа. Если в потоке есть backreference (ссылка на ранее десериализованный объект),
ObjectInputStream подставляет ссылку на существующий объект из таблицы handles. Это восстанавливает точную топологию исходного графа, включая циклы.Вызов readObject(). Если класс реализует
private void readObject(ObjectInputStream in), JVM вызывает этот метод после заполнения полей. Это позволяет выполнить пост-инициализацию: восстановить transient-поля, вычислить производные значения, проверить инварианты.Работа GC. Вновь созданные объекты размещаются в Eden. Если десериализуемый граф большой (миллионы объектов), они могут быстро заполнить Young Generation и вызвать Minor GC в процессе десериализации. Если объекты сохраняются в долгоживущую структуру (коллекцию, кэш), они переходят в Old Generation.
Потоковая сериализация и GC
При сериализации в
FileOutputStream или SocketOutputStream:Буфер
ObjectOutputStream (в куче) периодически сбрасывается. Сброс вызывает системный вызов write() (POSIX), который копирует байты из кучи JVM в нативную память ядра (кэш страниц или сетевой буфер).Буфер в куче переиспользуется — новые объекты не создаются при каждом сбросе. Это снижает давление на GC.
Если сериализация выполняется в цикле с высокой частотой (например, в RPC-сервере, обрабатывающем 10 000 запросов в секунду), объекты
ObjectOutputStream и ObjectInputStream создаются и закрываются для каждого запроса. Каждый такой объект — это десятки килобайт в куче. При интенсивной нагрузке они быстро заполняют Eden и вызывают частые Minor GC.Оптимизация: пул переиспользуемых потоков. В высоконагруженных системах
ObjectOutputStream создаётся один раз и сбрасывается через reset() между запросами. Метод reset() очищает внутренние таблицы handles, позволяя сериализовать новый граф объектов без создания нового потока.public class PooledSerializer {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
private final ObjectOutputStream oos;
public PooledSerializer() throws IOException {
this.oos = new ObjectOutputStream(baos);
}
public synchronized byte[] serialize(Object obj) throws IOException {
baos.reset(); // сбрасываем буфер, не создавая новый
oos.reset(); // сбрасываем таблицу handles
oos.writeObject(obj);
oos.flush();
return baos.toByteArray(); // копирует текущий буфер
}
}В этом примере
baos.reset() очищает счётчик байтов, но не освобождает внутренний массив. oos.reset() очищает таблицу handles, позволяя сериализовать новые объекты без утечки памяти. Это существенно снижает давление на GC в сценариях с высокой частотой сериализации.#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
Что выведет код?
#Tasks
import java.io.*;
public class Task290726 {
public static void main(String[] args) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
MyObject290726 obj = new MyObject290726("Hello");
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
MyObject290726 deserialized = (MyObject290726) ois.readObject();
ois.close();
System.out.println(deserialized.value);
}
static class MyObject290726 implements Serializable {
transient String value;
MyObject290726(String value) { this.value = value; }
}
}
#Tasks
👍3
Варианты ответа:
Anonymous Quiz
22%
Hello
33%
null
22%
Ошибка компиляции
22%
Исключение InvalidClassException
👍3
👍1
Что такое 🤓
Ответ:
Java 9 добавила перегрузку Stream.iterate(T seed, Predicate hasNext, UnaryOperator next).
Это эквивалент цикла for (seed; hasNext(seed); seed = next(seed)).
Например:
Stream.iterate(0, i -> i < 10, i -> i + 1)
.forEach(System.out::println).
Позволяет генерировать последовательности с условием остановки без необходимости использовать limit() или takeWhile().
Это делает код более читаемым для математических итераций.
#собеседование
Stream.iterate() с предикатом? Ответ:
Это эквивалент цикла for (seed; hasNext(seed); seed = next(seed)).
Например:
Stream.iterate(0, i -> i < 10, i -> i + 1)
.forEach(System.out::println).
Позволяет генерировать последовательности с условием остановки без необходимости использовать limit() или takeWhile().
Это делает код более читаемым для математических итераций.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Опубликована новая статья!
Только завтра она выйдет тут и читать ее будет менее удобно...
В будущем планирую вообще уйти в сторону публикаций объемных материалов на своем сайте (вот ток для мобилок отображение поправлю).
Напоминаю, ребят, вы можете оставлять рецензии и комментарии под ними! Не стесняйтесь))
Кроме того никто не запрещает вам публиковать свои статьи)) Дерзайте (в рамках разумного конечно😂)
Только завтра она выйдет тут и читать ее будет менее удобно...
В будущем планирую вообще уйти в сторону публикаций объемных материалов на своем сайте (вот ток для мобилок отображение поправлю).
Напоминаю, ребят, вы можете оставлять рецензии и комментарии под ними! Не стесняйтесь))
Кроме того никто не запрещает вам публиковать свои статьи)) Дерзайте (в рамках разумного конечно😂)
👍4
История технологии сегодня — 30 июля
ℹ️ Кто родился в этот день
Клайв Марльз Синклер (англ. Clive Marles Sinclair; 30 июля 1940 — 16 сентября 2021) — британский предприниматель, изобретатель и бывший владелец компании, которая выпустила микрокомпьютер ZX Spectrum в 1982 году.
Ге́нри Форд (англ. Henry Ford; 30 июля 1863 — 7 апреля 1947) — американский промышленник, владелец заводов по производству автомобилей по всему миру, изобретатель, рационализатор, организатор производства, автор 161 патента США. Его лозунг — «автомобиль для всех»; завод Форда выпускал наиболее дешёвые автомобили в начале эпохи автомобилестроения. Компания «Ford Motor Company» существует по сей день.
🌐 Знаковые события
1970 — компания IBM анонсировала новую серию мейнфреймов System/370.
#Biography #Birth_Date #Events #30июля
Клайв Марльз Синклер (англ. Clive Marles Sinclair; 30 июля 1940 — 16 сентября 2021) — британский предприниматель, изобретатель и бывший владелец компании, которая выпустила микрокомпьютер ZX Spectrum в 1982 году.
Ге́нри Форд (англ. Henry Ford; 30 июля 1863 — 7 апреля 1947) — американский промышленник, владелец заводов по производству автомобилей по всему миру, изобретатель, рационализатор, организатор производства, автор 161 патента США. Его лозунг — «автомобиль для всех»; завод Форда выпускал наиболее дешёвые автомобили в начале эпохи автомобилестроения. Компания «Ford Motor Company» существует по сей день.
1970 — компания IBM анонсировала новую серию мейнфреймов System/370.
#Biography #Birth_Date #Events #30июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
[Совет по Java #066]
Тема:
Проблема: Многие разработчики воспринимают
Однако метод всегда создает новый массив заданной длины, копируя в него элементы из исходного. Исходный массив остается неизменным.
При этом, если новый размер больше исходного, недостающие элементы заполняются значениями по умолчанию (0 для примитивов,
Решение: Используйте
Результат всегда присваивайте новой переменной. Если вы хотите расширить существующий массив и продолжить использовать его в том же контексте, необходимо явно переприсвоить переменную:
Также стоит помнить, что
Объяснение:
Исходный массив не затрагивается, так как метод получает его копию ссылки и не изменяет объект массива. При увеличении размера заполнение значениями по умолчанию гарантирует, что массив не содержит мусора. Для примитивов это безопасно, для объектов — добавляются
Поверхностное копирование означает, что массив-результат содержит те же ссылки, что и исходный, поэтому изменение состояния объекта отразится в обоих массивах. Если требуется независимая копия объектов, необходимо клонировать каждый элемент отдельно.
#Java #советы
Тема:
Arrays.copyOf создает новый массив с новым размером, не меняет исходный.Проблема: Многие разработчики воспринимают
Arrays.copyOf как метод, модифицирующий исходный массив, особенно при увеличении или уменьшении его длины. Однако метод всегда создает новый массив заданной длины, копируя в него элементы из исходного. Исходный массив остается неизменным.
При этом, если новый размер больше исходного, недостающие элементы заполняются значениями по умолчанию (0 для примитивов,
null для объектов). Если размер меньше, копируются только первые элементы, остальные отбрасываются. Эта семантика часто упускается из виду, что приводит к неожиданным последствиям: ссылки на исходный массив остаются старыми, а изменения в новом массиве не отражаются на старом. Также важно помнить, что для массивов объектов копируются ссылки, а не сами объекты (поверхностное копирование), поэтому изменение объектов внутри скопированного массива затронет и исходный, если они ссылаются на одни и те же экземпляры.Решение: Используйте
Arrays.copyOf для создания новой копии массива с измененной длиной. Результат всегда присваивайте новой переменной. Если вы хотите расширить существующий массив и продолжить использовать его в том же контексте, необходимо явно переприсвоить переменную:
arr = Arrays.copyOf(arr, newLength). При работе с объектами учитывайте, что мутабельность элементов влияет на обе копии. Для глубокого копирования объектов потребуется дополнительная логика. Для простого копирования всей длины или части массива можно использовать Arrays.copyOf или Arrays.copyOfRange. Также стоит помнить, что
Arrays.copyOf внутри использует System.arraycopy, поэтому он эффективен.import java.util.Arrays;
public class ArraysCopyOfExample {
public static void main(String[] args) {
int[] original = {1, 2, 3};
//Копирование с увеличением длины
int[] expanded = Arrays.copyOf(original, 5);
System.out.println("Expanded: " + Arrays.toString(expanded)); // [1, 2, 3, 0, 0]
System.out.println("Original unchanged: " + Arrays.toString(original)); // [1, 2, 3]
//Копирование с уменьшением длины
int[] truncated = Arrays.copyOf(original, 2);
System.out.println("Truncated: " + Arrays.toString(truncated)); // [1, 2]
//Для объектов — копируются ссылки
class Person { String name; Person(String n) { name = n; } }
Person[] people = {new Person("Alice"), new Person("Bob")};
Person[] copy = Arrays.copyOf(people, 3);
copy[0].name = "Charlie"; // изменяем объект через копию
System.out.println("Original[0].name: " + people[0].name); // Charlie — изменилось!
// Это поверхностное копирование, объекты общие
//Присвоение результата обратно для замены массива
int[] numbers = {10, 20, 30};
numbers = Arrays.copyOf(numbers, numbers.length + 1);
numbers[3] = 40;
System.out.println("After reassign: " + Arrays.toString(numbers)); // [10, 20, 30, 40]
}
}
Объяснение:
Arrays.copyOf — это удобная обертка над System.arraycopy, которая выделяет новый массив нужной длины и заполняет его. Исходный массив не затрагивается, так как метод получает его копию ссылки и не изменяет объект массива. При увеличении размера заполнение значениями по умолчанию гарантирует, что массив не содержит мусора. Для примитивов это безопасно, для объектов — добавляются
null. Поверхностное копирование означает, что массив-результат содержит те же ссылки, что и исходный, поэтому изменение состояния объекта отразится в обоих массивах. Если требуется независимая копия объектов, необходимо клонировать каждый элемент отдельно.
#Java #советы
👍4
Что выведет данный код?
#Tasks
import java.util.Arrays;
public class Task300726 {
static class Item300726 {
int value;
Item300726(int v) { value = v; }
}
public static void main(String[] args) {
Item300726[] original = {new Item300726(1), new Item300726(2)};
Item300726[] copy = Arrays.copyOf(original, 2);
copy[0].value = 10;
copy[1] = new Item300726(20);
System.out.println(original[0].value);
System.out.println(original[1].value);
}
}
#Tasks
👍3
17.Идемпотентность в микросервисах: все способы защиты от повторной обработки
В этом видео мы разбираем фундаментальную проблему распределённых систем: как гарантировать, что операция выполнится ровно один раз, несмотря на сетевые сбои, ретраи и асинхронные коммуникации. Мы проходим путь от осознания неизбежности повторов до построения надёжной архитектуры с использованием нескольких паттернов идемпотентности.
Что вы узнаете:
- Что такое идемпотентность и почему она бывает технической и бизнес.
- Как в Kafka и Kafka Connect рождаются дубли (и почему идемпотентный продюсер здесь не спасает).
- Почему нельзя просто отключить ретраи – теоретический предел at‑least‑once.
- Четыре способа защиты от дублей в реальном коде:
- Уникальность в БД – финальная гарантия для платежей.
- Redis-блокировка – быстрый фильтр перед БД.
- Inbox Pattern – транзакционная дедупликация для Kafka-консьюмеров.
- Idempotency‑Key для REST API – защита на входе в систему.
- Сравнительный анализ и критерии выбора подхода для вашего сценария.
- Почему после всех защит неизбежна Eventual Consistency и как с ней жить.
Исходный код проекта на GitHub наверняка заслуживает Ваших звезд!🙂
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Жду ваших реакций и оценок🙂
В этом видео мы разбираем фундаментальную проблему распределённых систем: как гарантировать, что операция выполнится ровно один раз, несмотря на сетевые сбои, ретраи и асинхронные коммуникации. Мы проходим путь от осознания неизбежности повторов до построения надёжной архитектуры с использованием нескольких паттернов идемпотентности.
Что вы узнаете:
- Что такое идемпотентность и почему она бывает технической и бизнес.
- Как в Kafka и Kafka Connect рождаются дубли (и почему идемпотентный продюсер здесь не спасает).
- Почему нельзя просто отключить ретраи – теоретический предел at‑least‑once.
- Четыре способа защиты от дублей в реальном коде:
- Уникальность в БД – финальная гарантия для платежей.
- Redis-блокировка – быстрый фильтр перед БД.
- Inbox Pattern – транзакционная дедупликация для Kafka-консьюмеров.
- Idempotency‑Key для REST API – защита на входе в систему.
- Сравнительный анализ и критерии выбора подхода для вашего сценария.
- Почему после всех защит неизбежна Eventual Consistency и как с ней жить.
Исходный код проекта на GitHub наверняка заслуживает Ваших звезд!
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!
Жду ваших реакций и оценок
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3
Как работает 🤓
Ответ:
String.lines() (Java 11) разбивает строку на строки по разделителям \n, \r, \r\n и возвращает Stream<String>.
Это удобно для обработки многострочных текстов, логов, CSV-файлов построчно без BufferedReader.
Пример:
"a\nb\nc".lines()
.forEach(System.out::println).
В отличие от split("\n"), lines() обрабатывает все стандартные разделители и не создает промежуточный массив, экономя память при больших текстах.
#собеседование
String.lines() и для чего нужен? Ответ:
String.lines() (Java 11)
Это удобно для обработки многострочных текстов, логов, CSV-файлов построчно без BufferedReader.
Пример:
"a\nb\nc".lines()
.forEach(System.out::println).
В отличие от split("\n"), lines() обрабатывает все стандартные разделители и не создает промежуточный массив, экономя память при больших текстах.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологии сегодня — 31 июля
ℹ️ Кто родился в этот день
Габриэ́ль Кра́мер (нем. Gabriel Cramer, 31 июля 1704, Женева, Швейцария — 4 января 1752, Баньоль-сюр-Сез, Франция) — швейцарский математик, ученик и друг Иоганна Бернулли, один из создателей линейной алгебры.
🌐 Знаковые события
1999 — программа Discovery — Lunar Prospector : НАСА производит управляемое крушение космического зонда на Луну, тем самым заканчивая свою миссию по обнаружению замёрзшей воды на поверхности Луны.
#Biography #Birth_Date #Events #31июля
Габриэ́ль Кра́мер (нем. Gabriel Cramer, 31 июля 1704, Женева, Швейцария — 4 января 1752, Баньоль-сюр-Сез, Франция) — швейцарский математик, ученик и друг Иоганна Бернулли, один из создателей линейной алгебры.
1999 — программа Discovery — Lunar Prospector : НАСА производит управляемое крушение космического зонда на Луну, тем самым заканчивая свою миссию по обнаружению замёрзшей воды на поверхности Луны.
#Biography #Birth_Date #Events #31июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Всем привет!
По мотивам вступления вчерашнего видео, написал сегодня статью по маппингу типов передаваемых объектов в Kafka.
Можете ее оценить на devforge.ru и на Хабре.
Кстати - попрошу - оцените где удобнее читать и почему (но только не про мобилку я это знаю и работаю над этим)
❤️
По мотивам вступления вчерашнего видео, написал сегодня статью по маппингу типов передаваемых объектов в Kafka.
Можете ее оценить на devforge.ru и на Хабре.
Кстати - попрошу - оцените где удобнее читать и почему (но только не про мобилку я это знаю и работаю над этим)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)
Глава 3. Сериализация и форматы обмена
Нативная сериализация Java: интерфейс Serializable
Интерфейс
Он не требует реализации методов, не содержит констант и не накладывает контрактов на поведение класса. Его единственное назначение — сигнализировать JVM и стандартной библиотеке, что экземпляры данного класса разрешено преобразовывать в поток байтов и восстанавливать из него.
Этот механизм глубоко интегрирован в JVM: он обходит граф объектов, записывает метаданные классов, управляет версионностью и обеспечивает восстановление ссылочной целостности. Однако за простотой использования скрывается сложная внутренняя архитектура, множество подводных камней и серьёзные ограничения, которые делают нативную сериализацию устаревшим выбором для новых систем.
Маркерный интерфейс Serializable
Маркерный интерфейс — это интерфейс в Java без методов и полей, служащий метаданным для компилятора или runtime.
При попытке сериализовать объект, класс которого не реализует
Если
#Java #для_новичков #beginner #IO #NIO #Serialize #Serializable
Глава 3. Сериализация и форматы обмена
Нативная сериализация Java: интерфейс Serializable
Интерфейс
java.io.Serializable — это центральный элемент механизма нативной сериализации Java, существующий с версии JDK 1.1.Он не требует реализации методов, не содержит констант и не накладывает контрактов на поведение класса. Его единственное назначение — сигнализировать JVM и стандартной библиотеке, что экземпляры данного класса разрешено преобразовывать в поток байтов и восстанавливать из него.
Этот механизм глубоко интегрирован в JVM: он обходит граф объектов, записывает метаданные классов, управляет версионностью и обеспечивает восстановление ссылочной целостности. Однако за простотой использования скрывается сложная внутренняя архитектура, множество подводных камней и серьёзные ограничения, которые делают нативную сериализацию устаревшим выбором для новых систем.
Маркерный интерфейс Serializable
Serializable — это маркерный интерфейс (marker interface). В отличие от обычных интерфейсов, он не объявляет ни одного метода. Его роль чисто декларативная: наличие implements Serializable в объявлении класса изменяет поведение JVM при обработке этого класса механизмом сериализации.import java.io.Serializable;
public class Document implements Serializable {
// Маркерный интерфейс не требует методов
private String title;
private String content;
}
Маркерный интерфейс — это интерфейс в Java без методов и полей, служащий метаданным для компилятора или runtime.
Cloneable, Serializable, RandomAccess — примеры таких интерфейсов. Они не влияют на структуру класса напрямую, но изменяют поведение встроенных механизмов JVM. В современной Java роль маркерных интерфейсов частично вытесняется аннотациями (например, @Entity в JPA), но Serializable остаётся неизменным с 1997 года по причинам обратной совместимости.Аннотация (annotation) — это форма метаданных в Java, введённая в Java 5, которая предоставляет данные о программе, не являющиеся частью самой программы. Аннотации не влияют непосредственно на операции кода, но могут обрабатываться компилятором, инструментами или во время выполнения через рефлексию.
При попытке сериализовать объект, класс которого не реализует
Serializable, ObjectOutputStream.writeObject() выбросит NotSerializableException. Это проверка выполняется рекурсивно: если класс реализует Serializable, но одно из его полей — объект класса без Serializable, исключение возникнет при попытке записать это поле.public class Container implements Serializable {
private Document doc; // Document должен быть Serializable
private Metadata meta; // Metadata тоже должен быть Serializable
}Если
Metadata не реализует Serializable, сериализация Container завершится NotSerializableException на этапе обхода поля meta. Это ограничение каскадируется по всему графу объектов: каждый объект, достижимый от корневого, должен быть сериализуемым, либо должен быть помечен как transient.#Java #для_новичков #beginner #IO #NIO #Serialize #Serializable
👍4
Что сериализуется и что нет
Сериализуемое состояние
Механизм нативной сериализации записывает состояние экземпляра объекта — значения всех полей экземпляра, за исключением тех, что явно исключены. Это включает:
Поля всех уровней доступности:
Поля примитивных типов: их значения записываются в бинарном виде.
Поля ссылочных типов: объекты, на которые ссылаются поля, сериализуются рекурсивно. Если поле ссылается на другой объект, механизм обходит ссылку и сериализует целевой объект, затем возвращается к текущему.
При сериализации
Что не сериализуется
Статические поля не сериализуются. Они принадлежат классу, а не экземпляру, и их значения хранятся в Metaspace (в структурах класса), а не в куче вместе с объектом. При десериализации статические поля получают текущие значения, определённые в загруженном классе, а не значения из потока.
Это логично: если бы статические поля сериализовались, восстановление объекта на другой JVM с другим состоянием класса привело бы к конфликтам.
Transient-поля явно исключаются из сериализации. Ключевое слово
Важно:
Поля-суперклассы
Если класс реализует
При десериализации
Механизм обхода графа объектов
Отслеживание ссылок и циклы
Ключевая особенность: каждый объект в графе записывается ровно один раз. Для этого
Объект записывается полностью (дескриптор класса + поля).
Объекту присваивается handle — следующее свободное целое число.
Handle сохраняется в таблице.
При повторной встрече того же объекта (через другую ссылку или в цикле):
В поток записывается маркер backreference (TC_REFERENCE) + handle ранее записанного объекта.
Полная сериализация не выполняется.
Этот механизм предотвращает бесконечную рекурсию при циклических ссылках и гарантирует, что после десериализации топология графа сохранится:
Handle (в контексте сериализации Java) — это целочисленный идентификатор, присваиваемый каждому уникальному объекту в потоке сериализации. Handles начинаются с базового значения (обычно
#Java #для_новичков #beginner #IO #NIO #Serialize
Сериализуемое состояние
Механизм нативной сериализации записывает состояние экземпляра объекта — значения всех полей экземпляра, за исключением тех, что явно исключены. Это включает:
Поля всех уровней доступности:
public, protected, package-private и private. Сериализация обходит модификаторы доступа через механизм reflection или нативный доступ JNI, поэтому приватные поля сохраняются и восстанавливаются без геттеров и сеттеров.Поля примитивных типов: их значения записываются в бинарном виде.
Поля ссылочных типов: объекты, на которые ссылаются поля, сериализуются рекурсивно. Если поле ссылается на другой объект, механизм обходит ссылку и сериализует целевой объект, затем возвращается к текущему.
public class Library implements Serializable {
private String name;
private List<Book> books; // Book тоже должен быть Serializable
private Librarian head; // Librarian тоже должен быть Serializable
}При сериализации
Library механизм рекурсивно обойдёт список books: каждый элемент Book будет сериализован со своими полями, включая ссылки на Author, Publisher и т.д. Затем будет сериализован head (Librarian) со всеми его полями. Результат — глубокая копия всего графа объектов в виде потока байтов.Глубокая копия (deep copy) — это копия объекта, при которой копируются не только сам объект, но и все объекты, на которые он ссылается, рекурсивно. В отличие от поверхностной копии (shallow copy), где копируются только ссылки на те же внутренние объекты.
Что не сериализуется
Статические поля не сериализуются. Они принадлежат классу, а не экземпляру, и их значения хранятся в Metaspace (в структурах класса), а не в куче вместе с объектом. При десериализации статические поля получают текущие значения, определённые в загруженном классе, а не значения из потока.
Это логично: если бы статические поля сериализовались, восстановление объекта на другой JVM с другим состоянием класса привело бы к конфликтам.
public class Config implements Serializable {
private static String globalPrefix = "app"; // не сериализуется
private String instanceName; // сериализуется
}Transient-поля явно исключаются из сериализации. Ключевое слово
transient указывает механизму, что поле не должно быть записано в поток. При десериализации такое поле получает значение по умолчанию: null для ссылочных типов, 0 для чисел, false для boolean.public class UserSession implements Serializable {
private String username;
private transient String password; // не сериализуется
private transient Connection dbConn; // не сериализуется
private transient byte[] cachedToken; // не сериализуется
}transient используется для чувствительных данных (пароли, ключи), ресурсов, привязанных к runtime (соединения с БД, потоки, сокеты), и вычисляемых/кэшированных значений, которые можно восстановить после загрузки.Важно:
transient не влияет на видимость поля — оно остаётся приватным или публичным, просто исключается из сериализационного протокола.Поля-суперклассы
Если класс реализует
Serializable, а его суперкласс — нет, то поля суперкласса не сериализуются. При десериализации суперкласс инициализируется через свой no-arg конструктор (конструктор без аргументов). Если такого конструктора нет — InvalidClassException.public class Base { // не Serializable
private int baseField;
public Base() {} // обязателен no-arg конструктор
}
public class Derived extends Base implements Serializable {
private int derivedField; // только это поле сериализуется
}При десериализации
Derived поле baseField не восстанавливается из потока. Вместо этого вызывается Base(), и baseField получает значение по умолчанию (0), независимо от того, каким оно было при сериализации. Это частый источник багов: разработчик забывает, что суперкласс не serializable, и удивляется, почему поля предка обнуляются.No-arg конструктор (no-argument constructor, default constructor) — это конструктор без параметров. В Java, если класс не объявляет явных конструкторов, компилятор генерирует no-arg конструктор автоматически. Если объявлен хотя бы один конструктор с параметрами, no-arg конструктор нужно объявлять явно.
Механизм обхода графа объектов
ObjectOutputStream не просто записывает байты полей. Он обходит весь граф объектов, начиная с корневого, используя алгоритм, похожий на обход в ширину (BFS) или обход в глубину (DFS) с отслеживанием посещённых узлов.Отслеживание ссылок и циклы
Ключевая особенность: каждый объект в графе записывается ровно один раз. Для этого
ObjectOutputStream поддерживает внутреннюю таблицу handles — отображение от сериализованного объекта к целочисленному идентификатору (int). При первой встрече объекта:Объект записывается полностью (дескриптор класса + поля).
Объекту присваивается handle — следующее свободное целое число.
Handle сохраняется в таблице.
При повторной встрече того же объекта (через другую ссылку или в цикле):
В поток записывается маркер backreference (TC_REFERENCE) + handle ранее записанного объекта.
Полная сериализация не выполняется.
public class Node implements Serializable {
private String name;
private Node next; // может образовать цикл
}
// Создаём цикл: A -> B -> C -> A
Node a = new Node("A");
Node b = new Node("B");
Node c = new Node("C");
a.next = b;
b.next = c;
c.next = a; // цикл!
// Сериализация не зацикливается благодаря таблице handles
oos.writeObject(a); // записывает A, B, C, затем backreference на AЭтот механизм предотвращает бесконечную рекурсию при циклических ссылках и гарантирует, что после десериализации топология графа сохранится:
c.next будет ссылаться на тот же объект a, что и корневой указатель. Без handles граф с циклами привёл бы к StackOverflowError при сериализации.Handle (в контексте сериализации Java) — это целочисленный идентификатор, присваиваемый каждому уникальному объекту в потоке сериализации. Handles начинаются с базового значения (обычно
baseWireHandle = 0x7E0000) и инкрементируются. Они используются для кодирования backreferences.#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
serialVersionUID: идентификатор версии класса
Что такое serialVersionUID
При десериализации она сравнивает
Этот механизм защищает от десериализации объектов, сериализованных несовместимой версией класса. Например, если класс
Генерация по умолчанию
Если класс не объявляет
Имя класса.
Модификаторы класса (
Имена интерфейсов, которые реализует класс.
Имена полей, их модификаторы, типы.
Имена методов, их модификаторы, сигнатуры (возвращаемый тип, параметры).
Имена конструкторов, их сигнатуры.
Имена и сигнатуры методов
Имена статических полей (кроме
Этот хеш вычисляется через алгоритм, похожий на Secure Hash Algorithm (SHA), но упрощённый. Результат — 64-битное целое число
Проблема автоматической генерации
Автоматическая генерация
Если объект
Это происходит даже если изменение семантически совместимо: добавление метода не влияет на состояние объекта, но меняет вычисленный хеш.
Решение: явное объявление
Рекомендуется всегда объявлять
При явном объявлении JVM не вычисляет UID динамически, а использует указанное значение. Теперь добавление методов, изменение порядка полей, рефакторинг внутренней структуры не влияет на совместимость сериализации — до тех пор, пока изменения совместимы с форматом потока.
Как выбрать начальное значение? Традиционно используется
Когда менять serialVersionUID
Несовместимые изменения включают:
Удаление полей.
Изменение типа поля (например,
Изменение модификаторов класса с
Перемещение класса в другой пакет (имя класса входит в поток).
Изменение порядка полей суперкласса (влияет на порядок в потоке).
Изменение класса с
Совместимые изменения
При явном
Добавление полей: новые поля в потоке отсутствуют, поэтому при десериализации они получают значения по умолчанию (
Добавление классов в иерархию: если добавить новый суперкласс, который сам реализует
Удаление классов из иерархии: если удалить сериализуемый суперкласс, поля этого суперкласса в потоке игнорируются.
Добавление/удаление методов
Изменение модификатора доступа поля: не влияет, так как сериализация обходит модификаторы.
Пометка поля как transient: поле перестаёт читаться из потока, получает значение по умолчанию.
При десериализации объекта версии 1 в класс версии 2 поле
Обратная совместимость: новый объект в старый класс
Если объект сериализован версией 2 (с полем
#Java #для_новичков #beginner #IO #NIO #Serialize
Что такое serialVersionUID
serialVersionUID — это статическое поле типа long, которое выступает уникальным идентификатором версии класса для механизма сериализации. При сериализации JVM записывает serialVersionUID класса в поток.При десериализации она сравнивает
serialVersionUID из потока с serialVersionUID загруженного класса. Если значения не совпадают — InvalidClassException.public class Document implements Serializable {
private static final long serialVersionUID = 1L;
private String title;
}Этот механизм защищает от десериализации объектов, сериализованных несовместимой версией класса. Например, если класс
Document v1 имел поля title и author, а в v2 поле author было удалено и добавлено content, десериализация v1-объекта в v2-класс без контроля версий привела бы к повреждению данных или ClassCastException.Генерация по умолчанию
Если класс не объявляет
serialVersionUID явно, JVM вычисляет его автоматически на основе структуры класса. Алгоритм генерации (документирован в спецификации Java Object Serialization) учитывает:Имя класса.
Модификаторы класса (
public, final, abstract, interface).Имена интерфейсов, которые реализует класс.
Имена полей, их модификаторы, типы.
Имена методов, их модификаторы, сигнатуры (возвращаемый тип, параметры).
Имена конструкторов, их сигнатуры.
Имена и сигнатуры методов
writeObject, readObject, writeReplace, readResolve.Имена статических полей (кроме
serialVersionUID itself).Этот хеш вычисляется через алгоритм, похожий на Secure Hash Algorithm (SHA), но упрощённый. Результат — 64-битное целое число
long.SHA (Secure Hash Algorithm) — семейство криптографических хеш-функций, которые преобразуют входные данные произвольной длины в строку фиксированной длины (дайджест). В контексте serialVersionUID используется не криптографический SHA, а похожий алгоритм хеширования структурных элементов класса.
Проблема автоматической генерации
Автоматическая генерация
serialVersionUID кажущеися удобной, но на практике является источником хрупкости. Любое изменение класса — даже добавление приватного метода, изменение порядка полей, добавление комментария (нет, комментарии не влияют, но форматирование может влиять на порядок элементов в некоторых компиляторах) — может изменить вычисленный serialVersionUID.// Версия 1
public class User implements Serializable {
private String name;
private int age;
}
// Версия 2 — добавлен метод, serialVersionUID изменился!
public class User implements Serializable {
private String name;
private int age;
public void greet() { // новый метод изменяет вычисленный UID
System.out.println("Hello");
}
}
Если объект
User был сериализован версией 1, а десериализуется версией 2, JVM вычислит новый serialVersionUID для загруженного класса, сравнит с UID из потока, обнаружит несоответствие и выбросит:java.io.InvalidClassException: User; local class incompatible:
stream classdesc serialVersionUID = -123456789,
local class serialVersionUID = 987654321
Это происходит даже если изменение семантически совместимо: добавление метода не влияет на состояние объекта, но меняет вычисленный хеш.
Решение: явное объявление
Рекомендуется всегда объявлять
serialVersionUID явно:public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
}При явном объявлении JVM не вычисляет UID динамически, а использует указанное значение. Теперь добавление методов, изменение порядка полей, рефакторинг внутренней структуры не влияет на совместимость сериализации — до тех пор, пока изменения совместимы с форматом потока.
Как выбрать начальное значение? Традиционно используется
1L. Если позже потребуется несовместимое изменение, значение увеличивают: 2L, 3L и т.д. Некоторые команды используют timestamp или хеш от начальной структуры класса, но на практике последовательные номера проще в управлении.Когда менять serialVersionUID
serialVersionUID следует изменять (инкрементировать) при несовместимых изменениях структуры класса. Несовместимые изменения включают:
Удаление полей.
Изменение типа поля (например,
int → String).Изменение модификаторов класса с
Serializable на несериализуемый.Перемещение класса в другой пакет (имя класса входит в поток).
Изменение порядка полей суперкласса (влияет на порядок в потоке).
Изменение класса с
enum на обычный или наоборот.Совместимые изменения
При явном
serialVersionUID можно вносить изменения, не ломающие десериализацию старых объектов:Добавление полей: новые поля в потоке отсутствуют, поэтому при десериализации они получают значения по умолчанию (
null, 0, false). JVM использует ObjectInputStream.defaultReadObject(), который читает из потока только те поля, которые есть в текущем классе, игнорируя лишние данные в потоке.Добавление классов в иерархию: если добавить новый суперкласс, который сам реализует
Serializable, старые объекты десериализуются корректно — новые поля суперкласса получат значения по умолчанию.Удаление классов из иерархии: если удалить сериализуемый суперкласс, поля этого суперкласса в потоке игнорируются.
Добавление/удаление методов
writeObject/readObject: не влияет на serialVersionUID при явном объявлении.Изменение модификатора доступа поля: не влияет, так как сериализация обходит модификаторы.
Пометка поля как transient: поле перестаёт читаться из потока, получает значение по умолчанию.
// Версия 1
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
// Версия 2 — совместимое изменение: добавлено поле
public class User implements Serializable {
private static final long serialVersionUID = 1L; // тот же UID!
private String name;
private String email; // новое поле, старые объекты получат null
}
При десериализации объекта версии 1 в класс версии 2 поле
email получит null. Это безопасно, но требует, чтобы код класса корректно обрабатывал null (например, через инициализацию в readObject() или ленивую инициализацию в геттере).Обратная совместимость: новый объект в старый класс
Если объект сериализован версией 2 (с полем
email), а десериализуется классом версии 1 (без email), JVM прочитает из потока поле email, но не найдёт соответствующего поля в классе. В этом случае данные поля сохраняются во внутренней структуре ObjectInputStream как no-data или skipped fields, но не восстанавливаются. Это не вызывает исключения, но данные теряются.#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
Расширенный контроль: writeObject, readObject, readResolve
writeObject и readObject
Класс может переопределить поведение сериализации, объявив приватные методы:
JVM вызывает эти методы через рефлексию, если они объявлены. Метод
Этот паттерн позволяет сериализовать
readResolve
Метод
Без
writeReplace
Аналогично,
Этот паттерн рекомендуется Джошуа Блохом (Joshua Bloch) в "Effective Java" как предпочтительный способ сериализации сложных объектов
#Java #для_новичков #beginner #IO #NIO #Serialize
writeObject и readObject
Класс может переопределить поведение сериализации, объявив приватные методы:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
JVM вызывает эти методы через рефлексию, если они объявлены. Метод
writeObject вызывается после записи дескриптора класса, но перед записью полей. Внутри него можно выполнить out.defaultWriteObject() (записать стандартные поля) и затем дописать произвольные данные.public class SecureDocument implements Serializable {
private static final long serialVersionUID = 1L;
private String title;
private transient String secretContent; // не сериализуем напрямую
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // записываем title и другие нетранзиентные поля
// Шифруем секретный контент перед записью
String encrypted = encrypt(secretContent);
out.writeUTF(encrypted);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // читаем title
// Дешифруем секретный контент
String encrypted = in.readUTF();
this.secretContent = decrypt(encrypted);
}
private String encrypt(String data) { /* ... */ return data; }
private String decrypt(String data) { /* ... */ return data; }
}Этот паттерн позволяет сериализовать
transient-поля в зашифрованном виде, выполнять валидацию инвариантов, восстанавливать производные структуры.readResolve
Метод
readResolve вызывается после полной десериализации объекта и позволяет заменить десериализованный экземпляр на другой объект. Это критично для реализации singleton и enum-like паттернов.Singleton — это порождающий паттерн проектирования, гарантирующий, что класс имеет только один экземпляр, и предоставляющий глобальную точку доступа к нему. При десериализации singleton механизм создаёт новый объект, нарушая гарантию единственности. readResolve возвращает существующий экземпляр.
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
// При десериализации возвращаем существующий INSTANCE, а не новый объект
private Object readResolve() {
return INSTANCE;
}
}Без
readResolve десериализация Singleton создала бы новый объект в куче, и getInstance() вернул бы старый INSTANCE, в то время как десериализованный объект — отдельная сущность. readResolve гарантирует, что любая десериализация вернёт канонический экземпляр.writeReplace
Аналогично,
writeReplace вызывается перед сериализацией и позволяет заменить объект на другой перед записью в поток. Используется для прокси-сериализации (serialization proxy pattern), когда внутреннее представление объекта не должно попадать в поток.Прокси-сериализация (serialization proxy pattern) — это паттерн, при котором вместо самого объекта сериализуется лёгкий прокси-объект (proxy), содержащий только необходимые данные, а при десериализации прокси через readResolve восстанавливает оригинальный объект через его конструктор или фабрику. Это защищает инварианты, предотвращает создание "сырых" объектов и позволяет менять внутреннюю структуру класса, не ломая совместимость.
public class ComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private final int x;
private final int y;
// инвариант: x >= 0 && y >= 0
public ComplexObject(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
this.x = x;
this.y = y;
}
// Вместо себя сериализуем прокси
private Object writeReplace() {
return new SerializationProxy(x, y);
}
// Запрещаем десериализацию ComplexObject напрямую
private void readObject(ObjectInputStream in) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
private static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final int x;
private final int y;
SerializationProxy(int x, int y) {
this.x = x;
this.y = y;
}
// При десериализации прокси восстанавливаем ComplexObject через конструктор
private Object readResolve() {
return new ComplexObject(x, y); // инвариант проверяется!
}
}
}Этот паттерн рекомендуется Джошуа Блохом (Joshua Bloch) в "Effective Java" как предпочтительный способ сериализации сложных объектов
#Java #для_новичков #beginner #IO #NIO #Serialize
👍4
Что выведет код?
#Tasks
import java.io.*;
public class Task310726 {
static class DataV1 implements Serializable {
private static final long serialVersionUID = 1L;
String name = "John";
}
static class DataV2 implements Serializable {
private static final long serialVersionUID = 1L;
String fullName;
}
public static void main(String[] args) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(new DataV1());
}
byte[] bytes = baos.toByteArray();
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
DataV2 obj = (DataV2) ois.readObject();
System.out.println(obj.fullName);
} catch (Exception e) {
System.out.println(e.getClass().getSimpleName());
}
}
}
#Tasks
👍2
👍2