Szia! A mai leckében a Java szerializálásáról és deszerializálásáról fogunk beszélni. Egy egyszerű példával kezdjük. Képzeld el, hogy számítógépes játékfejlesztő vagy. Ha a 90-es években nőtt fel, és emlékszik az akkori játékkonzolokra, akkor valószínűleg tudja, hogy hiányzott belőlük valami, amit ma magától értetődőnek tartunk – a játékok mentésének és betöltésének képessége :) Ha nem, képzelje el!Sorozatosítás és deszerializálás Java nyelven - 1Félek, hogy egy mai játék ezek nélkül a képességek nélkül pusztulásra lenne ítélve! Egyáltalán, mit jelent egy játék "mentése" és "betöltése"? Nos, értjük a mindennapi jelentést: onnan akarjuk folytatni a játékot, ahol abbahagytuk. Ehhez létrehozunk egy bizonyos „ellenőrző pontot”, amelyet később a játék betöltésére használunk. De mit jelent ez egy programozónak, nem pedig egy hétköznapi játékosnak? A válasz egyszerű: elmentjük programunk állapotát. Tegyük fel, hogy Spanyolországgal játszol egy stratégiai játékban. A játékodnak van állapota: melyik területtel rendelkezik mindenkinek, mennyi erőforrása van mindenkinek, milyen szövetségek léteznek és kivel, ki áll háborúban, és így tovább. Ezt az információt, a programunk állapotát valahogy el kell menteni az adatok visszaállításához és a játék folytatásához. Ahogy megtörténik, A Java szerializálása egy objektum állapotának bájtok sorozatakénti mentésének folyamata. A deszerializáció a Java-ban egy objektum visszaállításának folyamata ezekből a bájtokból. Bármely Java objektum konvertálható bájtsorozattá. Miért van erre szükségünk? Többször mondtuk, hogy a programok önmagukban nem léteznek. Leggyakrabban kölcsönhatásba lépnek egymással, adatokat cserélnek stb. A bájtformátum kényelmes és hatékony erre. Például konvertálhatjuk a SavedGame egy objektumátosztályt bájtok sorozatává, vigye át ezeket a bájtokat a hálózaton keresztül egy másik számítógépre, majd a másik számítógépen alakítsa vissza ezeket a bájtokat Java objektummá! Nehezen hangzik, mi? Úgy tűnik, nehéz lenne mindezt megvalósítani: / Szerencsére ez nem így van! :) Java-ban a Serializable felület felelős a szerializálási folyamatért. Ez a felület rendkívül egyszerű: egyetlen módszert sem kell megvalósítania a használatához! Nézze meg, milyen egyszerű a játékmentési osztályunk:

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) +
               '}';
   }
}
Három tömb felel a területekre, erőforrásokra és a diplomáciára vonatkozó információkért, a Serializable felület pedig azt mondja a Java gépnek: ' minden rendben van, ha az ebbe az osztályba tartozó objektumok szerializálhatók '. Furcsán néz ki egy interfész egyetlen interfész nélkül :/ Miért van rá szükség? Erre a kérdésre fent adjuk meg a választ: csak a szükséges információk megadásához szükséges a Java gépnek. Egy korábbi leckében röviden megemlítettük a marker interfészt. Ezek speciális információs felületek, amelyek egyszerűen megjelölik osztályainkat további információkkal, amelyek a jövőben hasznosak lesznek a Java gép számára. Nincsenek módszereik, amelyeket végre kell hajtani. Íme a Serializable – egy ilyen interfész. Íme egy másik fontos pont: Miért van szükségünk aprivát statikus végső hosszú serialVersionUID változó, amelyet az osztályban definiáltunk? Ez a mező tartalmazza a sorosított osztály egyedi verzióazonosítóját. Minden osztály, amely megvalósítja a Serializable interfészt, rendelkezik verzióazonosítóval. Meghatározása az osztály tartalma — mezők és deklarációs sorrendjük, valamint a metódusok és azok deklarációs sorrendje alapján történik. Ha pedig megváltoztatunk egy mezőtípust és/vagy a mezők számát az osztályunkban, akkor a verzióazonosító azonnal megváltozik. A serialVersionUID az osztály szerializálásakor is kiírásra kerül. Amikor megpróbálunk deszerializálni, azaz visszaállítani egy objektumot egy bájtsorozatból, a serialVersionUID értéke összehasonlításra kerül a serialVersionUID értékével.osztályból a programunkban. Ha az értékek nem egyeznek, akkor egy java.io.InvalidClassException jelenik meg. Alább láthatunk erre egy példát. Az ilyen helyzetek elkerülése érdekében egyszerűen beállítjuk az osztályunk verzióazonosítóját manuálisan. A mi esetünkben ez egyszerűen egyenlő 1-gyel (bármilyen más számot használhat). Nos, itt az ideje, hogy megpróbáljuk szerializálni a SavedGame objektumunkat, és meglátjuk, mi történik!

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();
   }
}
Amint látja, 2 adatfolyamot hoztunk létre: FileOutputStream és ObjectOutputStream . Az első tudja, hogyan kell adatokat írni a fájlba, a második pedig az objektumokat bájtokká alakítja. Láttál már hasonló beágyazott konstrukciókat, például az új BufferedReader(new InputStreamReader(...)) , az előző leckékben, így nem szabad megijeszteni :) A két folyamból álló lánc létrehozásával mindkét feladatot végrehajtjuk: a SavedGame objektumot bájtok sorozatává alakítjuk és a writeObject() metódussal fájlba mentjük. És mellesleg meg sem néztük, mit kaptunk! Ideje megnézni a fájlt! *Megjegyzés: nem szükséges előre létrehozni a fájlt. Ha a megadott nevű fájl nem létezik, akkor automatikusan létrejön* És itt van a tartalma: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ terrierInfoq ~ xpur [Ljava.lang. Karakterlánc;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°СОРЏ ѕР·РёС†РёСЋ нейтралит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ РѕСЃСЃРёРё 80 Р° РКРЕСѕ! †РёРё 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРЕСЃСЃРёРёРцРхѕССС1 ёР№t &РЈ Франции 8 провинций Ó, jaj :( Úgy tűnik, a programunk nem működött : ( Valójában működött. Emlékszel, hogy bájtsorozatot küldtünk a fájlba, nem csak objektumot vagy szöveget? Nos, ez a bájtsorozat úgy néz ki :) Ez a mi elmentett játékunk!Ha vissza akarjuk állítani az eredeti objektumunkat, azaz ott kezdjük és folytatjuk a játékot, ahol abbahagytuk, akkor fordított folyamatra van szükségünk: deserializáció. Nálunk így néz ki:

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);
   }
}
És itt az eredmény! SavedGame{territoryInfo=[Spanyolországnak 6 tartománya van, Oroszországnak 10 tartománya, Franciaországnak 8 tartománya], resourceInfo=[Spanyolországnak 100 aranya, Oroszországnak 80 aranya, Franciaországnak 90 aranya van], diplomacyInfo=[Franciaország háborúban áll Oroszországgal, Spanyolország semleges álláspontot képvisel]} Kiváló! Sikerült először fájlba menteni a játékunk állapotát, majd visszaállítani a fájlból. Most próbáljuk meg ugyanezt, de eltávolítjuk a verzióazonosítót a SavedGame osztályunkból. Nem írjuk át mindkét osztályunkat. A kódjuk ugyanaz lesz. Csak eltávolítjuk a privát statikus végleges hosszú serialVersionUID-t a SavedGame osztályból. Íme az objektum a szerializálás után: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territorioInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранСОСІРКСяСБРБЕСЏСБ оссией, НОВЕНЕНЕНЕНЕН СОВЕНЕНЕНЕНЕ ёРё 100 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 олотаuq ~ отаuq ~ отаuq 6 провинцийt %РЈ РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 проиЪции 8 РїСЂРѕРёР℆des, hogy mi történjen ize it: InvalidClassException: helyi osztály nem kompatibilis: stream classdesc serialVersionUID = -196410440475012755, helyi osztály serialVersionUID = -6675950253085108747 Egyébként valami fontosat kihagytunk. Nyilvánvaló, hogy a karakterláncok és a primitívek könnyen szerializálhatók: a Java bizonyosan beépített mechanizmussal rendelkezik erre. De mi van akkor, ha a szerializálható osztályunkban olyan mezők vannak, amelyek nem primitívek, hanem hivatkozások más objektumokra? Például hozzunk létre külön TerritoryInfo , ResourceInfo és DiplomacyInfo osztályt a SavedGame osztályunkkal való együttműködéshez .

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 + '\'' +
               '}';
   }
}
És most egy kérdéssel kell szembenéznünk: ezeknek az összes osztálynak szerializálhatónak kell lennie , ha szerializálni akarjuk a SavedGame osztályunkat?

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 +
               '}';
   }
}
Akkor rendben! Teszteljük! Egyelőre mindent úgy hagyunk, ahogy van, és megpróbálunk szerializálni egy SavedGame objektumot:

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();
   }
}
Eredmény: Kivétel a "main" szálban java.io.NotSerializableException: DiplomacyInfo Nem működött! Tehát itt a válasz kérdésünkre. Amikor egy objektumot szerializálnak, akkor a példányváltozói által hivatkozott összes objektum sorba kerül. És ha ezek az objektumok más objektumokra is hivatkoznak, akkor szintén szerializálódnak. És tovább és örökké. Ebben a láncban minden osztálynak Serializable -nak kell lennie , különben lehetetlenné válik a szerializálásuk, és kivételt kell tenni. Ez egyébként problémákat okozhat az úton. Például mit tegyünk, ha a szerializálás során nincs szükségünk egy osztály egy részére? Vagy mi lenne, ha a TerritoryInfo osztályunkat „öröklés útján” kapnánk egy könyvtár részeként? És tegyük fel továbbá, hogy nemés ennek megfelelően nem tudjuk megváltoztatni. Ez azt jelentené, hogy nem adhatunk TerritoryInfo mezőt a SavedGame osztályunkhoz , mert akkor az egész SavedGame osztály sorozatozhatatlanná válna! Ez egy probléma: / Java-ban az ilyen jellegű problémákat a tranziensSorozatosítás és deszerializálás Java nyelven - 2 kulcsszó oldja meg . Ha hozzáadja ezt a kulcsszót az osztály egyik mezőjéhez, akkor a mező nem lesz sorosítva. Próbáljuk meg a SavedGame osztály egyik mezőjét tranzienssé tenni , majd sorosítunk és visszaállítunk egyetlen objektumot.

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


   }
}
És íme az eredmény: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spanyolországnak 100 aranya, Oroszországnak 80 aranya, Franciaországnak 90 aranya van'}, diplomacyInfo=DiplomacyInfo{info='Franciaország háborúban áll Oroszországgal, Spanyolországgal semleges pozíciót vett fel'}} Ez azt jelenti, hogy választ kaptunk arra a kérdésre, hogy milyen értéket rendelünk egy tranziens mezőhöz. Hozzá van rendelve az alapértelmezett érték. Objektumok esetén ez null . Erről a témáról egy kiváló fejezetet olvashatsz a 'Head-First Java' könyvben, figyelj rá :)