Для чего нужна сериализация java
To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype’s public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class’s state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.
During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.
When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will be thrown and will identify the class of the non-serializable object.
Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:
The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object’s fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object’s non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance’s class than the sending party, and the receiver’s version extends classes that are not extended by the sender’s version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a «hostile» or incomplete source stream.
Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:
This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private, protected and package-private access. Subclass access to this method follows java accessibility rules.
Classes that need to designate a replacement when an instance of it is read from the stream should implement this special method with the exact signature.
This readResolve method follows the same invocation rules and accessibility rules as writeReplace.
The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an InvalidClassException . A serializable class can declare its own serialVersionUID explicitly by declaring a field named «serialVersionUID» that must be static, final, and of type long : If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassException s during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class—serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.
Все, что вам нужно знать о Java-сериализации
В предыдущей статье мы рассмотрели пять различных способов создания объектов в Java , я объяснил, как десериализация сериализованного объекта создает новый объект, и в этом блоге я собираюсь подробно обсудить сериализацию и десериализацию.
Мы будем использовать ниже Employee объект класса в качестве примера для объяснения
Что такое сериализация и десериализация?
В Java мы создаем несколько объектов, которые живут и умирают соответственно, и каждый объект обязательно умрет, когда JVM умрет. Но иногда нам может потребоваться повторно использовать объект между несколькими JVM или мы можем перенести объект на другой компьютер по сети.
Что ж, сериализация позволяет нам преобразовывать состояние объекта в поток байтов, который затем можно сохранить в файл на локальном диске или отправить по сети на любой другой компьютер. А десериализация позволяет нам обратить процесс вспять, что означает повторное преобразование сериализованного байтового потока в объект снова.
Проще говоря, сериализация объекта — это процесс сохранения состояния объекта в последовательности байтов, а десериализация — процесс восстановления объекта из этих байтов. Как правило, полный процесс называется сериализацией, но я думаю, что для большей ясности лучше классифицировать их как отдельные:
Процесс сериализации не зависит от платформы, объект, сериализованный на одной платформе, может быть десериализован на другой платформе.
Для сериализации и десериализации, наш объект в файл , мы должны вызов ObjectOutputStream.writeObject() и , ObjectInputStream.readObject() как это сделано в следующем коде:
Только классы, которые реализуют Serializable, могут быть сериализированы
Подобно интерфейсу Cloneable для клонирования Java в сериализации, у нас есть один интерфейс маркера, Serializable, который работает как флаг для JVM. Любой класс, который реализует Serializable интерфейс напрямую или через своего родителя, может быть сериализован, а классы, которые не реализуют, Serializable не могут быть сериализованы.
Процесс сериализации Java по умолчанию полностью рекурсивен, поэтому всякий раз , когда мы пытаемся сериализовать один объект, то попытка процесса сериализации сериализовать все поля (примитивные и ссылочные) с нашим классом ( за исключением static и transient полей).
Когда класс реализует
Serializable интерфейс, все его подклассы также сериализуются. Но когда объект имеет ссылку на другой объект, эти объекты должны реализовывать
Serializable интерфейс отдельно. Если у нашего класса есть хотя бы одна ссылка на не
Serializable класс, JVM сгенерирует
NotSerializableException .
Почему сериализуемый объект не реализуется объектом?
Теперь возникает вопрос: если Serialization является очень базовой функциональностью, и любой класс, который не реализует, Serializable не может быть сериализован, то почему Serializable не реализуется сам по Object себе? Таким образом, все наши объекты могут быть сериализованы по умолчанию.
Object Класс не реализует Serializable интерфейс , потому что мы не можем сериализовать все объекты, например сериализацию нити не имеет никакого смысла , так как нить работает в моей JVM будет использовать память моей системы, сохраняющийся его и пытаемся запустить его в JVM будет не имеет смысла.
Переходные и статические поля не сериализуются
Если мы хотим сериализовать один объект, но не хотим сериализовать определенные поля, мы можем пометить эти поля как временные .
Все статические поля принадлежат классу, а не объекту, и процесс сериализации сериализует объект, поэтому статические поля не могут быть сериализованы.
- Сериализация не заботится о модификаторах доступа области, таких как private . Все непереходные и нестатические поля считаются частью постоянного состояния объекта и могут быть сериализованы.
- Мы можем присваивать значения конечным полям только в обработчиках, и процесс сериализации не вызывает никакого конструктора, но все же может присваивать значения конечным полям.
Что такое serialVersionUID? И почему мы должны это объявлять?
Предположим, у нас есть класс, и мы сериализовали его объект в файл на диске, и из-за некоторых новых требований мы добавили / удалили одно поле из нашего класса. Теперь, если мы попытаемся десериализовать уже сериализованный объект, мы получим InvalidClassException ; Почему?
Мы получаем его, потому что по умолчанию JVM связывает номер версии с каждым сериализуемым классом для управления версиями класса. Он используется для проверки того, что сериализованные и десериализованные объекты имеют одинаковые атрибуты и, следовательно, совместимы с десериализацией. Номер версии сохраняется в поле с именем serialVersionUID . Если сериализуемый класс не объявляет a serialVersionUID , JVM сгенерирует его автоматически во время выполнения.
Если мы изменим структуру нашего класса, например поля удаления / добавления, этот номер версии также изменится, и в соответствии с JVM наш класс не будет совместим с версией класса сериализованного объекта. Вот почему мы получаем исключение, но если вы действительно думаете об этом, почему оно должно быть выброшено только потому, что я добавил поле? Не может ли поле просто установить его значение по умолчанию, а затем записать в следующий раз?
Да, это можно сделать, предоставив serialVersionUID поле вручную и убедившись, что оно всегда одинаково. Настоятельно рекомендуется, чтобы каждый сериализуемый класс объявлял его serialVersionUID как сгенерированный класс зависимым от компилятора и, следовательно, может привести к непредвиденным последствиям . InvalidClassExceptions
Вы можете использовать утилиту, поставляемую с дистрибутивом JDK,
serialver для просмотра того, каким будет этот код по умолчанию (это просто хэш-код объекта по умолчанию).
Настройка сериализации и десериализации с помощью методов writeObject и readObject
JVM полностью контролирует сериализацию объекта в процессе сериализации по умолчанию, но есть много недостатков в использовании процесса сериализации по умолчанию, некоторые из которых:
- Он не может обработать сериализацию полей, которые не сериализуются.
- Процесс десериализации не вызывает конструкторов при создании объекта, поэтому он не может вызвать логику инициализации, предоставленную конструктором.
Но мы можем переопределить это поведение сериализации по умолчанию внутри нашего Java-класса и предоставить некоторую дополнительную логику для улучшения нормального процесса. Это можно сделать, предоставив два метода writeObject и readObject внутри класса, который мы хотим сериализовать:
Объявление обоих методов как закрытых необходимо (общедоступные методы не будут работать), поэтому, кроме JVM, ничто другое не сможет их увидеть. Это также доказывает, что ни один метод не наследуется, не переопределяется и не перегружается. JVM автоматически проверяет эти методы и вызывает их в процессе сериализации-десериализации. JVM может вызывать эти закрытые методы, но другие объекты не могут. Таким образом, целостность класса сохраняется, и протокол сериализации может продолжать работать в обычном режиме.
Несмотря на то, что эти специализированные частные методы предоставляются, сериализация объектов работает так же, вызывая ObjectOutputStream.writeObject() или ObjectInputStream.readObject() .
Вызов ObjectOutputStream.writeObject() или ObjectInputStream.readObject() запускает протокол сериализации. Сначала проверяется объект на предмет его реализации Serializable , а затем проверяется, предоставляется ли какой-либо из этих закрытых методов. Если они предоставляются, класс потока передается в качестве параметра этим методам, предоставляя коду контроль над его использованием.
Мы можем вызывать ObjectOutputStream.defaultWriteObject() и ObjectInputStream.defaultReadObject() из этих методов получить логику сериализации по умолчанию. Эти вызовы делают то, на что они похожи — они выполняют запись и чтение сериализованного объекта по умолчанию, что важно, потому что мы не заменяем обычный процесс; мы только добавляем к этому.
Эти закрытые методы могут использоваться для любой настройки, которую вы хотите выполнить в процессе сериализации, например, шифрование может быть добавлено к выводу и дешифрование ко входу (обратите внимание, что байты записываются и читаются в виде открытого текста без всякой обфускации). Они могут быть использованы для добавления дополнительных данных в поток, возможно, код версии компании, возможности действительно безграничны.
Остановка сериализации и десериализации
Предположим, у нас есть класс, который получил возможность сериализации от своего родителя, что означает, что наш класс происходит от другого реализующего класса Serializable .
Это означает, что любой может сериализовать и десериализовать объект нашего класса. Но что, если мы не хотим, чтобы наш класс был сериализован или десериализован? Например, какой у нас класс одноэлементный и мы хотим предотвратить создание любого нового объекта? Помните, что процесс десериализации создает новый объект .
Чтобы остановить сериализацию для нашего класса, мы можем, опять же, использовать вышеупомянутые приватные методы, чтобы просто бросить NotSerializableException . Любая попытка сериализации или десериализации нашего объекта теперь всегда приводит к возникновению исключения. И поскольку эти методы объявлены как private , никто не может переопределить ваши методы и изменить их.
Однако это является нарушением принципа подстановки Лискова. И, writeReplace и readResolve методы могут быть использованы для достижения синглтоноподобного поведения. Эти методы используются, чтобы позволить объекту предоставить альтернативное представление для себя в ObjectStream . Проще говоря, readResolve может использоваться для изменения данных, десериализованных посредством readObject метода, и writeReplace может использоваться для изменения данных, которые сериализуются через writeObject .
Сериализация Java также может использоваться для глубокого клонирования объекта . Клонирование Java — самая дискуссионная тема в сообществе Java, и оно, безусловно, имеет свои недостатки, но это все еще самый популярный и простой способ создания копии объекта до тех пор, пока этот объект не заполнит обязательные условия клонирования Java. Я покрыл клонирование в деталях в 3 статье длиной Java Клонирование серии , которая включает в себя статью , такой как Java Клонирование и типы Cloning (поверхностная и глубокая) подробно на примере , Java Клонирование — Конструктор копирования Versus Cloning , Java Клонирование — Даже копировать конструктор не Достаточно , прочитайте их, если хотите узнать больше о клонировании.
Заключение
- Сериализация — это процесс сохранения состояния объекта в последовательность байтов, которая затем может быть сохранена в файле или отправлена по сети, а десериализация — это процесс восстановления объекта из этих байтов.
- Только подклассы Serializable интерфейса могут быть сериализованы.
- Если наш класс не реализует Serializable интерфейс или если у него есть ссылка на не- Serializable класс, JVM сгенерирует NotSerializableException .
- Все transient и static поля не сериализуются.
- serialVersionUID Используются для проверки того, что сериализации и десериализации объекты имеют те же атрибуты , и , таким образом, совместимы с десериализацией.
- Мы должны создать serialVersionUID поле в нашем классе, поэтому, если мы изменим структуру нашего класса (добавление / удаление полей), JVM не будет проходить InvalidClassException . Если мы не предоставляем его, JVM предоставляет тот, который может измениться при изменении структуры нашего класса.
- Мы можем переопределить поведение сериализации по умолчанию внутри нашего Java-класса, предоставив реализацию writeObject и readObject методы.
- И мы можем назвать ObjectOutputStream.defaultWriteObject() и ObjectInputStream.defaultReadObject от writeObject и readObject методы получения сериализации по умолчанию и десериализации логике.
- Мы можем выбросить NotSerializableException исключение из writeObject и readObject , если мы не хотим, чтобы наш класс был сериализован или десериализован.
Процесс сериализации Java может быть дополнительно настроен и улучшен с помощью Externalizable интерфейса, который я объяснил в разделе Как настроить сериализацию в Java с помощью интерфейса Externalizable .
Вы можете найти полный исходный код для этой статьи в этом репозитории GitHub , и, пожалуйста, не стесняйтесь оставить свой ценный отзыв.
Для чего нужна сериализация java
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Сериализация в Java
Что такое «сериализация»?
Сериализация (Serialization) — процесс преобразования структуры данных в линейную последовательность байтов для дальнейшей передачи или сохранения. Сериализованные объекты можно затем восстановить (десериализовать).
В Java, согласно спецификации Java Object Serialization существует два стандартных способа сериализации: стандартная сериализация, через использование интерфейса java.io.Serializable и «расширенная» сериализация — java.io.Externalizable .
Сериализация позволяет в определенных пределах изменять класс. Вот наиболее важные изменения, с которыми спецификация Java Object Serialization может справляться автоматически:
- добавление в класс новых полей;
- изменение полей из статических в нестатические;
- изменение полей из транзитных в нетранзитные.
Обратные изменения (из нестатических полей в статические и из нетранзитных в транзитные) или удаление полей требуют определенной дополнительной обработки в зависимости от того, какая степень обратной совместимости необходима.
Опишите процесс сериализации/десериализации с использованием Serializable .
При использовании Serializable применяется алгоритм сериализации, который с помощью рефлексии (Reflection API) выполняет:
- запись в поток метаданных о классе, ассоциированном с объектом (имя класса, идентификатор SerialVersionUID , идентификаторы полей класса);
- рекурсивную запись в поток описания суперклассов до класса java.lang.Object (не включительно);
- запись примитивных значений полей сериализуемого экземпляра, начиная с полей самого верхнего суперкласса;
- рекурсивную запись объектов, которые являются полями сериализуемого объекта.
При этом ранее сериализованные объекты повторно не сериализуются, что позволяет алгоритму корректно работать с циклическими ссылками.
Для выполнения десериализации под объект выделяется память, после чего его поля заполняются значениями из потока. Конструктор объекта при этом не вызывается. Однако при десериализации будет вызван конструктор без параметров родительского несериализуемого класса, а его отсутствие повлечёт ошибку десериализации.
Как изменить стандартное поведение сериализации/десериализации?
- Реализовать интерфейс java.io.Externalizable , который позволяет применение пользовательской логики сериализации. Способ сериализации и десериализации описывается в методах writeExternal() и readExternal() . Во время десериализации вызывается конструктор без параметров, а потом уже на созданном объекте вызывается метод readExternal .
- Если у сериализуемого объекта реализован один из следующих методов, то механизм сериализации будет использовать его, а не метод по умолчанию :
- writeObject() — запись объекта в поток;
- readObject() — чтение объекта из потока;
- writeReplace() — позволяет заменить себя экземпляром другого класса перед записью;
- readResolve() — позволяет заменить на себя другой объект после чтения.
Как исключить поля из сериализации?
Для управления сериализацией при определении полей можно использовать ключевое слово transient , таким образом исключив поля из общего процесса сериализации.
Что обозначает ключевое слово transient ?
Поля класса, помеченные модификатором transient , не сериализуются.
Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить. Другой пример такого поля — ссылка на экземпляр объекта, который не требует сериализации или не может быть сериализован.
Какое влияние оказывают на сериализуемость модификаторы полей static и final
При стандартной сериализации поля, имеющие модификатор static, не сериализуются. Соответственно, после десериализации это поле значения не меняет. При использовании реализации Externalizable сериализовать и десериализовать статическое поле можно, но не рекомендуется этого делать, т.к. это может сопровождаться трудноуловимыми ошибками.
Поля с модификатором final сериализуются как и обычные. За одним исключением – их невозможно десериализовать при использовании Externalizable , поскольку final поля должны быть инициализированы в конструкторе, а после этого в readExternal() изменить значение этого поля будет невозможно. Соответственно, если необходимо сериализовать объект с final полем необходимо использовать только стандартную сериализацию.
Как не допустить сериализацию?
Чтобы не допустить автоматическую сериализацию можно переопределить private методы для создания исключительной ситуации NotSerializableException .
Любая попытка записать или прочитать этот объект теперь приведет к возникновению исключительной ситуации.
Как создать собственный протокол сериализации?
Для создания собственного протокола сериализации достаточно реализовать интерфейс Externalizable , который содержит два метода:
Какая роль поля serialVersionUID в сериализации?
serialVersionUID используется для указания версии сериализованных данных.
Когда мы не объявляем serialVersionUID в нашем классе явно, среда выполнения Java делает это за нас, но этот процесс чувствителен ко многим метаданным класса включая количество полей, тип полей, модификаторы доступа полей, интерфейсов, которые реализованы в классе и пр.
Рекомендуется явно объявлять serialVersionUID т.к. при добавлении, удалении атрибутов класса динамически сгенерированное значение может измениться и в момент выполнения будет выброшено исключение InvalidClassException .
Когда стоит изменять значение поля serialVersionUID ?
serialVersionUID нужно изменять при внесении в класс несовместимых изменений, например при удалении какого-либо его атрибута.
В чем проблема сериализации Singleton?
Проблема в том что после десериализации мы получим другой объект. Таким образом, сериализация дает возможность создать Singleton еще раз, что недопустимо. Существует два способа избежать этого:
- явный запрет сериализации.
- определение метода с сигнатурой (default/public/private/protected/) Object readResolve() throws ObjectStreamException , назначением которого станет возврат замещающего объекта вместо объекта, на котором он вызван.
Какие существуют способы контроля за значениями десериализованного объекта
Если есть необходимость выполнения контроля за значениями десериализованного объекта, то можно использовать интерфейс ObjectInputValidation с переопределением метода validateObject() .
Так же существуют способы подписывания и шифрования, позволяющие убедиться, что данные не были изменены:
Для чего нужна сериализация java
Сериализация представляет процесс записи состояния объекта в поток, соответственно процесс извлечения или восстановления состояния объекта из потока называется десериализацией . Сериализация очень удобна, когда идет работа со сложными объектами.
Интерфейс Serializable
Сразу надо сказать, что сериализовать можно только те объекты, которые реализуют интерфейс Serializable . Этот интерфейс не определяет никаких методов, просто он служит указателем системе, что объект, реализующий его, может быть сериализован.
Сериализация. Класс ObjectOutputStream
Для сериализации объектов в поток используется класс ObjectOutputStream . Он записывает данные в поток.
Для создания объекта ObjectOutputStream в конструктор передается поток, в который производится запись:
Для записи данных ObjectOutputStream использует ряд методов, среди которых можно выделить следующие:
void close() : закрывает поток
void flush() : очищает буфер и сбрасывает его содержимое в выходной поток
void write(byte[] buf) : записывает в поток массив байтов
void write(int val) : записывает в поток один младший байт из val
void writeBoolean(boolean val) : записывает в поток значение boolean
void writeByte(int val) : записывает в поток один младший байт из val
void writeChar(int val) : записывает в поток значение типа char, представленное целочисленным значением
void writeDouble(double val) : записывает в поток значение типа double
void writeFloat(float val) : записывает в поток значение типа float
void writeInt(int val) : записывает целочисленное значение int
void writeLong(long val) : записывает значение типа long
void writeShort(int val) : записывает значение типа short
void writeUTF(String str) : записывает в поток строку в кодировке UTF-8
void writeObject(Object obj) : записывает в поток отдельный объект
Эти методы охватывают весь спектр данных, которые можно сериализовать.
Например, сохраним в файл один объект класса Person:
Десериализация. Класс ObjectInputStream
Класс ObjectInputStream отвечает за обратный процесс — чтение ранее сериализованных данных из потока. В конструкторе он принимает ссылку на поток ввода:
Функционал ObjectInputStream сосредоточен в методах, предназначенных для чтения различных типов данных. Рассмотрим основные методы этого класса:
void close() : закрывает поток
int skipBytes(int len) : пропускает при чтении несколько байт, количество которых равно len
int available() : возвращает количество байт, доступных для чтения
int read() : считывает из потока один байт и возвращает его целочисленное представление
boolean readBoolean() : считывает из потока одно значение boolean
byte readByte() : считывает из потока один байт
char readChar() : считывает из потока один символ char
double readDouble() : считывает значение типа double
float readFloat() : считывает из потока значение типа float
int readInt() : считывает целочисленное значение int
long readLong() : считывает значение типа long
short readShort() : считывает значение типа short
String readUTF() : считывает строку в кодировке UTF-8
Object readObject() : считывает из потока объект
Например, извлечем выше сохраненный объект Person из файла:
Теперь совместим сохранение и восстановление из файла на примере списка объектов:
Исключение данных из сериализации
По умолчанию сериализуются все переменные объекта. Однако, возможно, мы хотим, чтобы некоторые поля были исключены из сериализации. Для этого они должны быть объявлены с модификатором transient . Например, исключим из сериализации объекта Person переменные height и married: