Bună! În lecția de astăzi, vom vorbi despre serializare și deserializare în Java. Vom începe cu un exemplu simplu. Imaginează-ți că ești un dezvoltator de jocuri pe computer. Dacă ai crescut în anii 90 și îți amintești consolele de jocuri din acea epocă, probabil știi că le lipsea ceva ce luăm de la sine înțeles astăzi — capacitatea de a salva și încărca jocuri :) Dacă nu, imaginează-ți asta!Mi-e teamă că un joc fără aceste abilități astăzi ar fi condamnat! Oricum, ce înseamnă să „salvați” și să „încărcați” un joc? Ei bine, înțelegem sensul de zi cu zi: vrem să continuăm jocul din locul de unde am plecat. Pentru a face acest lucru, creăm un anumit „punct de verificare” pe care îl folosim mai târziu pentru a încărca jocul. Dar ce înseamnă asta pentru un programator mai degrabă decât pentru un jucător ocazional? Răspunsul este simplu: salvăm starea programului nostru. Să presupunem că joci Spania într-un joc de strategie. Jocul tău are o stare: ce teritorii are fiecare, câte resurse are fiecare, ce alianțe există și cu cine, cine este în război și așa mai departe. Aceste informații, starea programului nostru, trebuie să fie salvate cumva pentru a restabili datele și a continua jocul. După cum se întâmplă, Serializarea în Java este procesul de salvare a stării unui obiect ca o secvență de octeți. Deserializarea în Java este procesul de restaurare a unui obiect din acești octeți. Orice obiect Java poate fi convertit într-o secvență de octeți. De ce avem nevoie de asta? Am spus în repetate rânduri că programele nu există de la sine. Cel mai adesea, ei interacționează unul cu celălalt, schimbă date etc. Un format de octeți este convenabil și eficient pentru aceasta. De exemplu, putem converti un obiect din SavedGameclasa într-o secvență de octeți, transferați acești octeți prin rețea pe un alt computer și apoi pe celălalt computer convertiți acești octeți înapoi într-un obiect Java! Sună dificil, nu? Se pare că ar fi dificil să se întâmple toate astea: / Din fericire, nu este cazul! :) În Java, interfața Serializable este responsabilă pentru procesul de serializare. Această interfață este extrem de simplă: nu trebuie să implementați o singură metodă pentru a o folosi! Uită-te la cât de simplă este clasa noastră pentru salvarea jocurilor:
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) +
'}';
}
}
Trei matrice sunt responsabile pentru informații despre teritorii, resurse și diplomație, iar interfața Serializable spune mașinii Java: „ totul este în regulă dacă obiectele din această clasă pot fi serializate ”. O interfață fără o singură interfață arată ciudat :/ De ce este necesară? Răspunsul la această întrebare este dat mai sus: este necesar doar pentru a furniza informațiile necesare mașinii Java. Într-o lecție trecută, am menționat pe scurt interfețele de marcare. Acestea sunt interfețe informaționale speciale care pur și simplu marchează clasele noastre cu informații suplimentare care vor fi utile mașinii Java în viitor. Nu au nicio metodă pe care trebuie să le implementați. Iată Serializable - o astfel de interfață. Iată un alt punct important: De ce avem nevoie deprivat static final lung serialVersionUID variabilă pe care am definit-o în clasă? Acest câmp conține identificatorul unic de versiune al clasei serializate. Fiecare clasă care implementează interfața Serializable are un identificator de versiune. Se determină pe baza conținutului clasei — câmpuri și ordinea lor de declarare, și metode și ordinea lor de declarare. Și dacă schimbăm un tip de câmp și/sau numărul de câmpuri din clasa noastră, identificatorul versiunii se schimbă instantaneu. SerialVersionUID este scris și atunci când clasa este serializată. Când încercăm să deserializăm, adică să restaurăm un obiect dintr-o secvență de octeți, valoarea serialVersionUID este comparată cu valoarea serialVersionUIDa clasei din programul nostru. Dacă valorile nu se potrivesc, atunci va fi lansată o excepție java.io.InvalidClassException. Vom vedea un exemplu în acest sens mai jos. Pentru a evita astfel de situații, pur și simplu setăm manual identificatorul de versiune pentru clasa noastră. În cazul nostru, va fi pur și simplu egal cu 1 (puteți folosi orice alt număr doriți). Ei bine, este timpul să încercăm să serializam obiectul nostru SavedGame și să vedem ce se întâmplă!
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();
}
}
După cum puteți vedea, am creat 2 fluxuri: FileOutputStream și ObjectOutputStream . Primul știe cum să scrie date în fișier, iar al doilea convertește obiectele în octeți. Ați văzut deja constructe imbricate similare, de exemplu, nou BufferedReader(new InputStreamReader(...)) , în lecțiile anterioare, așa că nu ar trebui să vă sperie :) Prin crearea acestui lanț de două fluxuri, îndeplinim ambele sarcini: convertim obiectul SavedGame într-o secvență de octeți și îl salvăm într-un fișier folosind metoda writeObject() . Și, apropo, nici nu ne-am uitat la ce am primit! E timpul să te uiți la dosar! *Notă: nu este necesar să creați fișierul în avans. Dacă nu există un fișier cu numele specificat, atunci acesta va fi creat automat* Și iată conținutul acestuia: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang. String;¬ТVзй{G xp t pФранция воюет СЃ Р РѕСЃСЃРёРСЃСЃРёРция воюет СЃ Р РѕСЃСЃРёРСЃСЃРёРСРРёСЏ СЏ заняла позицию нейтраит етаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Роспании 100 золотаt РЈ Роспании 80 оспании ‚Р°t !РЈ Франции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt РёР№t %СРСРЕРёРё 6 провинцийt &РЈ Франции 8 РїСЂРѕРёРивиции Oh, oh :( Se pare că programul nostru nu a funcționat : ( De fapt, a funcționat. Vă amintiți că am trimis o secvență de octeți, și nu doar un obiect sau text, la fișier? Ei bine, asta este această secvență de octeți arată ca :) Este jocul nostru salvat! Dacă vrem să ne restabilim obiectul original, adică să începem și să continuăm jocul de unde l-am lăsat, atunci avem nevoie de procesul invers: deserializarea. Iată cum arată pentru noi:
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);
}
}
Și iată rezultatul! SavedGame{territoryInfo=[Spania are 6 provincii, Rusia are 10 provincii, Franța are 8 provincii], resourceInfo=[Spania are 100 de aur, Rusia are 80 de aur, Franța are 90 de aur], diplomacyInfo=[Franța este în război cu Rusia, Spania a luat o poziție neutră]} Excelent! Am reușit să salvăm mai întâi starea jocului nostru într-un fișier, apoi să-l restaurăm din fișier. Acum să încercăm să facem același lucru, dar vom elimina identificatorul versiunii din clasa noastră SavedGame . Nu ne vom rescrie ambele clase. Codul lor va fi același. Vom elimina doar serialVersionUID final static privat din clasa SavedGame . Iată obiectul nostru după serializare: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ teritoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранСРЕнцнщ С‚ СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию РЅРРёСЏ заняла позицию РЅРРёСЏ РЅРйѰѰРѰицию ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ РФранции 90 РѕРё ~ 90 РС·Рѕ Ј Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франровинцийt &РЈ ФранроРСинцийt ёРЅС†РёР№ Dar uită-te la ce se întâmplă când încercăm să-l deserializăm: InvalidClassException: clasă locală incompatibilă: stream classdesc serialVersionUID = -196410440475012755, clasa locală serialVersionUID = -6675950253085108747 Apropo, am omis ceva important. Evident, șirurile și primitivele sunt serializate cu ușurință: Java are cu siguranță un mecanism încorporat pentru asta. Dar ce se întâmplă dacă clasa noastră serializabilă are câmpuri care nu sunt primitive, ci mai degrabă referințe la alte obiecte? De exemplu, să creăm clase separate TerritoryInfo , ResourceInfo și DiplomacyInfo pentru a lucra cu clasa noastră 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 + '\'' +
'}';
}
}
Și acum ne confruntăm cu o întrebare: toate aceste clase trebuie să fie serializabile dacă vrem să ne serializam clasa 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 +
'}';
}
}
În regulă atunci! Să-l testăm! Deocamdată, vom lăsa totul așa cum este și vom încerca să serializeze un obiect 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();
}
}
Rezultat: Excepție în firul „principal” java.io.NotSerializableException: DiplomacyInfo Nu a funcționat! Deci, iată răspunsul la întrebarea noastră. Când un obiect este serializat, toate obiectele la care se face referire de variabilele sale de instanță sunt serializate. Și dacă acele obiecte fac referire și la alte obiecte, atunci ele sunt, de asemenea, serializate. Și mai departe pentru totdeauna. Toate clasele din acest lanț trebuie să fie Serializable , altfel va fi imposibil să le serializeze și se va arunca o excepție. Apropo, acest lucru poate crea probleme pe viitor. De exemplu, ce ar trebui să facem dacă nu avem nevoie de o parte dintr-o clasă în timpul serializării? Sau ce se întâmplă dacă am obține clasa noastră TerritoryInfo „prin moștenire” ca parte a unei biblioteci? Și să presupunem în continuare că esteși, în consecință, nu o putem schimba. Asta ar însemna că nu putem adăuga un câmp TerritoryInfo la clasa noastră SavedGame , pentru că atunci întreaga clasă SavedGame ar deveni neserialabilă! Aceasta este o problemă: / În Java, acest tip de problemă este rezolvată de cuvântul cheie tranzitoriu . Dacă adăugați acest cuvânt cheie la un câmp al clasei dvs., atunci acel câmp nu va fi serializat. Să încercăm să facem unul dintre câmpurile clasei noastre SavedGame tranzitoriu și apoi vom serializa și restaura un singur obiect.
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();
}
}
Și iată rezultatul: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spania are 100 de aur, Rusia are 80 de aur, Franța are 90 de aur'}, diplomacyInfo=DiplomacyInfo{info='Franța este în război cu Rusia, Spania a luat o poziție neutră'}} Acestea fiind spuse, am primit un răspuns la întrebarea ce valoare va fi atribuită unui câmp tranzitoriu . I se atribuie valoarea implicită. Pentru obiecte, acesta este null . Puteți citi un capitol excelent pe acest subiect în cartea „Head-First Java”, Fiți atenți la el :)
GO TO FULL VERSION