CodeGym /Java блог /Случаен /Сериализация и десериализация в Java
John Squirrels
Ниво
San Francisco

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

Публикувано в групата
здрасти В днешния урок ще говорим за сериализация и десериализация в Java. Ще започнем с прост пример. Представете си, че сте разработчик на компютърни игри. Ако сте израснали през 90-те години и си спомняте игровите конзоли от онази епоха, вероятно знаете, че им липсваше нещо, което днес приемаме за даденост — възможността да запазвате и зареждате игри :) Ако не, представете си това!Сериализация и десериализация в Java - 1Страхувам се, че игра без тези способности днес би била обречена! Както и да е, Howво означава "запазване" и "зареждане" на игра? Е, ние разбираме всекидневния смисъл: искаме да продължим играта от мястото, където сме я спрели. За да направим това, създаваме определена „контролна точка“, която използваме по-късно, за да заредим играта. Но Howво означава това за програмист, а не за обикновен играч? Отговорът е прост: запазваме състоянието на нашата програма. Да приемем, че играете Испания в стратегическа игра. Вашата игра има състояние: кои територии има всеки, колко ресурси има всеки, Howви съюзи съществуват и с кого, кой е във война и т.н. Тази информация, състоянието на нашата програма, трябва по няHowъв начин да бъде запазена, за да възстановим данните и да продължим играта. Както се случва, Сериализацията в Java е процес на запазване на състоянието на обект като поредица от byteове. Десериализацията в Java е процесът на възстановяване на обект от тези byteове. Всеки Java обект може да бъде преобразуван в последователност от byteове. Защо имаме нужда от това? Многократно сме казвали, че програмите не съществуват сами по себе си. Най-често те взаимодействат помежду си, обменят данни и т.н. Байтовият формат е удобен и ефективен за това. Например, можем да конвертираме обект от нашата SavedGameклас в поредица от byteове, прехвърлете тези byteове по мрежата на друг компютър и след това на другия компютър преобразувайте тези byteове обратно в Java обект! Звучи трудно, а? Изглежда, че ще бъде трудно всичко това да се случи: / За щастие, това не е така! :) В Java интерфейсът Serializable е отговорен за процеса на сериализация. Този интерфейс е изключително прост: не е необходимо да прилагате нито един метод, за да го използвате! Вижте колко прост е нашият клас за запазване на игри:

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoryInfo;
   private String[] resourceInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoryInfo, String[] resourceInfo, String[] diplomacyInfo){
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(String[] territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public String[] getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(String[] resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + Arrays.toString(territoryInfo) +
               ", resourceInfo=" + Arrays.toString(resourceInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Три масива отговарят за информация относно територии, ресурси и дипломация, а интерфейсът Serializable казва на Java машината: „ всичко е наред, ако обектите от този клас могат да бъдат сериализирани “. Интерфейс без нито един интерфейс изглежда странно :/ Защо е необходимо? Отговорът на този въпрос е даден по-горе: необходимо е само за предоставяне на необходимата информация на Java машината. В минал урок споменахме накратко интерфейсите на маркерите. Това са специални информационни интерфейси, които просто маркират нашите класове с допълнителна информация, която ще бъде полезна за Java машината в бъдеще. Те нямат ниHowви методи, които трябва да приложите. Ето Serializable — един такъв интерфейс. Ето още един важен момент: Защо се нуждаем отprivate static final long serialVersionUID променлива, която дефинирахме в класа? Това поле съдържа уникалния идентификатор на versionта на сериализирания клас. Всеки клас, който имплементира интерфейса Serializable , има идентификатор на versionта. Определя се въз основа на съдържанието на класа — полета и техния ред на деклариране и методи и техния ред на деклариране. И ако променим типа на полето и/or броя на полетата в нашия клас, идентификаторът на versionта се променя моментално. serialVersionUID също се записва , когато класът е сериализиран. Когато се опитваме да десериализираме, т.е. да възстановим обект от последователност от byteове, стойността на serialVersionUID се сравнява със стойността на serialVersionUIDот класа в нашата програма. Ако стойностите не съвпадат, тогава ще бъде хвърлено изключение java.io.InvalidClassException. Ще видим пример за това по-долу. За да избегнем подобни ситуации, просто задаваме ръчно идентификатора на versionта за нашия клас. В нашия случай то просто ще бъде равно на 1 (можете да използвате всяко друго число, което желаете). Е, време е да опитаме да сериализираме нашия обект SavedGame и да видим Howво ще се случи!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

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

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourceInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and release resources
       objectOutputStream.close();
   }
}
Както можете да видите, създадохме 2 потока: FileOutputStream и ObjectOutputStream . Първият знае How да записва данни във file, а вторият преобразува обектите в byteове. Вече сте виждали подобни вложени конструкции, например new BufferedReader(new InputStreamReader(...)) в предишни уроци, така че те не трябва да ви плашат :) Създавайки тази верига от два потока, ние изпълняваме и двете задачи: преобразуваме обекта SavedGame в поредица от byteове и го запазваме във файл с помощта на метода writeObject() . И, между другото, ние дори не погледнахме Howво имаме! Време е да погледнете file! *Забележка: не е необходимо да създавате file предварително. Ако файл с указаното име не съществува, той ще бъде създаден автоматично* И ето неговото съдържание: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ teritorijInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию РЅРµ йтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РJ Франции 90 Р·Рѕ Р »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ ФранцииРё 8 провинций О, о :( Изглежда, че нашата програма не работи : ( Всъщност тя проработи. Спомняте ли си, че изпратихме поредица от byteове, а не просто обект or текст, към file? Е, това е тази поредица от byteове изглежда така :) Това е нашата запазена игра! Ако искаме да възстановим нашия оригинален обект, т.е.

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{territoryInfo=[Испания има 6 провинции, Русия има 10 провинции, Франция има 8 провинции], resourceInfo=[Испания има 100 златни, Русия има 80 златни, Франция има 90 златни], diplomacyInfo=[Франция е във война с Русия, Испания зае неутрална позиция]} Отлично! Успяхме първо да запазим състоянието на нашата игра във файл и след това да го възстановим от file. Сега нека се опитаме да направим същото, но ще премахнем идентификатора на versionта от нашия клас SavedGame . Няма да пренаписваме и двата си класа. Техният code ще бъде същият. Просто ще премахнем private static final long serialVersionUID от класа SavedGame . Ето нашия обект след сериализация: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ teritorijInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранция воюет СЃ Р РѕСЃСЃРёРµ Р№, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 Р·РѕР»РѕС ‚Р °t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РJ Р˜СЃРїР°РЅРёРё 6 РїСЂРѕРІРёРЅ цийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РJ Франции 8 провинций Но вижте Howво се случва, когато се опитаме да го десериализираме: InvalidClassException: локален клас несъвместим: поток classdesc сериен VersionUID = -196410440475012755, локален клас serialVersionUID = -6675950253085108747 Между другото, пропуснахме нещо важно. Очевидно низовете и примитивите се сериализират лесно: Java със сигурност има няHowъв вграден механизъм за това. Но Howво ще стане, ако нашият сериализуем клас има полета, които не са примитиви, а по-скоро препратки към други обекти? Например, нека създадем отделен клас TerritoryInfo , ResourceInfo и DiplomacyInfo , за да работим с нашия клас SavedGame .

public class TerritoryInfo {

   private String info;

   public TerritoryInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoryInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourceInfo {

   private String info;

   public ResourceInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourceInfo{" +
               "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 + '\'' +
               '}';
   }
}
И сега се изправяме пред въпроса: Трябва ли всички тези класове да могат да се сериализират , ако искаме да сериализираме нашия клас SavedGame ?

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoryInfo getTerritoryInfo() {
       return territoryInfo;
   }

   public void setTerritoryInfo(TerritoryInfo territoryInfo) {
       this.territoryInfo = territoryInfo;
   }

   public ResourceInfo getResourceInfo() {
       return resourceInfo;
   }

   public void setResourceInfo(ResourceInfo resourceInfo) {
       this.resourceInfo = resourceInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

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

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoryInfo=" + territoryInfo +
               ", resourceInfo=" + resourceInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Добре тогава! Нека го тестваме! Засега ще оставим всичко Howто е и ще се опитаме да сериализираме обект SavedGame :

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

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

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, diplomacyInfo);

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

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Резултат: Изключение в нишката "main" java.io.NotSerializableException: DiplomacyInfo Не работи! И така, ето отговора на нашия въпрос. Когато даден обект е сериализиран, всички обекти, посочени от неговите променливи на екземпляр, се сериализират. И ако тези обекти също препращат към други обекти, тогава те също са сериализирани. И така завинаги. Всички класове в тази верига трябва да са Serializable , в противен случай ще бъде невъзможно да бъдат сериализирани и ще бъде хвърлено изключение. Между другото, това може да създаде проблеми надолу по пътя. Например, Howво трябва да направим, ако не се нуждаем от част от клас по време на сериализация? Или Howво, ако получим нашия клас TerritoryInfo „чрез наследяване“ като част от библиотека? И да предположим още, че еи съответно не можем да го променим. Това би означавало, че не можем да добавим поле TerritoryInfo към нашия клас SavedGame , защото тогава целият клас SavedGame ще стане несериализиран! Това е проблем: / Сериализация и десериализация в Java - 2В Java този вид проблем се решава от преходната ключова дума. Ако добавите тази ключова дума към поле от вашия клас, това поле няма да бъде сериализирано. Нека опитаме да направим едно от полетата на нашия клас SavedGame преходно и след това ще сериализираме и възстановим един обект.

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoryInfo territoryInfo;
   private ResourceInfo resourceInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoryInfo territoryInfo, ResourceInfo resourceInfo, DiplomacyInfo diplomacyInfo) {
       this.territoryInfo = territoryInfo;
       this.resourceInfo = resourceInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

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

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoryInfo, resourceInfo, 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{territoryInfo=null, resourceInfo=ResourceInfo{info='Испания има 100 златни, Русия има 80 златни, Франция има 90 златни'}, diplomacyInfo=DiplomacyInfo{info='Франция е във война с Русия, Испания зае неутрална позиция'}} Това каза, че получихме отговор на въпроса Howва стойност ще бъде присвоена на преходно поле. Присвоява му се стойността по подразбиране. За обекти това е null . Можете да прочетете отлична глава по тази тема в книгата „Head-First Java“, обърнете внимание на нея :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION