Pages

what-is-the-difference-between-serialization-and-deserialization-in-java

what serialization and deserealization are for. Serialization is the process of storing the state of an object in a sequence of bytes. Deserialization is the process of restoring an object from these bytes. Any Java object can be converted to a byte sequence. Why would we need that? We've said more than once that programs don't exist on their own. Most often, they interact with other programs, exchange data, etc. And a byte sequence is a convenient and efficient format. For example, we can turn our SavedGame object into a sequence of bytes, send these bytes over the network to another computer, and then on the second computer turn these bytes back into a Java object! Sounds difficult, right? And implementing this process seems like a pain :/ Happily, this isn't so! :) In Java, the Serializable interface is responsible for the serialization process. This interface is extremely simple: you don't need to implement a single method to use it! This is how simple our game-saving class looks:
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) +
               '}';
   }
}
The three arrays are responsible for information about territories, resources, and diplomacy. The Serializable interface tells the Java virtual machine: "Everything is OK — if necessary, objects of this class can be serialized". An interface without a single interface looks weird :/ Why is it necessary? The answer to this question can be seen above: it only serves to provide the necessary information to the Java virtual machine. In one of our previous lessons, we briefly mentioned marker interfaces. These are special informational 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 you have to implement. Serializable is one of those interfaces. Another important point: Why do we need the private static final long serialVersionUID variable that we defined in the class? Why is it needed? This field contains an unique identifier for the version 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: its fields, the order in which they are declared, methods, etc. If we change the type of field and/or the number of fields in our class, then the version identifier immediately changes. 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 associated serialVersionUID is compared with the value of serialVersionUID for the class in our program. If the values don't match, then a java.io.InvalidClassException will be thrown. We will see an example of this below. To avoid this, we simply set the version identifier manually in our class. In our case, it will simply be equal to 1 (but you can substitute any other number you like). Well, it's time to try to serialize our SavedGame object and 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 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();
   }
}
As you can see, we created 2 streams: FileOutputStream and ObjectOutputStream. The first one can write data to a file, and the second one converts objects to bytes. You have already seen similar "nested" constructs, for example, new BufferedReader(new InputStreamReader(...)), in previous lessons, so those shouldn't scare you :) By creating such a "chain" of two streams, we perform both tasks: we convert the SavedGame object into a set of bytes and save it to a file using the writeObject() method. And, by the way, we didn't even look at what we got! It's time to look at the file! *Note: you don't have to create the file in advance. If a file with that name doesn't 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Франция воюет СЃ Россией, Р˜СЃРїР°РЅРёСЏ заняла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅРёРё 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Uh-oh :( It seems that our program didn't work :( In fact, it did work. You recall that we sent a set of bytes, not merely an object or text, to the file? Well, this is what that set of bytes looks like :) This is our saved game! If we want to restore our original object, i.e. start and continue the game where we left off, then we need the reverse process: deserialization. Here's 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's 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 taken a neutral position]}
Excellent! We managed to first save our game's state to a file, and then restore it from the file. Now let's try to do the same thing, but without the version identifier for our SavedGame class. We won't rewrite both of our classes. Their code will remain the same, but we'll remove private static final long serialVersionUID from the SavedGame class. Here's our object after serialization:
¬н 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 провинций
But look at what happens when we try to deserialize it:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
This is the very exception we mentioned above. By the way, we missed something important. It makes sense that Strings and primitives can be easily serialized: Java probably has some kind of built-in mechanism to do this. But what if our serializable class has fields that are not primitives, but rather references to other objects? For example, let's create separate TerritoriesInfo, ResourcesInfo and DiplomacyInfo classes to work with our SavedGame class.
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 + '\'' +
               '}';
   }
}
And now a question rises: do all these classes need to be Serializable if we want to serialize our altered SavedGame class?
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 test it! Let's leave everything as it is and try to serialize a SavedGame object:
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();
   }
}
Result:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
It didn't work! Basically, that's the answer to our question. When an object is serialized, all of the objects referenced by its instance variables are serialized. And if those objects also reference other objects, then they are also serialized. And so on ad infinitum. All the classes in this chain must be Serializable, otherwise it will be impossible to serialize them and an exception will be thrown. By the way, this can create problems down the road. What should we do if, for example, we don't need part of a class when we serialize? Or, for example, what if the TerritoryInfo class came to us as part of some third-party library. And suppose further that it isn't Serializable and, accordingly, we can't change it. It turns out that we can't add a TerritoryInfo field to our SavedGame class, because doing so would make the whole SavedGame class non- serializable! That's a problem :/ What is the difference between serialization and deserialization in Java? - 2In Java, problems of this kind are solved using the transient keyword. If you add this keyword to a field of your class, then that field won't be serialized. Let's try to make one of the SavedGame class's instance fields transient. Then we'll serialize and restore one object.
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();


   }
}
And here's 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 neutral position'}}
Additionally, we got an answer to our question about what value gets assigned to a transient field. It gets assigned the default value. For objects, this is null. You can read this excellent article on serialization when you have a few minutes to spare. It also mentions the Externalizable interface, which we'll talk about in the next lesson. Additionally, the book "Head-First Java" has a chapter on this topic. Give it some attention :)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.