Привет! В сегодняшней лекции мы поговорим о сериализации и десериализации в Java.
Если нужен короткий ответ: сериализация это превращение Java-объекта в последовательность байт, которую можно записать в файл, положить в базу или передать по сети. Десериализация это обратная операция: из тех же байт собирается объект с тем же состоянием. Отвечают за все это интерфейс-маркер java.io.Serializable и пара потоков, ObjectOutputStream и ObjectInputStream.
Кратко
Сериализация сохраняет состояние объекта в виде последовательности байт, десериализация восстанавливает объект обратно.
Класс должен реализовать Serializable. Методов в этом интерфейсе нет, он только помечает класс как пригодный для сохранения.
Объект записывает ObjectOutputStream.writeObject(), читает ObjectInputStream.readObject(), а результат чтения нужно привести к нужному типу.
Поле private static final long serialVersionUID фиксирует версию класса. Без него номер версии считает сама JVM, и после правки класса старый файл уже не прочитается: вылетит InvalidClassException.
Сериализуется весь граф объектов: каждый класс в цепочке ссылок тоже должен быть Serializable, иначе будет NotSerializableException.
Поля с transient и статические поля в поток не попадают. После десериализации transient-поле получает значение по умолчанию: null для ссылок и 0 для чисел.
Начнем с простого примера. Допустим, ты создатель компьютерной игры. Если ты рос в 90-е и помнишь игровые приставки тех времен, наверняка знаешь, что в них отсутствовала очевидная сегодня вещь: сохранение и загрузка игры :) Если нет… представь себе!
Боюсь, сегодня игра без такой возможности будет обречена на провал!
А, собственно, что такое «сохранение» и «загрузка» игры? Ну, в обычном смысле мы понимаем, что это: мы хотим продолжить игру с того места, где закончили в прошлый раз. Для этого мы создаем некую «контрольную точку», которую потом используем для загрузки игры. Но что это значит не в житейском, а в «программистском» смысле?
Ответ прост: мы сохраняем состояние нашей программы. Допустим, ты играешь в стратегию за Испанию. У твоей игры есть состояние: кто какими территориями владеет, у кого сколько ресурсов, кто с кем в союзе, а кто наоборот в состоянии войны, и так далее. Эту информацию, состояние нашей программы, необходимо как-то сохранить, чтобы в дальнейшем восстановить данные и продолжить игру.
Для этого как раз и используются механизмы сериализации и десериализации.
Сериализация это процесс сохранения состояния объекта в последовательность байт.
Десериализация это процесс восстановления объекта из этих байт.
Любой Java-объект преобразуется в последовательность байт. Для чего это нужно?
Мы уже не раз говорили, что программы не существуют сами по себе. Чаще всего они взаимодействуют друг с другом, обмениваются данными и т.д. И байтовый формат для этого удобен и эффективен. Мы можем, например, превратить наш объект класса SavedGame (сохраненная игра) в последовательность байт, передать эти байты по сети на другой компьютер, и на втором компьютере превратить эти байты снова в Java-объект!
На слух воспринимается сложно, да? Судя по всему, и организовать этот процесс будет непросто :/
К счастью, нет! :)
Интерфейс Serializable
В Java за процессы сериализации отвечает интерфейс Serializable. Этот интерфейс крайне прост: чтобы им пользоваться, не нужно реализовывать ни одного метода!
Вот так просто будет выглядеть наш класс сохранения игры:
import java.io.Serializable;
import java.util.Arrays;
public class SavedGame implements Serializable {
private static final long serialVersionUID = 1L;
private String[] territoriesInfo;
private String[] resourcesInfo;
private String[] diplomacyInfo;
public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
this.territoriesInfo = territoriesInfo;
this.resourcesInfo = resourcesInfo;
this.diplomacyInfo = diplomacyInfo;
}
public String[] getTerritoriesInfo() {
return territoriesInfo;
}
public void setTerritoriesInfo(String[] territoriesInfo) {
this.territoriesInfo = territoriesInfo;
}
public String[] getResourcesInfo() {
return resourcesInfo;
}
public void setResourcesInfo(String[] resourcesInfo) {
this.resourcesInfo = resourcesInfo;
}
public String[] getDiplomacyInfo() {
return diplomacyInfo;
}
public void setDiplomacyInfo(String[] diplomacyInfo) {
this.diplomacyInfo = diplomacyInfo;
}
@Override
public String toString() {
return "SavedGame{" +
"territoriesInfo=" + Arrays.toString(territoriesInfo) +
", resourcesInfo=" + Arrays.toString(resourcesInfo) +
", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
'}';
}
}
Три массива данных отвечают за информацию о территориях, экономике и дипломатии, а интерфейс Serializable говорит Java-машине: «все ок, если что, объекты этого класса можно сериализовать».
Интерфейс, у которого нет ни одного метода, выглядит странно :/ Зачем он нужен? Ответ на этот вопрос есть выше: только чтобы предоставить нужную информацию Java-машине.
В одной из прошлых лекций мы мельком упоминали интерфейсы-маркеры. Это специальные информативные интерфейсы, которые просто помечают наши классы дополнительной информацией, в будущем полезной для Java-машины. Никаких методов, которые нужно было бы имплементировать, у них нет. Так вот, Serializable это один из таких интерфейсов.
Что такое serialVersionUID
Еще один важный момент: переменная private static final long serialVersionUID, которую мы определили в классе. Зачем она нужна?
Это поле содержит уникальный идентификатор версии сериализованного класса.
Идентификатор версии есть у любого класса, который имплементирует интерфейс Serializable. Он вычисляется по содержимому класса: по имени класса, его модификаторам, списку интерфейсов, полям и методам. И если мы поменяем в нашем классе тип поля и/или количество полей, идентификатор версии моментально изменится. serialVersionUID тоже записывается при сериализации класса.
Когда мы пытаемся провести десериализацию, то есть восстановить объект из набора байт, значение serialVersionUID сравнивается со значением serialVersionUID класса в нашей программе. Если значения не совпадают, будет выброшено исключение java.io.InvalidClassException. Мы увидим пример этого ниже.
Чтобы избежать таких ситуаций, мы просто вручную задаем для нашего класса этот идентификатор версии. В нашем случае он будет равен просто 1 (можешь подставить любое другое понравившееся число).
С Java 14 у этого поля появилась своя аннотация, @Serial из пакета java.io. На работу программы она не влияет, зато компилятор проверит, что поле или метод, относящиеся к сериализации, объявлены правильно. Примерно как @Override для методов.
Пробуем сериализовать
Ну, самое время попробовать сериализовать наш объект SavedGame и посмотреть, что получится!
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws IOException {
//создаем наш объект
String[] territoryInfo = {"У Испании 6 провинций", "У России 10 провинций", "У Франции 8 провинций"};
String[] resourcesInfo = {"У Испании 100 золота", "У России 80 золота", "У Франции 90 золота"};
String[] diplomacyInfo = {"Франция воюет с Россией, Испания заняла позицию нейтралитета"};
SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);
//создаем 2 потока для сериализации объекта и сохранения его в файл
//try-with-resources закроет оба потока и освободит ресурсы сам
try (FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
// сохраняем игру в файл
objectOutputStream.writeObject(savedGame);
}
}
}
Как видишь, мы создали 2 потока: FileOutputStream и ObjectOutputStream. Первый из них умеет записывать данные в файл, а второй преобразует объекты в байты.
Ты уже видел подобные «вложенные» конструкции, например, new BufferedReader(new InputStreamReader(...)), в лекции про BufferedReader и BufferedWriter, так что они не должны тебя пугать :)
Оба потока объявлены в круглых скобках после try: это конструкция try-with-resources, она есть в Java еще с версии 7 и сама закрывает потоки на выходе из блока, даже если внутри вылетит исключение. Так надежнее, чем вызывать close() руками: если ошибка случится раньше, до вызова close() дело просто не дойдет.
Создав такую «цепочку» из двух потоков мы выполняем обе задачи: превращаем объект SavedGame в набор байт и сохраняем его в файл с помощью метода writeObject(). А, кстати, мы же даже не проверили, что у нас получилось! Самое время заглянуть в файл!
*Примечание: файл необязательно создавать заранее. Если файла с таким названием не существует, он будет создан автоматически*
А вот и его содержимое!
¬н sr SavedGame [
diplomacyInfot [Ljava/lang/String;[
resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранция воюет СЃ Россией, Рспания заняла позицию нейтралитетаuq ~ t "РЈ Рспании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Рспании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Ой-ой :( Кажется, не сработала наша программа :(
На самом деле, сработала. Ты же помнишь, что мы передавали в файл именно набор байт, а не просто объект или текст? Ну, вот так этот набор и выглядит :) Это и есть наша сохраненная игра!
Десериализация: восстанавливаем объект
Если же мы хотим восстановить наш исходный объект, то есть, загрузиться и продолжит игру с того места, где остановились, нам нужен обратный процесс, десериализация.
Вот как она будет выглядеть в нашем случае:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
SavedGame savedGame = (SavedGame) objectInputStream.readObject();
System.out.println(savedGame);
}
}
}
А вот и результат!
SavedGame{territoriesInfo=[У Испании 6 провинций, У России 10 провинций, У Франции 8 провинций], resourcesInfo=[У Испании 100 золота, У России 80 золота, У Франции 90 золота], diplomacyInfo=[Франция воюет с Россией, Испания заняла позицию нейтралитета]}
Отлично! У нас получилось сначала сохранить состояние нашей игры в файл, а потом восстановить ее из файла.
Что будет без serialVersionUID
А теперь давай попробуем сделать то же, но уберем из нашего класса SavedGame идентификатор версии.
Не будем переписывать оба наших класса, код в них будет тем же, просто из класса SavedGame уберем private static final long serialVersionUID.
Вот наш объект после сериализации:
¬н sr SavedGameі€MіuОm‰ [
diplomacyInfot [Ljava/lang/String;[
resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранция воюет СЃ Россией, Рспания заняла позицию нейтралитетаuq ~ t "РЈ Рспании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Рспании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Важный нюанс: само по себе отсутствие serialVersionUID ничего не ломает. Проблема вылезает тогда, когда между сохранением и загрузкой класс изменили: JVM пересчитает номер версии, он не совпадет с записанным в файл, и прочитать такой файл уже не выйдет. Именно это и видно в двух разных числах ниже.
А при попытке его десериализовать произошло вот что:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Это то самое исключение, о котором говорилось выше. Подробнее об этом ты можешь прочесть в статье одного из наших учеников.
Сериализация вложенных объектов
Кстати, мы упустили один важный момент. Понятно, что строки и примитивы сериализуются легко: наверняка в Java есть какие-то встроенные механизмы для этого. Но что, если в нашем serializable-классе есть поля, выраженные не примитивами, а ссылками на другие объекты? Давай, например, создадим отдельные классы TerritoriesInfo, ResourcesInfo и DiplomacyInfo для работы с нашим классом SavedGame.
public class TerritoriesInfo {
private String info;
public TerritoriesInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "TerritoriesInfo{" +
"info='" + info + '\'' +
'}';
}
}
public class ResourcesInfo {
private String info;
public ResourcesInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "ResourcesInfo{" +
"info='" + info + '\'' +
'}';
}
}
public class DiplomacyInfo {
private String info;
public DiplomacyInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "DiplomacyInfo{" +
"info='" + info + '\'' +
'}';
}
}
А вот теперь перед нами возник вопрос: а должны ли все эти классы быть Serializable, если мы хотим сериализовать изменившийся класс SavedGame?
Что ж, давай проверим это на практике! Оставим пока все как есть и попробуем сериализовать объект SavedGame:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws IOException {
//создаем наш объект
TerritoriesInfo territoriesInfo = new TerritoriesInfo("У Испании 6 провинций, у России 10 провинций, у Франции 8 провинций");
ResourcesInfo resourcesInfo = new ResourcesInfo("У Испании 100 золота, у России 80 золота, у Франции 90 золота");
DiplomacyInfo diplomacyInfo = new DiplomacyInfo("Франция воюет с Россией, Испания заняла позицию нейтралитета");
SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);
try (FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(savedGame);
}
}
}
Результат:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Не вышло!
Сериализуется весь граф объектов
Собственно, вот и ответ на наш вопрос. При сериализации объекта сериализуются все объекты, на которые он ссылается в своих переменных экземпляра. И если те объекты тоже ссылаются на третьи объекты, они тоже сериализуются. И так до бесконечности. Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение.
Это, кстати, в перспективе может создать проблемы. Что делать, например, если часть класса при сериализации нам не нужна? Или, к примеру, класс TerritoriesInfo в нашей программе достался нам «по наследству» в составе какой-то библиотеки. При этом он не является Serializable, и мы, соответственно, не можем его менять.
Получается, что и добавить поле TerritoriesInfo в наш класс SavedGame мы не можем, ведь тогда весь класс SavedGame станет несериализуемым!
Проблема :/
Ключевое слово transient
Проблемы такого рода решаются в Java при помощи ключевого слова transient. Если добавить к полю класса это ключевое слово, значение этого поля не будет сериализовано.
Давай попробуем сделать одно из полей нашего класса SavedGame transient, после чего сериализуем и восстановим один объект.
import java.io.Serializable;
public class SavedGame implements Serializable {
private transient TerritoriesInfo territoriesInfo;
private ResourcesInfo resourcesInfo;
private DiplomacyInfo diplomacyInfo;
public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
this.territoriesInfo = territoriesInfo;
this.resourcesInfo = resourcesInfo;
this.diplomacyInfo = diplomacyInfo;
}
//...геттеры, сеттеры, toString()...
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws IOException {
//создаем наш объект
TerritoriesInfo territoriesInfo = new TerritoriesInfo("У Испании 6 провинций, у России 10 провинций, у Франции 8 провинций");
ResourcesInfo resourcesInfo = new ResourcesInfo("У Испании 100 золота, у России 80 золота, у Франции 90 золота");
DiplomacyInfo diplomacyInfo = new DiplomacyInfo("Франция воюет с Россией, Испания заняла позицию нейтралитета");
SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);
try (FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(savedGame);
}
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
SavedGame savedGame = (SavedGame) objectInputStream.readObject();
System.out.println(savedGame);
}
}
}
А вот и результат:
SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='У Испании 100 золота, у России 80 золота, у Франции 90 золота'}, diplomacyInfo=DiplomacyInfo{info='Франция воюет с Россией, Испания заняла позицию нейтралитета'}}
Заодно мы получили ответ на вопрос, какое же значение будет присвоено transient-полю. Ему присваивается значение по умолчанию. В случае с объектами это null.
Что происходит с полем класса при сериализации
Сведем все в одну таблицу. Правило простое: по умолчанию в поток попадают все поля объекта, кроме помеченных transient и кроме статических, ведь статическое поле принадлежит классу, а не объекту.
Вид поля
Попадает в поток
Что будет после десериализации
обычное поле примитивного типа
да
то же значение, что было сохранено
обычное поле-ссылка на Serializable-объект
да, вместе со всем объектом
восстановленная копия объекта
поле-ссылка на класс без Serializable
нет, сериализация оборвется
объект вообще не сохранится, будет NotSerializableException
поле с transient
нет
значение по умолчанию: null, 0 или false
статическое поле
нет, оно принадлежит классу, а не объекту
то значение, которое сейчас у класса в этой JVM
На досуге ты можешь прочитать вот эту отличную статью про сериализацию, а еще заглянуть в спецификацию сериализации от Oracle. И там, и там написано об интерфейсе Externalizable, о котором мы поговорим в следующей лекции.
Кроме того, глава на эту тему есть в книге «Head-First Java», обрати на нее внимание :)
Вопросы и ответы
Что такое сериализация в Java простыми словами?
Это сохранение состояния объекта в виде последовательности байт. Байты можно записать в файл, положить в базу или отправить по сети, а потом собрать из них точно такой же объект. Обратная операция называется десериализацией.
Какие поля не сериализуются?
Поля с ключевым словом transient и статические поля. Первые программист исключает вручную, вторые не попадают в поток потому, что принадлежат классу, а не конкретному объекту. Все остальные поля объекта сохраняются по умолчанию.
Зачем нужен serialVersionUID и что будет, если его не задать?
Это номер версии класса. Если его не объявить, JVM вычислит номер сама, по имени класса, модификаторам, интерфейсам, полям и методам. Стоит потом изменить класс, номер изменится, и файл, записанный старой версией класса, прочитать уже не выйдет: при десериализации вылетит InvalidClassException.
Почему возникает NotSerializableException?
Потому что сохраняемый объект ссылается на объект класса, который не реализует Serializable. Сериализуется весь граф ссылок, поэтому Serializable должны быть все классы в цепочке. Либо достаточно пометить такое поле как transient, тогда оно в поток не попадет.
Какое значение получит transient-поле после десериализации?
Значение по умолчанию для своего типа: null для ссылок, 0 для числовых примитивов, false для boolean. Прежнее значение не восстанавливается, потому что его просто нет в файле.
Опасно ли десериализовать данные из ненадежного источника?
Да, это классическая уязвимость: специально собранный поток байт может заставить программу создать объекты, которых она не ждала. Начиная с Java 9 в JDK встроен фильтр входящих данных ObjectInputFilter и системное свойство jdk.serialFilter, которые позволяют пропускать только разрешенные классы.
Источник: Oracle Java SE API: ObjectOutputStream.
Владимир — один из тех, кто обучился Java-разработке благодаря курсу JavaRush, а после учебы устроился работать в нашу компанию. О ...
[Читать полную биографию]
было выброшено InvalidClassException не потому что мы удалили serialVersionUID а потому что когда мы убрали ее инициализацию она проинициализировалась Java машиной, тоесть теперь она перестала равнятся 1. А сериализовали мы обьект когда она равнялась 1
"Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение."
Насколько я поняла это не совсем так. Класс-родитель необязательно должен быть сериализуемый. Класс же может наследоваться и от Object, а он не Serializable. Главное чтобы у родительского класса был конструктор без параметров.
ПОЧЕМУ ЭТО СРАБОТАЛО из последнего примера
private transient TerritoriesInfo territoriesInfo;
private ResourcesInfo resourcesInfo;
private DiplomacyInfo diplomacyInfo;
у нас же классы ResourcesInfo, DiplomacyInfo - не реализуют интерфейс Serializable
слова автора из предпоследнего примера:
А вот теперь перед нами возник вопрос: а должны ли все эти классы быть Serializable, если мы хотим сериализовать изменившийся класс SavedGame?
Что ж, давай проверим это на практике! Оставим пока все как есть и попробуем сериализовать объект SavedGame:
Результат:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Не вышло!
Собственно, вот и ответ на наш вопрос. При сериализации объекта сериализуются все объекты, на которые он ссылается в своих переменных экземпляра. И если те объекты тоже ссылаются на третьи объекты, они тоже сериализуются. И так до бесконечности. Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение.
Почему мы отметили только одно поле как transient и все сработало, их же еще два
Осталось за скобками, что если класс не сериализуемый, но имеет пустой конструктор, то проблемы тоже не будет при наследовании. Ну и ,видимо, если такой класс есть внутри, то тоже ничего страшного не произойдёт.
Книга отличная.
Просто надо использовать не как справочник, а как источник ответов на "глупые вопросы новичков"...
Рекомендовано к дополнительному чтению.
Недоработанная лекция! В самом конце они пометили ключевым словом только поле TerritoriesInfo , а Ресурсы и Дипломатия так и остались без маркировки. И как же у них тогда не выскочило исключение?
Все доработано, прочитай внимательнее) Чтобы класс мог сериализоваться, он должен имплементировать маркерный интерфейс Serializable, ключевое же слово transient отвечает за то, будет ли это поле объекта пробовать сереализоваться, если поле объявлено с этим ключевым словом, то сериализоваться оно не будет (Если простыми словами, transient говорит JVM: если будешь сохранять этот объект, то вот это поле не надо). Ошибку не выбило потому что все классы имплементируют Serializable (об этом было сказано, но не показано)
Полностью с вами согласен. За исключением последнего момента. Все классы должны реализовывать маркерный интерфейс, но то что те поля его реализуют не было написано настолько явно. Даже сейчас перечитал еще раз. Написали что они должны по хорошему, но что они сделали их таковыми лично для меня не было очевидно
Блин ребята респект сразу видно что есть преподавательская жилка можете объяснить простыми словами сложные вещи ещё и с юмором это реально жесть я как вспомню как играл этот Червяк Джим который нельзя сохранить =)
"... При сериализации объекта сериализуются все объекты, на которые он ссылается в своих переменных экземпляра. И если те объекты тоже ссылаются на третьи объекты, они тоже сериализуются. И так до бесконечности. Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение..." и далее приводится пример работы кода с использованием transient, но опускается момент того, что классы ResourcesInfo и DiplomacyInfo должны также реализовывать интерфейс Serializable. Это намеренно сделано для упрощения или ошибка?
Да, должны. Просто говорили "что, если класс TerritoryInfo в нашей программе достался нам «по наследству» в составе какой-то библиотеки. При этом он не является Serializable, и мы, соответственно, не можем его менять",
поэтому показали обновленный код мэйна для такой ситуации, то есть имелось в виду, что остальные классы нормальные (изменяемые нами с реализацией Serializable)
Уважаемые админы!
Обратите внимание, срок действия ссылки на статью об интерфейсе Externalizable истек. http://www.skipy.ru/technics/serialization.html
> Любой Java-объект преобразуется в последовательность байт
Очень сомнительная фраза. А в каком виде объект был до этого? В виде грязи? Конечно он и был последовательностью байт. Только в разных местах записанных. Чаще всего в Оперативной памяти.
Фраза абсолютно прозрачная и корректная. Тебе нужно сохранить состояние объекта. В Java это можно сделать только при условии что объект сериализумый. Сериализация как процесс это перевод объекта в набор байт. Можно просто использовать официальную документацию, если не очень заходит на русском языке:
Сериализовать объект означает преобразовать его состояние в поток байтов, чтобы поток байтов мож...
В каком виде хранится обрабатывает процессор или хранится в оперативке или каким образом та или иная система выделяет JVM свои ресурсы, тебя как разработчика волновать не должно вообще.
Автор, объясни пожалуйста.
Есть три класса TerritoriesInfo, ResourcesInfo и у DiplomacyInfo. Ни один не помечен как Serializable.
Тогда почему ошибка возникает только у TerritoriesInfo? И почему после того, как мы помечаем поле TerritoriesInfo territoriesInfo как transien, то у других двух классов ошибка не появляется? Они там сами себя Serializable сделали?
Или я должен это на веру принять и не ставить под сомнение все лекции на JavaRush? Или я сам должен был додумать это?
Несколько раз перечитывал этот листинг, думая, что я что-то не понимаю.
Ошибка возникает во всех трех классах. ТerritoriesInfo просто объявлен раньше других, и программа падает на нем. А затем видно оставшиеся классы сделали сериализуемыми.
Не вижу где оставшиеся классы сделали сериализуемыми🤷 это не показано и нигде об этом не говорится. Нельзя же просто всё на веру принимать. Вот когда я эту лекцию читал повторно, то картина в голове не сходилась.
" Ты же помнишь, что мы передавали в файл именно набор байт, а не просто объект или текст? Ну, вот так этот набор и выглядит "
Так выглядит отсутствие кодировки UTF-8
это не так))
если бы это было так, ты бы сериализовал в блокнот через
ObjectOutputStream
и смог бы прочитать обычным ридером обратно, но ты не сможешь)) тут не те байты пишутся))
так можно - все что угодно. что нельзя прочитать, назвать отсутствием кодировки UTF-8
в общем это не отсутствие, а присутствие другой (в перемешку связанной с Char (UTF-8)) кодировкой
Во
Чтобы вручную лапками не набирать private static final long serialVersionUID
Intellij IDEA -> File -> Settings -> Editor -> Inspections -> Serialization issues -> Serializable class without ‘serialVersionUID’
Ставим галку
Severity ->Error / Warning (на Ваше усмотрение)
После этого будет высвечиваться либо предупреждение, либо ошибка
Вероятно потому что твой текстовый редактор, через который ты открыл и смотришь на записанный в файл объект прогнал единицы и нули через какую то кодировку возможно UTF-8 и в соответствии с этой кодировкой там будут "вот такие кракозябры", но когда эти единицы и нули затем прочтет JVM она то знает что это будет объект и как правильно эти единицы и нули интерпретировать и выдаст тебе твой объект а не кракозябры.
ты сериализуешь объект, понимаешь? ОБЪЕКТ это не просто буковки там еще сигнальная информация и проч. и проч. Так вот когда ты его в UTF-8 откроешь из за того что там все съехало даже если там и были русские буквы ты их все равно не увидишь.
все ты там прекрасно увидишь) конкретно вот эта околесица - Франция воюет СЃ Россией, Рспания заняла позицию нейтралитетаuq ~ t "РЈ Рспании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Рспании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций - так выглядит кириллица в неправильной кодировке) у тебя в файле же байты лежат, а текстовый редактор их автоматически приводит к тексту. Ток в данном случае некорректно)
- Тссс... Никому ни слова, что в своем классе можно определить (не переопределить) методы writeObject и readObject и задать свой ход сериализации.
private void writeObject(ObjectOutputStream) throws IOExcetion{
...
stream.defaultWriteObject();
...
}
и
private void readObject(ObjectInputStream) throws IOException, ClassNotFoundException{
...
stream.defaultReadObject();
...
}
Статья, и правда, отличная, есть только один нюанс:
А теперь давай попробуем сделать то же, но уберем из нашего класса SavedGame идентификатор версии.
Не будем переписывать оба наших класса, код в них будет тем же, просто из класса SavedGame уберем private static final long serialVersionUID.
Вот наш объект после сериализации:
Здесь возникает небольшая путанница: нужно было пояснить, что объект был сериализован, когда в классе ещё присутствовало поле идентификатора версии, а десериализован после того, как его убрали. В этом случае, да, возникнет исключение.
Если же убрать идентификатор версии, сериализовать экземпляр, а потом десериализовать, не меняя исходный код класса, то исключения InvalidClassException не возникнет
да, этот нюас заставляет думать, что в конкретном примере Java(TM) Object Serialization Specification не отрабатывает, хотя это не так.
Не знаю, когда этого можно ожидать?
Спасибо ты спас меня от когнитивного диссонанса, ибо я успешно сериализовывал и десериализовывал без идентификатора версии, а тут мне сказали что так не получится. o_o
У меня вот вопрос остался, если я добавлю явно SerialVersionUID = 1 (например) , и сериализую объект cat (например а унего будет поле ссылочного типа энимал напрмер ), а потом скажем я изменю класс так что этого поля больше не будет, обратно при десириализации что произойдет? SerialVersionUID - будет подходящий я его скажем оставлю таким же в классе. какое то исключение должно выброситься?
по вашей ссылке во втором диалоге говорится:
"Provided the serialVersionUID remains the same, field addition and deletion are both compatible under the rules defined in the Object Versioning chapter of the Object Serialization Specifcation, which you should certainly read."
"It also says that the serialization and deserialization part works correctly in the case where the stream from the old class contains the value and the new class doesn't: " the value of the field will be set to the default value because no value is available in the stream"
т.е. это можно интерпретировать скорее как: как null десериализуется не весь объект кошка - а новое удалённое или добавленное поле. (хотя странно, как может десериализироваться как null удалённое поле. скорее всего его просто не будет).
скорее всего в этом последнем абзаце имеется ввиду, что поле не удалено, а помечено как transient, поэтом и дефолтное значение.
как таки будет при удалении - таки надо проверять
Как создать программу, в которой у пользователя будут запрашиваться такие данные, как: имя, логин, возраст, а также список его хобби. После ввода всех данных выполните сериализацию и далее десиреализацию. Объект что будет получен при десериализации должен быть выведен с использованием переопределенния метода toString().
Если ты рос в 90-е и помнишь игровые приставки тех времен, наверняка знаешь, что в них отсутствовала очевидная сегодня вещь — сохранение и загрузка игры :)
Да ну нафиг! И кто бы стал в такое играть?! Маньяк-мазохист?!
В такое играли все, у кого была такая счастливая возможность. Не всем это было доступно. А ещё в то время лишь в некоторых домах были цветные телевизоры - ламповые, диагональю максимум 21 дюйм, у остальных - чёрно-белые, и то не у всех. Мониторов, представляешь? - не было! Вместо них приспосабливали имеющиеся телевизоры. А загружались программы с кассетных магнитофонов (это вместо дисковода), которые тоже были в то время дефицитом, но чуть более доступными чем телевизоры.
а еще на первую плойку отдельно продавались карты памяти (если не ошибаюсь на 32мб? не помню уже или 32 слота). и в ней было определенное количество слотов сохранения игры. причем фишка в том, что карта памяти одна на все игры. т.е. тебе дается 32 сохранения на все игры которые у тебя есть))))) соответственно, если карты памяти нет - сохранить игру не сможешь :D
Вы бы не могли пояснить реализацию LinkedList в Java. Теоретически я понимаю почему ссылка на объект-ноду следующую не сериализуется (зачем нам помнить адрес, когда расположение данных в куче другое), но нам же надо запомнить данные ноды, а модификатор transient полностью убивает сериализацию. Как же данные запоминаются, и запоминаются ли они ???
Данные не теряются при сериализации LinkedList, так как в нем переопределены методы writeObject и readObject (см. исходники LinkedList). Действительно, все поля LinkedList помечены как transient и стандартный механизм сериализации их пропускает. Зато в этих двух методах вручную написана сериализация. Там они сохраняют какую-то техническую информацию, затем размер списка, затем все элементы один за другим.
Статья посеет еще больше вопросов.
Во первых:
"Сейчас мы уберем переменную serialVersionUID. Теперь запускаем программу - и вот оно наше исключение" С чего это вдруг то? Тут упущен момент. Сериализуем объект в одном сеансе программы. Теперь изменяем исходный код нашего класса, добавляя/убавляя поля/методы и т.д. serialVersionUID класса изменяется. Вот теперь при дессериализации того самого объекта, сериализованного в первый раз, будет исключение.
Во вторых:
"Сделаем поля класса отдельными классами. Вот видите исключение. Делаем эту переменную trancient и все гуд." С чего это? Там осталось еще два класса, которые не реализуют Serializable.
Итог:
Ладно, я это понял. Но кто-то ищет в ссылках профессора достоверную информацию, а получает вот это вот!
Тут ничего не понятного нету. Сейчас объясню:
Во первых:
Когда мы добавили serialVersionUID = 1 он записал его с таким номером а когда мы его убрали он записал автоматически и значение serialVersionUID стали разными. Так как при запуске проверятся на равенства значение serialVersionUID и они не равны потом мы получим исключение.
Во вторых:
Другие классы реализуют Serializable и только TerritoriesInfo не реализуют Serializable (просто тут сказали образно и не показали это) и когда мы запускаем так то получаем ошибку так как TerritoriesInfo не реализуют Serializable а если перед поле TerritoriesInfo ставить transient то java не будет его записывать (Как бы на него не обращает внимание и вместо его записывает null)
Надеюсь я ответил на твои вопросы :)
Мне итак это понятно. Я негодовал по поводу подачи информации. Тут по первому пункту сохранение и загрузка происходит за один сеанс работы программы. serialVersionUID хоть убирай, хоть оставляй, хоть меняй - никакого исключения не будет. Плохо показано.
По второму пункту. Так я о том и пишу. Об этом говорят, а в коде не указывают. В общем подгорело у меня тогда.
Но все равно спасибо за коммент, я думаю многим он будет полезен))
Очень плохо показано. Из материала получается, что нужно всегда прописывать какой-то serialVersionUID, иначе работать не будет.
Во-первых будет.
Во-вторых, так и не понятно, что будет, если класс фактически изменится, а UID нет. Не будет ли хуже?
Если это сделать необдуманно - то будет конечно! Но, если будут внесены изменения, после которых (после десериализации) объект сможет нормально "функционировать" - всё ок!
В Head First Java в 14 главе кратко и доступно приведен список изменений, которые могут "навредить", и после которых с большой вероятностью объект сможет быть валидно десериализован
Судя по этому утверждению "Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение.", я так понимаю, классы DiplomacyInfo и ResourcesInfo должны были быть прописаны как:
public class DiplomacyInfo implements Serializable
public class ResourcesInfo implements Serializable
чтобы программа нормально сработала и не выбрасывалось исключение????
В таком случае, почему это не написано в лекции? Или я что-то не так понимаю?
Там же образно говориться )) То есть мы сами должны догадаться так как там ничего трудного и непонятного нету :)
Если ты внимательно прочитаешь то все поймешь :)
Например там примерно сказана если мы добавляем все объекты без сериз то джава ругается значить мы должны добавлять сериз для каждого объекта которого мы хотим сохранить в файл. Значить мы образно добавили для всех объектов сериз.
Далее если мы хотим чтоб одна из объектов не сохранилась значить мы образно следаем этого объекта без сериз и для этого объекта мы добавляем в поле transient и джава на это не будет обращать внимание и сохранить в место него нул
Почему мы объявили транзиентным только TerritoriesInfo и у нас сразу стал класс сериализоваться? Почему не нужно аналогичную процедуру делать с ResourcesInfo и DiplomacyInfo? В чем отличие?
Тоже несколько раз перечитывал, и не понимал почему ошибка только у TerritoriesInfo.
И почему после того, как его пометили transient, то код вдруг стал рабочим. Тогда ошибка должны возникнуть и у ResourcesInfo и у DiplomacyInfo. Но автор наверное решил, что мы должны принять это на веру или сами додуматься.
Привет!
Помогите, пожалуйста, разобраться.
Код при попытке десериализации выдает ошибку:
java.io.StreamCorruptedException: invalid type code: AC на строку:
centaur[i] = (Centaur) ois.readObject();
public class Centaur implements Serializable {
String name;
String color;
int age;
double weight;
boolean isCruel;
private static final long serialVersionUID = 1L;
//...конструкторы, геттеры, сеттеры, toString()...
public static void writeCentaur(String fileName, Centaur centaur) {
try (OutputStream os = new FileOutputStream(fileName, true);
BufferedOutputStream bos = new BufferedOutputStream(os, 10_000);
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(centaur);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
return;
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
InvalidClassExceptionне возникнет