Hoi! In de les van vandaag hebben we het over serialisatie en deserialisatie in Java. We beginnen met een eenvoudig voorbeeld. Laten we zeggen dat je een computerspel hebt gemaakt. 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 game zonder deze vaardigheden vandaag gedoemd zou zijn! Wat betekent het eigenlijk om een spel te "opslaan" en "laden"? Nou, we begrijpen de gewone betekenis: we willen het spel voortzetten vanaf de plek waar we waren gebleven. Om dit te doen, creëren we een soort "controlepunt", dat we vervolgens gebruiken om het spel te laden. Maar wat betekent dit voor een programmeur in plaats van een casual gamer? Het antwoord is simpel: wij'. Laten we zeggen dat je als Spanje speelt in Strategium. Je spel heeft een staat: wie bezit welke gebieden, wie heeft hoeveel grondstoffen, wie heeft een alliantie met wie, wie voert oorlog met wie, enzovoort. We moeten deze informatie, de status van ons programma, op de een of andere manier opslaan om deze in de toekomst te herstellen en het spel voort te zetten. Want dit is precies waar serialisatie en deserealisatie voor zijn. Serialisatie is het proces waarbij de status van een object wordt opgeslagen in een reeks bytes. Deserialisatieis het proces van het herstellen van een object uit deze bytes. Elk Java-object kan worden geconverteerd naar een bytereeks. Waarom zouden we dat nodig hebben? We hebben meer dan eens gezegd dat programma's niet op zichzelf staan. Meestal communiceren ze met andere programma's, wisselen ze gegevens uit, enz. En een bytereeks is een handig en efficiënt formaat. We kunnen ons object bijvoorbeeld omzetten
In Java worden dit soort problemen opgelost met behulp van het

SavedGame
in een reeks bytes, deze bytes via het netwerk naar een andere computer sturen en deze bytes op de tweede computer weer omzetten in een Java-object! Klinkt moeilijk, toch? En het implementeren van dit proces lijkt lastig :/ Gelukkig is dit niet zo! :) Op Java is deSerializable
interface is verantwoordelijk voor het serialisatieproces. Deze interface is uiterst eenvoudig: u hoeft geen enkele methode te implementeren om hem te gebruiken! Zo eenvoudig ziet onze spelbesparende les eruit:
import java.io.Serializable;
import java.util.Arrays;
public class SavedGame implements Serializable {
private static final long serialVersionUID = 1L;
private String[] territoriesInfo;
private String[] resourcesInfo;
private String[] diplomacyInfo;
public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
this.territoriesInfo = territoriesInfo;
this.resourcesInfo = resourcesInfo;
this.diplomacyInfo = diplomacyInfo;
}
public String[] getTerritoriesInfo() {
return territoriesInfo;
}
public void setTerritoriesInfo(String[] territoriesInfo) {
this.territoriesInfo = territoriesInfo;
}
public String[] getResourcesInfo() {
return resourcesInfo;
}
public void setResourcesInfo(String[] resourcesInfo) {
this.resourcesInfo = resourcesInfo;
}
public String[] getDiplomacyInfo() {
return diplomacyInfo;
}
public void setDiplomacyInfo(String[] diplomacyInfo) {
this.diplomacyInfo = diplomacyInfo;
}
@Override
public String toString() {
return "SavedGame{" +
"territoriesInfo=" + Arrays.toString(territoriesInfo) +
", resourcesInfo=" + Arrays.toString(resourcesInfo) +
", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
'}';
}
}
De drie arrays zijn verantwoordelijk voor informatie over territoria, middelen en diplomatie. De Serializable-interface vertelt de virtuele Java-machine: " Alles is in orde - indien nodig kunnen objecten van deze klasse worden geserialiseerd ". Een interface zonder een enkele interface ziet er raar uit :/ Waarom is het nodig? Het antwoord op deze vraag is hierboven te zien: het dient alleen om de noodzakelijke informatie aan de Java virtual machine te verstrekken. In een van onze vorige lessen hebben we het kort over marker-interfaces gehad . 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.Serializable
is een van die interfaces. Nog een belangrijk punt: waarom hebben we de private static final long serialVersionUID
variabele nodig die we in de klas hebben gedefinieerd? Waarom is het nodig? Dit veld bevat een unieke identificatie voor de versie van de geserialiseerde klasse . Elke klasse die de Serializable
interface implementeert, heeft een version
identifier. Het wordt berekend op basis van de inhoud van de klasse: de velden, de volgorde waarin ze zijn gedeclareerd, methoden, enz. Als we het type veld en/of het aantal velden in onze klasse wijzigen, verandert de versie-ID onmiddellijk . serialVersionUID
wordt ook geschreven wanneer de klasse is geserialiseerd. Wanneer we proberen een object te deserialiseren, dat wil zeggen een object uit een reeks bytes herstellen, serialVersionUID
wordt het bijbehorende object vergeleken met de waarde vanserialVersionUID
voor de klas in ons programma. Als de waarden niet overeenkomen, wordt een java.io. InvalidClassException wordt gegenereerd. Een voorbeeld daarvan zien we hieronder. Om dit te voorkomen, stellen we de versie-ID eenvoudig handmatig in onze klasse in. In ons geval is het gewoon gelijk aan 1 (maar u kunt elk ander getal vervangen dat u wilt). Welnu, het is tijd om te proberen ons object te serialiseren SavedGame
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[] resourcesInfo = {"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, resourcesInfo, 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 free resources
objectOutputStream.close();
}
}
Zoals je kunt zien, hebben we 2 streams gemaakt: FileOutputStream
en ObjectOutputStream
. De eerste kan gegevens naar een bestand schrijven en de tweede converteert objecten naar bytes. Je hebt al vergelijkbare "geneste" constructies gezien, bijvoorbeeld , new BufferedReader(new InputStreamReader(...))
in eerdere lessen, dus die zouden je niet moeten afschrikken :) Door zo'n "ketting" van twee stromen te maken, voeren we beide taken uit: we zetten het SavedGame
object om in een set van bytes en sla het op in een bestand met behulp van de writeObject()
methode. En trouwens, we hebben niet eens gekeken naar wat we kregen! Het is tijd om naar het dossier te kijken! *Let op: u hoeft het bestand niet vooraf aan te maken. Als er geen bestand met die naam bestaat, wordt het automatisch aangemaakt* En hier is de inhoud!
¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранция воюет СЃ Россией, Рспания заняла позицию нейтралитетаuq ~ t "РЈ Рспании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Рспании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Uh-oh :( Het lijkt erop dat ons programma niet werkte :( Het werkte zelfs. Herinner je je dat we een set bytes stuurden, niet alleen een object of tekst, naar het bestand? Nou, dit is wat dat set van bytes ziet eruit als :) Dit 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 zal het eruit zien in onze geval:
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{territoriesInfo=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
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 zonder de versie-ID voor onze SavedGame
klasse. We gaan onze beide lessen niet herschrijven. Hun code blijft hetzelfde, maar we verwijderen ze private static final long serialVersionUID
uit de SavedGame
klas. Dit is ons object na serialisatie:
¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ 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: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Dit is de uitzondering die we hierboven noemden. Trouwens, we hebben iets belangrijks gemist. Het is logisch dat strings en primitieven gemakkelijk kunnen worden geserialiseerd: Java heeft waarschijnlijk een of ander ingebouwd mechanisme om dit te doen. Maar wat als onze serializable
klasse velden heeft die geen primitieven zijn, maar verwijzingen naar andere objecten? Laten we bijvoorbeeld afzonderlijke TerritoriesInfo
, ResourcesInfo
en DiplomacyInfo
klassen maken om met onze klas te werken SavedGame
.
public class TerritoriesInfo {
private String info;
public TerritoriesInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "TerritoriesInfo{" +
"info='" + info + '\'' +
'}';
}
}
public class ResourcesInfo {
private String info;
public ResourcesInfo(String info) {
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "ResourcesInfo{" +
"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 rijst een vraag: moeten al deze klassen zijn als we onze gewijzigde klasse Serializable
willen serialiseren ?SavedGame
import java.io.Serializable;
import java.util.Arrays;
public class SavedGame implements Serializable {
private TerritoriesInfo territoriesInfo;
private ResourcesInfo resourcesInfo;
private DiplomacyInfo diplomacyInfo;
public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
this.territoriesInfo = territoriesInfo;
this.resourcesInfo = resourcesInfo;
this.diplomacyInfo = diplomacyInfo;
}
public TerritoriesInfo getTerritoriesInfo() {
return territoriesInfo;
}
public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
this.territoriesInfo = territoriesInfo;
}
public ResourcesInfo getResourcesInfo() {
return resourcesInfo;
}
public void setResourcesInfo(ResourcesInfo resourcesInfo) {
this.resourcesInfo = resourcesInfo;
}
public DiplomacyInfo getDiplomacyInfo() {
return diplomacyInfo;
}
public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
this.diplomacyInfo = diplomacyInfo;
}
@Override
public String toString() {
return "SavedGame{" +
"territoriesInfo=" + territoriesInfo +
", resourcesInfo=" + resourcesInfo +
", diplomacyInfo=" + diplomacyInfo +
'}';
}
}
Nou, laten we het testen! Laten we alles laten zoals het is en proberen 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(territoriesInfo, resourcesInfo, diplomacyInfo);
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(savedGame);
objectOutputStream.close();
}
}
Resultaat:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Het werkte niet! Dat is eigenlijk 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 zo tot in het oneindige. Alle klassen in deze keten moeten zijnSerializable
, anders is het onmogelijk om ze te serialiseren en wordt er een uitzondering gegenereerd. Overigens kan dit later voor problemen zorgen. Wat moeten we doen als we bijvoorbeeld geen deel van een klasse nodig hebben bij het serialiseren? Of, bijvoorbeeld, wat als de TerritoryInfo
klas naar ons toe kwam als onderdeel van een bibliotheek van derden. En stel verder dat het niet zo is Serializable
en dat we het dus niet kunnen veranderen. Het blijkt dat we geen TerritoryInfo
veld kunnen toevoegen aan onzeSavedGame
klasse, omdat dit de hele SavedGame
klasse niet-serialiseerbaar zou maken! Dat is een probleem :/ 
transient
trefwoord. Als u dit trefwoord toevoegt aan een veld van uw klasse, wordt dat veld niet geserialiseerd. Laten we proberen een van de SavedGame
instantievelden van de klasse van voorbijgaande aard te maken. Vervolgens zullen we één object serialiseren en herstellen.
import java.io.Serializable;
public class SavedGame implements Serializable {
private transient TerritoriesInfo territoriesInfo;
private ResourcesInfo resourcesInfo;
private DiplomacyInfo diplomacyInfo;
public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
this.territoriesInfo = territoriesInfo;
this.resourcesInfo = resourcesInfo;
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(territoriesInfo, resourcesInfo, 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{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
Bovendien kregen we een antwoord op onze vraag welke waarde aan een transient
veld wordt toegekend. Het krijgt de standaardwaarde toegewezen. Voor objecten is dit null
. U kunt dit uitstekende artikel over serialisatie lezen als u een paar minuten over heeft. Het vermeldt ook de Externalizable
interface, waar we het in de volgende les over zullen hebben. Daarnaast bevat het boek "Head-First Java" een hoofdstuk over dit onderwerp. Geef het wat aandacht :)
GO TO FULL VERSION