Hoi! In de les van vandaag zullen we het hebben over serialisatie en deserialisatie in Java. We beginnen met een eenvoudig voorbeeld. Stel je voor dat je een ontwikkelaar van computerspellen bent. Als je bent opgegroeid in de jaren 90 en je de gameconsoles uit die tijd herinnert, weet je waarschijnlijk dat ze iets misten dat we tegenwoordig als vanzelfsprekend beschouwen: de mogelijkheid om games op te slaan en te laden :) Zo niet, stel je dat eens voor!Ik ben bang dat een spel zonder deze mogelijkheden vandaag gedoemd zou zijn! Hoe dan ook, wat betekent het om een spel te 'opslaan' en 'laden'? Nou, we begrijpen de alledaagse betekenis: we willen het spel voortzetten waar we gebleven waren. Om dit te doen, creëren we een bepaald 'controlepunt' dat we later gebruiken om het spel te laden. Maar wat betekent dat voor een programmeur in plaats van een casual gamer? Het antwoord is simpel: we slaan de status van ons programma op. Laten we zeggen dat je tegen Spanje speelt in een strategiespel. Je spel heeft status: welke gebieden iedereen heeft, hoeveel middelen iedereen heeft, welke allianties er zijn en met wie, wie in oorlog is, enzovoort. Deze informatie, de status van ons programma, moet op de een of andere manier worden opgeslagen om de gegevens te herstellen en het spel voort te zetten. Zoals het gebeurt, Serialisatie in Java is het proces waarbij de status van een object wordt opgeslagen als een reeks bytes. Deserialisatie in Java is het proces waarbij een object uit deze bytes wordt hersteld. Elk Java-object kan worden geconverteerd naar een bytereeks. Waarom hebben we dit nodig? We hebben herhaaldelijk gezegd dat programma's op zichzelf niet bestaan. Meestal communiceren ze met elkaar, wisselen ze gegevens uit, enz. Een byte-indeling is hiervoor handig en efficiënt. We kunnen bijvoorbeeld een object van ons SavedGame converterenklasse in een reeks bytes, breng deze bytes via het netwerk over naar een andere computer en converteer deze bytes vervolgens op de andere computer weer naar een Java-object! Het klinkt moeilijk, hè? Het lijkt alsof het moeilijk zou zijn om dit allemaal voor elkaar te krijgen: / Gelukkig is dat niet het geval! :) In Java is de Serializable- interface verantwoordelijk voor het serialisatieproces. Deze interface is uiterst eenvoudig: u hoeft geen enkele methode te implementeren om hem te gebruiken! Kijk hoe eenvoudig onze klasse voor het opslaan van spellen is:
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) +
'}';
}
}
Drie arrays zijn verantwoordelijk voor informatie over territoria, bronnen en diplomatie, en de Serializable-interface vertelt de Java-machine: ' alles is in orde als objecten van deze klasse kunnen worden geserialiseerd '. Een interface zonder een enkele interface ziet er raar uit :/ Waarom is het nodig? Het antwoord op die vraag is hierboven gegeven: het is alleen nodig om de benodigde informatie aan de Java-machine te verstrekken. In een vorige les hebben we het kort gehad over markeringsinterfaces. Dit zijn speciale informatieve interfaces die onze klassen eenvoudig markeren met aanvullende informatie die in de toekomst nuttig zal zijn voor de Java-machine. Ze hebben geen methoden die u moet implementeren. Hier is Serializable — zo'n interface. Hier is nog een belangrijk punt: waarom hebben we deprivate statische laatste lange serialVersionUID- variabele die we in de klas hebben gedefinieerd? Dit veld bevat de unieke versie-ID van de geserialiseerde klasse. Elke klasse die de Serializable -interface implementeert, heeft een versie-ID. Het wordt bepaald op basis van de inhoud van de klasse: velden en hun declaratievolgorde, en methoden en hun declaratievolgorde. En als we een veldtype en/of het aantal velden in onze klasse wijzigen, verandert de versie-ID onmiddellijk. De serialVersionUID wordt ook geschreven wanneer de klasse is geserialiseerd. Wanneer we proberen te deserialiseren, dwz een object uit een bytereeks herstellen, wordt de waarde van serialVersionUID vergeleken met de waarde van de serialVersionUIDvan de klas in ons programma. Als de waarden niet overeenkomen, wordt een java.io.InvalidClassException gegenereerd. We zullen hieronder een voorbeeld hiervan zien. Om dergelijke situaties te voorkomen, stellen we de versie-ID voor onze klasse eenvoudig handmatig in. In ons geval is het gewoon gelijk aan 1 (je kunt elk ander getal gebruiken dat je leuk vindt). Welnu, het is tijd om te proberen ons SavedGame- object te serialiseren en te kijken wat er gebeurt!
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();
}
}
Zoals u kunt zien, hebben we 2 streams gemaakt: FileOutputStream en ObjectOutputStream . De eerste weet hoe gegevens naar het bestand moeten worden geschreven en de tweede converteert objecten naar bytes. Je hebt al vergelijkbare geneste constructies gezien, bijvoorbeeld new BufferedReader(new InputStreamReader(...)) , in eerdere lessen, dus ze zouden je niet moeten afschrikken :) Door deze keten van twee streams te maken, voeren we beide taken uit: we converteren het SavedGame- object naar een reeks bytes en slaan het op in een bestand met de methode writeObject() . En trouwens, we hebben niet eens gekeken naar wat we kregen! Het is tijd om naar het dossier te kijken! *Let op: het is niet nodig om het bestand vooraf aan te maken. Als een bestand met de opgegeven naam niet bestaat, wordt het automatisch gemaakt* En dit is de inhoud: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ TerritoriumInfoq ~ xpur [Ljava.lang. Tekenreeks;¬ТVзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅР ёСЏ заняла позицию нейтралит етаuq ~ t "РЈ Р˜СЃРїРРЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 Р·РѕР» отаt !РЈ ФрРнции 90 Р·РѕР »РѕС‚Р°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 maanden geleden 8 maanden geleden Oh, oh :( Het lijkt erop dat ons programma niet werkte : ( Eigenlijk werkte het. Weet je nog dat we een reeks bytes naar het bestand stuurden, en niet alleen een object of tekst? Nou, dit is wat deze bytereeks ziet eruit als :) Het is ons opgeslagen spel! Als we ons oorspronkelijke object willen herstellen, dwz het spel starten en voortzetten waar we gebleven waren, dan hebben we het omgekeerde proces nodig: deserialisatie. Zo ziet het er voor ons uit:
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);
}
}
En hier is het resultaat! SavedGame{territoryInfo=[Spanje heeft 6 provincies, Rusland heeft 10 provincies, Frankrijk heeft 8 provincies], resourceInfo=[Spanje heeft 100 goud, Rusland heeft 80 goud, Frankrijk heeft 90 goud], diplomacyInfo=[Frankrijk is in oorlog met Rusland, Spanje heeft een neutraal standpunt ingenomen]} Uitstekend! We zijn erin geslaagd om eerst de status van onze game in een bestand op te slaan en deze vervolgens vanuit het bestand te herstellen. Laten we nu proberen hetzelfde te doen, maar we zullen de versie-ID uit onze SavedGame- klasse verwijderen. We gaan onze beide lessen niet herschrijven. Hun code zal hetzelfde zijn. We verwijderen alleen private static final long serialVersionUID uit de klasse SavedGame . Dit is ons object na serialisatie: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoriaInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранция РІРѕСЋРµ С‚ СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию РЅРµР№С‚СЂР°Р»РёС‚РµС ‚Р°uq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотР°t РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ РѕСЃСЃРёРё 10 РїСовинцийt &РЈ ФрРнции 8 РїСЂРѕРІР Maar kijk eens wat er gebeurt als we het proberen te deserialiseren: InvalidClassException: lokale klasse incompatibel: stream classdesc serialVersionUID = -196410440475012755, lokale klasse serialVersionUID = -6675950253085108747 Trouwens, we hebben iets belangrijks gemist. Het is duidelijk dat strings en primitieven gemakkelijk kunnen worden geserialiseerd: Java heeft hier zeker een ingebouwd mechanisme voor. Maar wat als onze serialiseerbare klasse velden heeft die geen primitieven zijn, maar eerder verwijzingen naar andere objecten? Laten we bijvoorbeeld een aparte klasse TerritoryInfo , ResourceInfo en DiplomacyInfo maken om met onze SavedGame- klasse te werken.
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 + '\'' +
'}';
}
}
En nu staan we voor een vraag: moeten al deze klassen serialiseerbaar zijn als we onze SavedGame- klasse willen serialiseren ?
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 +
'}';
}
}
Oke dan! Laten we het testen! Voor nu laten we alles zoals het is en proberen we een SavedGame- object te serialiseren:
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();
}
}
Resultaat: Uitzondering in thread "main" java.io.NotSerializableException: DiplomacyInfo Het werkte niet! Dus, hier is het antwoord op onze vraag. Wanneer een object is geserialiseerd, worden alle objecten waarnaar wordt verwezen door de instantievariabelen geserialiseerd. En als die objecten ook verwijzen naar andere objecten, dan zijn ze ook geserialiseerd. En door en door voor altijd. Alle klassen in deze keten moeten Serializable zijn , anders is het onmogelijk om ze te serialiseren en wordt er een uitzondering gegenereerd. Overigens kan dit later voor problemen zorgen. Wat moeten we bijvoorbeeld doen als we een deel van een klasse niet nodig hebben tijdens serialisatie? Of wat als we onze TerritoryInfo- klasse 'door overerving' als onderdeel van een bibliotheek zouden krijgen? En stel verder dat het 'en dienovereenkomstig kunnen we het niet veranderen. Dat zou betekenen dat we geen TerritoryInfo- veld aan onze SavedGame- klasse kunnen toevoegen, omdat de hele SavedGame- klasse dan niet meer serieel kan worden! Dat is een probleem: / In Java wordt dit soort problemen opgelost door het transiënte trefwoord. Als u dit trefwoord toevoegt aan een veld van uw klasse, wordt dat veld niet geserialiseerd. Laten we proberen een van de velden van onze klasse SavedGame transient te maken , en dan zullen we een enkel object serialiseren en herstellen.
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();
}
}
En hier is het resultaat: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{info='Spanje heeft 100 goud, Rusland heeft 80 goud, Frankrijk heeft 90 goud'}, diplomacyInfo=DiplomacyInfo{info='Frankrijk is in oorlog met Rusland, Spanje heeft een neutrale positie ingenomen'}} Dat gezegd hebbende, hebben we een antwoord gekregen op de vraag welke waarde zal worden toegekend aan een tijdelijk veld. Het krijgt de standaardwaarde toegewezen. Voor objecten is dit null . Je kunt een uitstekend hoofdstuk over dit onderwerp lezen in het boek 'Head-First Java', let erop :)
GO TO FULL VERSION