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!Fé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 tranziens 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á :)
GO TO FULL VERSION