Советы и лайфхаки

Java сериализация графов – Граф сериализации в Java. Лучше 40 раз по разу, чем 1 раз 40 раз / Песочница / Хабр

Как работает сериализация в Java

В этой статье мы расскажем, что такое сериализация и как она работает в Java

Введение

Сериализация объекта это способность объекта сохранять полную копию его и любых других объектов на которые он ссылается, используя поток вывода (например, во внешний файл). Таким образом, объект может быть воссоздан из сериализованной (сохраненной) копии немного позже, когда это потребуется. Сериализация объектов, как новая возможность введенная в JDK 1.1, предоставляет функцию для преобразования групп или отдельных объектов, в поток битов или массив байтов, для хранения или передаче по сети. И как было сказано, данный поток битов или массив байтов, можно преобразовать обратно в объекты Java. Главным образом это происходит автоматически благодаря классам ObjectInputStream и ObjectOutputStream. Программист может решить реализовать эту функцию, путем реализации интерфейса Serializable при создании класса. Процесс сериализиции также известен как
маршалинг
объекта, десериализация же известна как демаршалинг. Сериализация это механизм, предоставляющий возможность объекту сохранить свою копию и все другие объекты на которые ссылается данный объект, во внешний файл с помощью класса ObjectOutputStream. Сохранены могут быть структуры данных, диаграммы, объекты класса JFrame или любые другие объекты, независимо от их типа. В то же время, сериализация сохраняет информацию о том, какого типа объект, чтобы в дальнейшем, при десериализации, эта информация использовалась для воссоздания точного типа объекта, которым он был. Итак, сериализация предоставляет следующие возможности:
  • Система хранения объектов, т.е.: сохранение их свойств во внешний файл, на диск или в базу данных.
  • Система вызовов удаленных процедур.
  • Система распределения объектов, для примера, в программных компонентах типа COM, COBRA.
  • Система идентификации изменений переменных данных во времени.
Чтобы полностью понять концепцию сериализации, надо иметь четкое понимание других двух концепций — персистентности объектов и потоков. Здесь мы немного расскажем о каждой из них, дабы вспомнить. Полное же пояснение касательно них, требовало бы по отдельной главе для каждой из этих концепций.

Потоки:

Каждая программа должна записывать свои данные в место хранения или канал, и каждая программа должна считывать данные из канала или места хранения. В Java, эти каналы, куда программы записывают и откуда программы считывают данные, называются Потоками(Stream). Рисунок 1. Графическое представление Потоков Потоки в основном делятся на два типа:
  • Байтовые классы-потоки именуемые *Streams
  • Символьные классы-потоки именуемые *Reader и *Writer
Каждый поток записи данных, содержит набор методов записи. И каждый поток считывания данных, соответственно имеет подобный набор методов чтения. Как только поток создается, все эти методы должны быть вызваны.

Персистентность

Персистентность объекта это способность обекта жить или по-другому — «пережить» выполнение программы. Это значит, что любой объект, который был создан во времени выполнения, уничтожается «мусорщиком» JVM, всякий раз, когда данный объект далее перестает использоваться. Но в случае реализации API персистентности, данные объекты не будут уничтожаться «мусорщиком» JVM, вместо чего им будет позволено «жить», что также дает возможность доступа к ним при следующем запуске приложения. Другими словами, персистенция означает существование времени жизни объекта, независимо от времени жизни приложения, которое запущено. Один из способов реализации персистентности это хранение объектов где-нибудь во внешнем файле или в базе данных, а затем восстановление их в более позднее время, используя данные файлы или базу данных как источники. Здесь сериализация и вступает в игру. Любой неперсистентный объект существует так долго, как долго работает JVM. Сериализованные объекты — это просто объекты, преобразованные в потоки, которые затем сохраняются во внешний файл или передаются через сеть для хранения и восстановления.

Реализация интерфейса Serializable

Любой класс должен реализовывать интерфейс
java.io.Serializable
для сериализации объектов этого класса. Интерфейс Serializable не имеет методов и только маркерует класс, чтобы можно было идентифицировать его как сериализуемый. Только поля объекта сериализованного класса могут быть сохранены. Методы или конструкторы не сохраняются, как части сериализованного потока. Если какой-либо объект действует как ссылка на другой объект, то поля этого объекта также сериализованны, если класс этого объекта реализует интерфейс Serializable. Другими словам, получаемый таким образом граф этого объекта, сериализуем полностью. Граф объекта включает дерево или структуру полей объекта и его подобъектов. Два главных класса, которые помогают реализовать интерфейс
Seriliazable
:
  • ObjectInputStream
  • ObjectOutputStream
Листинг 1. Пример простого класса, чтобы показать сериализацию
import java.io.*;
public class RandomClass implements Serializable {
 
 private static int r() {
        return (int)(Math.random() * 10);
 }
    private int data[];
    
public RandomClass() {
        datafile = new int[r()];
        for (int i=0; i<datafile.length; i++)
        datafile[i]=r();
 }
    public void printout() {
 System.out.println("This RandomClass has "+datafile.length+" random integers");
 for (int i=0; i<datafile.length; i++) {
        System.out.print(datafile[i]+":");
        System.out.println();
    }
}
В приведенном выше коде, создается класс, который является сериализуемым, т.к. «промаркерован» интерфейсом сериализации. Класс создает массив случайных целых чисел, когда создается его экземпляр. Приведенный ниже код, показывает возможность записи объектов в поток, используя класс
ObjectOutputStream
. Программа имеет массив целых чисел, но для сериализации мы не должны перебирать ее внутренние объекты. Интерфейс Seriliazable заботится об этом автоматически. Листинг 2. Простой пример сериализации объектов для вывода в файл
import java.io.*;
import java.util.*;
public class OutSerialize {
    public static void main (String args[]) throws IOException {
        RandomClass rc1 = new RandomClass();
        RandomClass rc2 = new RandomClass();

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objects.dat"));
        Date now = new Date(System.currentTimeMillis());

        out.writeObject(now);
        out.writeObject(rc1);
        out.writeObject(rc2);
out.close();
        System.out.println("I have written:");
System.out.println("A Date object: "+now);
        System.out.println("Two Group of randoms");
rc1.printout();
rc2.printout();
 }
}
Код ниже демонстрирует возможности класса ObjectInputStream, который считывает сериализованные данные с внешнего файла, в программу. Заметьте, что объекты считываются в том же порядке, в котором были записаны в файл. Листинг 3. Чтение сериализованных объектов или Десериализация
import java.io.*;
import java.util.*;
public class InSerialize {
 public static void main (String args[]) throws  IOException, ClassNotFoundException {
    ObjectInputStream in =  new ObjectInputStream (new FileInputStream("objects.dat"));
 Date d1 = (Date)in.readObject();
 RandomClass rc1 = (RandomClass)in.readObject();
    RandomClass rc2 = (RandomClass)in.readObject();
    System.out.println("I have read:");
    System.out.println("A Date object: "+d1);
    System.out.println("Two Group of randoms");
    rc1.printout();
rc2.printout();
 }
}
Почти все классы Java могут быть сериализованны, включая классы AWT. Фрейм, который является окном, содержит набор графических компонентов. Если фрейм сериализован, механизм сериализации заботится об этом и сериализует все его компоненты и данные(позицию, содержание и т.д.). Некоторые объекты классов Java, не могут быть сериализованы, потому что содержат данные, что ссылаются на кратковременные ресурсы операционных систем. Например классы
java.io.FileInputStream
и java.lang.Thread. Если объект содержит ссылки на несериализуемые элементы, вся операция сериализации потерпит неудачу и будет выброшено исключение NotSerializableException. Если какой-либо объект ссылается на ссылку несериализованного объекта, то его можно сериализовать используя ключевое слово transient. Листинг 4. Создание сериализуемых объектов используя ключево слово transient
public class Sclass implements Serializable{
public transient Thread newThread;

    private String studentID;
    private int sum;
}

Безопасность в Сериализации

Сериализация класса в Java, подразумевает передачу всех его данных во внешний файл или базу данных через поток. Мы можем ограничить данные, которые будут сериализованны, когда того пожелаем. Есть два способа сделать это:
  • Каждый параметр класса объявленный как transient, не сериализуются(по умолчанию все параметры класса сериализуются)
  • Или каждый параметр класса, который мы хотим сериализовать, помечается тегом Externalizable(по умолчанию никакие параметры не сериализуются).
Поле данных не будет сериализовано с помощью ObjectOutputStream, когда оно будет вызвано для объекта, если это поле данных, данного объекта помечено как transient. Например: private transient String password. С другой стороны, для явного объявления данных объекта как сериализуемых, мы должны промаркировать класс как
ExternalizablewriteExternal
и readExteranl для записи и чтения данных этого объекта явно.

Заключение

Особенность сериализации оъектов использована во многих распределенных системах, как способ передачи данных. Но сериализация раскрывает скрытые детали, таким образом разрушая подлинность абстрактных типов данных, что в свою очередь разрушает инкапсуляцию. В тоже время приятно знать, что данные сериализованного объекта, те же самые данные, что были в исходном, оригинальном объекте. Это также отличная возможность для реализации интерфейса ObjectInputValidation и переопределения метода validateObject(), даже если ипользуются несколько строк кода. Если объект не найден, то мы можем надлежащим образом выбросить исключение InvalidObjectException. Оригинал статьи: How to use…

javarush.ru

java — Сериализация части графа объектов

У меня проблема с Java-сериализацией. У меня есть график объектов и вы хотите настроить, где остановиться, когда я сериализую корневой объект с клиента на сервер.

Позвольте сделать это немного конкретным, понятным, предоставив образец сценария. У меня есть классы типа


компании Сотрудник (аннотация)
Менеджер расширяет штат сотрудников
Секретарь расширяет штат сотрудников
Аналитик расширяет штат сотрудников Проект

Вот соотношения:
Компания (1) — (п) Сотрудник
Менеджер (1) — (п) Проект
Аналитик (1) — (п) Проект

Представьте, я на стороне клиента, и я хочу создать новую компанию, назначить ее 10 сотрудникам (новым или некоторым существующим) и отправить эту новую компанию на сервер. То, что я ожидаю в этом сценарии, — это сериализовать компанию и всех ограничивающих сотрудников на стороне сервера, потому что я сохраню отношения в базе данных. До сих пор нет проблем, поскольку механизм сериализации Java по умолчанию сериализует весь граф объекта, исключая поле, которое является статическим или временным.

Моя цель заключается в следующем сценарии. Представьте, я загрузил компанию и ее 1000 сотрудников с сервера на клиентскую сторону. Теперь я хочу только переименовать название компании (или другое поле, которое непосредственно принадлежит компании) и обновить эту запись. На этот раз я хочу отправить только объект компании на серверную сторону, а не весь список сотрудников (я просто обновляю имя, сотрудники в этом случае неактуальны). Моя цель также включает в себя конфигурацию высказывания, передачу компании И сотрудников, а не Project-Relations, вы должны остановиться там.

Знаете ли вы какую-либо возможность достижения этого в общем виде, не реализуя writeObject, readObject для каждого объекта Entity-Object? Каковы будут ваши предложения?

Я бы очень признателен за ваши ответы. Я открыт для любых идей и готов ответить на ваши вопросы, если что-то неясно.

qaru.site

Сериализация и десериализация в Java

Привет! В сегодняшней лекции мы поговорим о сериализации и десериализации в Java. Начнем с простого примера. Допустим, ты создатель компьютерной игры. Если ты рос в 90-е и помнишь игровые приставки тех времен, наверняка знаешь, что в них отсутствовала очевидная сегодня вещь — сохранение и загрузка игры 🙂 Если нет… представь себе! Боюсь, сегодня игра без такой возможности будет обречена на провал! А, собственно, что такое «сохранение» и «загрузка» игры? Ну, в обычном смысле мы понимаем, что это: мы хотим продолжить игру с того места, где закончили в прошлый раз. Для этого мы создаем некую «контрольную точку», которую потом используем для загрузки игры. Но что это значит не в житейском, а в «программистском» смысле? Ответ прост: мы сохраняем состояние нашей программы. Допустим, ты играешь в стратегию за Испанию. У твоей игры есть состояние: кто какими территориями владеет, у кого сколько ресурсов, кто с кем в союзе, а кто наоборот — в состоянии войны, и так далее. Эту информацию, состояние нашей программы, необходимо как-то сохранить, чтобы в дальнейшем восстановить данные и продолжить игру. Для этого как раз и используются механизмы сериализации и десереализации. Сериализация — это процесс сохранения состояния объекта в последовательность байт. Десериализация — это процесс восстановления объекта из этих байт. Любой Java-объект преобразуется в последовательность байт. Для чего это нужно? Мы уже не раз говорили, что программы не существуют сами по себе. Чаще всего они взаимодействуют друг с другом, обмениваются данными и т.д. И байтовый формат для этого удобен и эффективен. Мы можем, например, превратить наш объект класса SavedGame (сохраненная игра) в последовательность байт, передать эти байты по сети на другой компьютер, и на втором компьютере превратить эти байты снова в Java-объект! На слух воспринимается сложно, да? Судя по всему, и организовать этот процесс будет непросто :/ К счастью, нет! 🙂 В 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 — один из таких интерфейсов. Еще один важный момент: переменная private static final long serialVersionUID, которую мы определили в классе. Зачем она нужна? Это поле содержит уникальный идентификатор версии сериализованного класса. Идентификатор версии есть у любого класса, который имплементирует интерфейс Serializable. Он вычисляется по содержимому класса — полям, порядку объявления, методам, порядку объявления. И если мы поменяем в нашем классе тип поля и/или количество полей, идентификатор версии моментально изменится. serialVersionUID тоже записывается при сериализации класса. Когда мы пытаемся провести десериализацию, то есть восстановить объект из набора байт, значение serialVersionUID сравнивается со значением serialVersionUID класса в нашей программе. Если значения не совпадают, будет выброшено исключение java.io.InvalidClassException. Мы увидим пример этого ниже. Чтобы избежать таких ситуаций, мы просто вручную задаем для нашего класса этот идентификатор версии. В нашем случае он будет равен просто 1 (можешь подставить любое другое понравившееся число). Ну, самое время попробовать сериализовать наш объект 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);

       
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       
       objectOutputStream.writeObject(savedGame);

       
       objectOutputStream.close();
   }
}
Как видишь, мы создали 2 потока — FileOutputStream и ObjectOutputStream. Первый из них умеет записывать данные в файл, а второй — преобразует объекты в байты. Ты уже видел подобные «вложенные» конструкции, например, new BufferedReader(new InputStreamReader(...)), в прошлых лекциях, так что они не должны тебя пугать 🙂 Создав такую «цепочку» из двух потоков мы выполняем обе задачи: превращаем объект 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 {

       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=[Франция воюет с Россией, Испания заняла позицию нейтралитета]} Отлично! У нас получилось сначала сохранить состояние нашей игры в файл, а потом восстановить ее из файла. А теперь давай попробуем сделать то же, но уберем из нашего класса 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 провинций А при попытке его десериализовать произошло вот что: 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?
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private 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;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Что ж, давай проверим это на практике! Оставим пока все как есть и попробуем сериализовать объект 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);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Результат: Exception in thread «main» java.io.NotSerializableException: DiplomacyInfo Не вышло! Собственно, вот и ответ на наш вопрос. При сериализации объекта сериализуются все объекты, на которые он ссылается в своих переменных экземпляра. И если те объекты тоже ссылаются на третьи объекты, они тоже сериализуются. И так до бесконечности. Все классы в этой цепочке должны быть Serializable, иначе их невозможно будет сериализовать и будет выброшено исключение. Это, кстати, в перспективе может создать проблемы. Что делать, например, если часть класса при сериализации нам не нужна? Или, к примеру, класс TerritoryInfo в нашей программе достался нам «по наследству» в составе какой-то библиотеки. При этом он не является Serializable, и мы, соответственно, не можем его менять. Получается, что и добавить поле TerritoryInfo в наш класс SavedGame мы не можем, ведь тогда весь класс SavedGame станет несериализуемым! Проблема :/ Проблемы такого рода решаются в 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;
   }

   
}



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);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
А вот и результат: SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info=’У Испании 100 золота, у России 80 золота, у Франции 90 золота’}, diplomacyInfo=DiplomacyInfo{info=’Франция воюет с Россией, Испания заняла позицию нейтралитета’}} Заодно мы получили ответ на вопрос, какое же значение будет присвоено transient-полю. Ему присваивается значение по умолчанию. В случае с объектами это null. На досуге ты можешь прочитать вот эту отличную статью про сериализацию. В ней еще написано об интерфейсе Externalizable, о котором мы поговорим в следующей лекции. Кроме того, глава на эту тему есть в книге «Head-First Java», обрати на нее внимание 🙂

javarush.ru

java — В чем разница между Serializable и Externalizable в Java?

Чтобы добавить к другим ответам, реализуя java.io.Serializable , вы получаете «автоматическую» возможность сериализации для объектов вашего класса. Нет необходимости реализовывать какую-либо другую логику, она будет работать. Время выполнения Java будет использовать рефлексию, чтобы выяснить, как маршалировать и развязывать ваши объекты.

В более ранней версии Java отражение было очень медленным, и поэтому сериализация крупных объектных графов (например, в клиент-серверных приложениях RMI) была проблемой производительности. Чтобы справиться с этой ситуацией, был предоставлен интерфейс java.io.Externalizable , который похож на java.io.Serializable но с настраиваемыми механизмами для выполнения функций маршаллинга и unmarshalling (вам необходимо реализовать readExternal и writeExternal для вашего класса). Это дает вам возможность обойти узкое место производительности отражения.

В последних версиях Java (начиная с версии 1.3, конечно) производительность отражения намного лучше, чем раньше, и поэтому это гораздо менее проблематично. Я подозреваю, что вам будет трудно получить значимую выгоду от Externalizable с помощью современной JVM.

Кроме того, встроенный механизм сериализации Java не является единственным, вы можете получить сторонние замены, такие как JBoss Serialization, что значительно быстрее и является заменой по умолчанию.

Большой недостаток Externalizable заключается в том, что вы должны сами поддерживать эту логику — если вы добавляете, удаляете или изменяете поле в своем классе, вам необходимо изменить методы writeExternal / readExternal для его учета.

Таким образом, Externalizable является реликвией Java 1.1 дней. В этом нет необходимости.

Для полноты ключевое слово transient также закрывает промежуток между ними.

Если вы хотите только сериализовать часть своего объекта, просто установите определенные поля как transient , отметив их как не сохраняемые, и реализуйте Serializable .

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html

Сериализация по умолчанию несколько подробна и предполагает максимально возможный сценарий использования сериализованного объекта, и, соответственно, формат по умолчанию (Serializable) аннотирует результирующий поток информацией о классе сериализованного объекта.

Экстернализация дает производителю потока объектов полный контроль над точными метаданными класса (если таковые имеются) за минимально необходимой идентификацией класса (например, его имя). Это явно желательно в определенных ситуациях, таких как закрытые среды, в которых сопоставляются производитель потока объектов и его потребителя (который подтверждает объект из потока), а дополнительные метаданные о классе не имеют никакой цели и ухудшают производительно

code-examples.net

Java 8 сериализация — urvanov.ru

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 файлы (NIO.2)».
Предыдущая статья — «Java 8 потоки ввода/вывода».

Класс ObjectInputStream  реализует интерфейс java.io.ObjectInput, а класс ObjectOutputStream  реализует интерфейс java.io.ObjectOutput, в который описаны основные методы для чтения и записи объектов.

Чтение объекта из ObjectInputStream  осуществляется с помощью метода

public final Object readObject() throws IOException, ClassNotFoundException

public final Object readObject()

                        throws IOException,

                               ClassNotFoundException

Запись объекта в ObjectOutputStream  производится с помощью метода:

public final void writeObject(Object obj) throws IOException

public final void writeObject(Object obj)

                       throws IOException

Эти методы считывают и записывают в поток не только сам передаваемый объект, но и все объекты, на которые он ссылается, которые необходимы, для того чтобы восстановить исходный объект при чтении. Если какой-нибудь объект записывается в поток два раза, то реально он будет записан туда только один раз, но будет записано две ссылки на него. Если же какой-нибудь объект записывается в два разных потока, то при чтении из этих двух потоков получится два разных объекта.

Только классы, реализующие интерфейсы java.io.Serializable  или java.io.Externalizable, могут быть считаны из потока и записаны в поток.

Метод readObject  возвращает тип Object , который должен быт приведён к ожидаемому типу с помощью операции приведения типов. Строки и массивы в Java являются объектами. Примитивные типы считываются с помощью методов DataInput  ( ObjectInput  наследуется от DataInput ) и записываются с помощью методов DataOutput  ( ObjectOutput  наследуется от DataOutput ).

Запись объекта называется сериализацией.

Чтение объекта называется десериализацией.

Поля, объявленные как transient  или static, игнорируются в процессе сериализации/десериализации.

При десериализации всегда создаются новые объекты, чтобы защитить уже существующие объекты от изменения.

В процессе чтения объекты создаются аналогично выполнению конструктора для нового объекта. Сначала выделяется память и заполняется нулями. Для несериализуемых классов вызываются конструкторы по умолчанию, а поля сериализуемых классов считываются из потока, начиная от наиболее близкого к java.lang.Object  класса и заканчивая наиболее специфичным классом.

Если классу нужна особая обработка в процессе сериализации/десериализации, то он должен реализовать методы:

private void writeObject(java.io.ObjectOutputStream stream) throws IOException; private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException; private void readObjectNoData() throws ObjectStreamException;

private void writeObject(java.io.ObjectOutputStream stream)

     throws IOException;

private void readObject(java.io.ObjectInputStream stream)

     throws IOException, ClassNotFoundException;

private void readObjectNoData()

     throws ObjectStreamException;

Метод readObject  отвечает за чтение и восстановление состояния объекта класса, записанного в поток соответствующим методом writeObject. Метод не должен беспокоиться о состоянии, принадлежащем его суперклассам или подклассам. Состояние восстанавливается чтением данных из ObjectInputStream  для каждого поля и присвоением этих значений соответствующим полям объекта. Чтение примитивных типов происходит с помощью DataInput.

Любая попытка считать данные, которые выходят за границу данных, записанных методом writeObject , приводит к исключению OptionalDataException. Методы чтения примитивных типов или в массив байт возвращают -1 или бросают исключение EOFException  в зависимости от метода.

Метод readObjectNoData  отвечает за инициализацию состояния объекта в случае, когда поток сериализации не имеет данного класса в качестве суперкласса объекта, десериализация которого происходит. Это может возникнуть, когда при сериализации использовалась другая версия класса, которая не имела в качестве суперкласса того класса, от которого наследуется класс у считывающей стороны. Эта ситуация может также возникнуть в случае подделки сериализованного потока. Метод readObjectNoData  полезен для инициализации объектов «враждебного» или повреждённого потока.

Сериализация не читает и не присваивает значения полям, которые не реализуют интерфейс java.io.Serializable. Подклассы класса Object, которые не сериализуемые, могут быть сериализуемыми, для этого они должны иметь конструктор по умолчанию. В этом случае задача сохранения и восстановления их состояния ложится на дочерний класс.

Любое исключение в процессе десериализации перехватывается ObjectInputStream -ом и останавливает процесс чтения.

Реализация интерфейса Externalizable  позволяет полностью контролировать содержимое и формат сериализации. Методы void writeExternal(ObjectOutput out) throws IOException  и void readExternal(ObjectInput in) throws IOException, ClassNotFoundException  интерфейса Externalizable  вызываются при сохранении и восстановлении состояния объектов.  При реализации каким-нибудь классом они могут записывать и считывать своё состояние с помощью методов ObjectOutput  и ObjectInput. Ответственность за обработку версионности ложится на сам объект.

Десериализация констант перечислений отличается от обычной сериализации и десериализации объектов. Сериализованная форма констант содержит только и имена, Значения полей констант опускаются. При десериализации константы ObjectInputStream  считывает имя константы из потока, затем вызывает метод Enum.valueOf(Class, String)  для получения значения. Процесс десериализации перечислений не может быть изменён. Любой метод readObject , readObjectNoData  и readResolve , объявленный в перечислении игнорируется.  Так же игнорируются поля serialPersistentFields  и serialVersionUID , перечисления имеют фиксированный serialVersionUID  0L.

Статическое поле serialVersionUID  означает версию класса. Оно объявляется так:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

Если в сериализуемом классе не объявлено поля serialVersionUID, то его значение вычисляется автоматически на основе окружения и полей класса. При чтении класса проверяется serialVersionUID. Если serialVersionUID  считываемого класса не равен serialVersionUID  нашего класса, то возникает исключение java.io.InvalidClassException. Старайтесь всегда объявлять вручную serialVersionUID, так как автоматически вычисленное значение может сильно отличаться в зависимости от платформы и реализации.

Класс, для которого при сериализации нужно использовать другой объект, должен реализовать метод:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

Метод writeReplace() вызывается в процессе сериализации, если он существует и доступен.

Если класс должен использовать другой объект при десериализации, то он должен реализовать метод:

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

Метод readResolve() вызывается в процессе десериализации, если он существует и доступен.

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 файлы (NIO.2)».
Предыдущая статья — «Java 8 потоки ввода/вывода».


Поделиться:
Загрузка…

urvanov.ru

java — Сериализация часть графа объектов

У меня есть проблема в отношении Java пользовательских сериализации. У меня есть графы объектов и хочу настроить, где остановиться, когда я сериализовать корневой объект от клиента к серверу.

Давайте сделаем его немного бетона, ясно давая пример сценария. У меня есть классы типа

Компания
Сотрудник (аннотация)
Менеджер расширяет сотрудник
Секретарь расширяет Сотрудник
Analyst расширяет сотрудник
проекта

Вот отношения:
Компания (1) — (п) Сотрудник
Manager (1) — (п) Проект
Analyst (1) — (п) проекта

Представьте себе, я на стороне клиента, и я хочу, чтобы создать новую компанию, назначить ему 10 сотрудников (новые или несколько существующих) и отправить эту новую компанию на сервер. Что я ожидаю в этом сценарии для сериализации компании и все ограничивающих сотрудников на сторону сервера, потому что я буду сохранять отношения на базе данных. До сих пор никаких проблем, так как механизм по умолчанию сериализация упорядочивает весь граф объектов, за исключением поля, которые являются статическими или временными.

Моя цель о следующем сценарии. Представьте себе, я загрузил компанию и ее 1000 сотрудников с сервера на сторону клиента. Теперь я только хочу, чтобы переименовать название компании (или какой-либо другой области, которые непосредственно принадлежит компании) и обновить эту запись. На этот раз, я хочу послать только объект компании на стороне сервера, а не весь список сотрудников (я просто обновить имя, сотрудники в этом прецеденте не имеет значения). Моя цель также включает в себя конфигурируемость говоря, передать компании и сотрудников, а не проект-отношения, вы должны останавливаться на достигнутом.

Знаете ли вы какие-либо возможности достижения этой цели в общем виде, без реализации writeObject, readObject для каждого Entity-объекта? Что бы ваши предложения?

Я действительно ценю ваши ответы. Я открыт для любых идей и готов ответить на ваши вопросы в случае, если что-то не понятно.

coredump.su

Добавить комментарий

Ваш адрес email не будет опубликован.