JavaRush /Java Blog /Random EN /Serialization and Deserialization in Java

Serialization and Deserialization in Java

Published in the Random EN group
Hello! In today's lecture we will talk about serialization and deserialization in Java. Let's start with a simple example. Let's say you are the creator of a computer game. If you grew up in the 90s and remember the game consoles of those times, you probably know that they lacked something obvious today - saving and loading games :) If not... imagine! I'm afraid that today a game without such an option will be doomed to failure! And, actually, what is “saving” and “loading” a game? Well, in the usual sense, we understand what it is: we want to continue the game from where we left off last time. To do this, we create a kind of “checkpoint”, which we then use to load the game. But what does this mean, not in the everyday sense, but in the “programmer” sense? The answer is simple: we save the state of our program. Let's say you're playing a strategy game for Spain. Your game has a state: who owns what territories, who has how many resources, who is in alliance with whom, and who, on the contrary, is at war, and so on. This information, the state of our program, must be saved somehow in order to later restore the data and continue the game. This is precisely what the mechanisms of serialization and deserialization are used for . Serialization is the process of storing the state of an object into a sequence of bytes. Deserialization is the process of reconstructing an object from these bytes. Any Java object is converted to a sequence of bytes. What is it for? We have said more than once that programs do not exist on their own. Most often they interact with each other, exchange data, etc. And the byte format is convenient and efficient for this. We can, for example, turn our class object SavedGame(a saved game) into a sequence of bytes, transfer those bytes over the network to another computer, and on the second computer turn those bytes back into a Java object! It's difficult to hear, right? Apparently, organizing this process will not be easy :/ Fortunately, no! :) In Java, the Serializable interface is responsible for serialization processes . This interface is extremely simple: you don't need to implement a single method to use it! This is how our save game class will look like:
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) +
               '}';
   }
}
Three data sets are responsible for information about territories, economics and diplomacy, and the Serializable interface tells the Java machine: “ everything is ok, if anything, objects of this class can be serialized .” An interface that doesn't have any methods looks weird :/ Why is it needed? The answer to this question is above: only to provide the necessary information to the Java machine. In one of the previous lectures, we briefly mentioned marker interfaces. These are special informative interfaces that simply mark our classes with additional information that will be useful to the Java machine in the future. They don’t have any methods that need to be implemented. So, Serializable is one of such interfaces. Another important point: the variable private static final long serialVersionUIDwe defined in the class. Why is it needed? This field contains the unique version identifier of the serialized class . Any class that implements the Serializable interface has a version identifier. It is calculated based on the contents of the class - fields, declaration order, methods. And if we change the field type and/or number of fields in our class, the version identifier will instantly change. serialVersionUID is also written when the class is serialized. When we try to deserialize, that is, restore an object from a set of bytes, the value serialVersionUIDis compared with the value serialVersionUIDof the class in our program. If the values ​​do not match, a java.io.InvalidClassException will be thrown. We will see an example of this below. To avoid such situations, we simply manually set this version ID for our class. In our case, it will simply be equal to 1 (you can substitute any other number you like). Well, it's time to try serializing our object SavedGameand see what happens!
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 position of neutrality"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);

       //create 2 threads 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 game to file
       objectOutputStream.writeObject(savedGame);

       // close the stream and release resources
       objectOutputStream.close();
   }
}
As you can see, we have created 2 threads - FileOutputStreamand ObjectOutputStream. The first of them can write data to a file, and the second can convert objects into bytes. You have already seen similar “nested” constructions, for example, new BufferedReader(new InputStreamReader(...))in previous lectures, so they should not scare you :) By creating such a “chain” of two threads, we perform both tasks: we turn the object SavedGameinto a set of bytes and save it to a file using method writeObject(). And, by the way, we didn’t even check what we got! It's time to look at the file! *Note: the file does not need to be created in advance. If a file with that name does not exist, it will be created automatically* And here are its contents! ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранцвоюет SЃ R оссией, Р˜С ЃРїР° РЅРёСЏ Р·Р°Ряла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt Р J R РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 РїСЂРѕРІРёРЅС †РёР№t %РЈ Р РѕСЃСЃРёРё 10 РїСЂРѕРІРёРЅС †РёР№t &РЈ ФранцРеРё 8 проввинций Oops :( It seems our program didn’t work :( Actually, it worked. You remember that we transferred exactly a set of bytes to the file, and not just an object or text? Well, this is what this set looks like :) This is our saved game! If we want to restore our original object, that is, load and continue the game from where we left off, we need the reverse process , deserialization ... This is what it will look like in our case:
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);
   }
}
And here is the result! 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 occupied position of neutrality]} Great! We managed to first save the state of our game to a file, and then restore it from the file. Now let's try to do the same, but remove SavedGamethe version identifier from our class. We won’t rewrite both of our classes, the code in them will be the same, we’ll just SavedGameremove private static final long serialVersionUID. Here is our object after serialization: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФранция воюет СЃ Россией, Р˜СЃРїР°РЅРЏР·Р°РЅСЏР»Р° RїРѕР·РёС†РёСЋ нейтралитетаuq ~ t "РЈ Р˜СЃРї ании 100 Р· олотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 R їСЂРѕРІРёРЅС† РёР№t %РЈ Р РѕСЃСЃРёРё 10 провнинцийt &РЈ Франции 8 проввинцинА And when trying to deserialize it, this is what happened: InvalidClassException: local class incompatible : stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747 This is the same exception that was mentioned above. You can read more about this in the article of one of our students. By the way, we missed one important point. It is clear that strings and primitives are easily serialized: Java certainly has some- then there are built-in mechanisms for this. But what if our serializable-class has fields that are expressed not as primitives, but as references to other objects? Let's, for example, create separate classes TerritoriesInfoto work with our class . ResourcesInfoDiplomacyInfoSavedGame
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 + '\'' +
               '}';
   }
}
But now we have a question: should all these classes be Serializable if we want to serialize the changed class 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 +
               '}';
   }
}
Well, let's check this in practice! Let's leave everything as it is for now and try to serialize the object 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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


       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();
   }
}
Result: Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo Failed! Actually, here is the answer to our question. When you serialize an object, all objects that it references in its instance variables are serialized. And if those objects also reference third objects, they are also serialized. And so on ad infinitum. All classes in this chain must be Serializable, otherwise they will not be serializable and an exception will be thrown. This, by the way, may create problems in the future. What should we do, for example, if we don’t need part of the class during serialization? Or, for example, TerritoryInfowe inherited a class in our program as part of some library. However, it is not Serializable, and, accordingly, we cannot change it. It turns out that we cannot add a field TerritoryInfoto our class , because then the entire class will become non-serializable! Problem:/ Problems of this kind are solved in Java using the keyword . If you add this keyword to a class field, the value of this field will not be serialized. Let's try to make one of the fields of our class , after which we will serialize and restore one object. SavedGameSavedGameSerialization and Deserialization in Java - 2transientSavedGame transient
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
       TerritoriesInfo territoriesInfo = new TerritoriesInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourcesInfo resourcesInfo = new ResourcesInfo("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 position of neutrality");


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


   }
}
And here is the result: 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 position neutrality'}} At the same time, we received an answer to the question of what value will be assigned transientto the -field. It is assigned a default value. In the case of objects this is null. At your leisure, you can read this excellent article about serialization . It also talks about the interface Externalizable, which we will talk about in the next lecture. In addition, there is a chapter on this topic in the book “Head-First Java”, pay attention to it :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION