Hi! In today's lesson, we'll talk about serialization and deserialization in Java. We'll start with a simple example. Imagine that you're a computer game developer. If you grew up in the 90s and remember the game consoles of that era, you probably know that they lacked something we take for granted today — the ability to save and load games :) If not, imagine that! I'm afraid that a game without these abilities today would be doomed! Anyway, what does it mean to 'save' and 'load' a game? Well, we understand the everyday meaning: we want to continue the game from the place where we left off. To do this, we create a certain 'check point' that we use later to load the game. But what does that mean to a programmer rather than a casual gamer? The answer is simple: we save the state of our program. Let's say you're playing Spain in a strategy game. Your game has state: which territories everybody has, how many resources everybody has, what alliances exist and with whom, who is at war, and so on. This information, our program's state, must somehow be saved in order to restore the data and continue the game. As it happens, serialization and deserialization are the mechanisms used for this. Serialization is the process of saving an object's state as 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 do we need this? We've repeatedly said that programs don't exist by themselves. Most often, they interact with one another, exchange data, etc. A byte format is convenient and efficient for this. For example, we can convert an object of our SavedGame class into a sequence of bytes, transfer these bytes over the network to another computer, and then on the other computer convert these bytes back into a Java object! It sounds difficult, huh? It seems like it would be difficult to make this all happen: / Happily, that's not the case! :) In Java, the Serializable interface is responsible for the serialization process. This interface is extremely simple: You don't have to implement a single method to use it! Look at how simple our class for saving games 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) +
               '}';
   }
}
Three arrays are responsible for information about territories, resources, and diplomacy, and the Serializable interface tells the Java machine: 'everything is fine if objects of this class can be serialized'.' An interface without a single interface looks weird :/ Why is it necessary? The answer to that question is given above: it is needed only to provide the necessary information to the Java machine. In a past lesson, 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. Here's Serializable — one such interface. Here's another important point: Why do we need the private static final long serialVersionUID variable that we defined in the class? This field contains the unique version identifier of the serialized class. Every class that implements the Serializable interface has a version identifier. It is determined based on the content of the class — fields and their declaration order, and methods and their declaration order. And if we change a field type and/or the number of fields in our class, the version identifier changes instantly. The serialVersionUID is also written when the class is serialized. When we try to deserialize, i.e. restore an object from a byte sequence, the value of serialVersionUID is compared with value of the serialVersionUID of the class in our program. If the values don't match, then a java.io.InvalidClassException will be thrown. We'll see an example of this below. To avoid such situations, we simply set the version identifier for our class manually. In our case, it will simply be equal to 1 (you can use 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[] 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();
   }
}
As you can see, we created 2 streams: FileOutputStream and ObjectOutputStream. The first knows how to write data to the file, and the second converts objects to bytes. You've already seen similar nested constructs, for example, new BufferedReader(new InputStreamReader(...)), in previous lessons, so they shouldn't scare you :) By creating this chain of two streams, we perform both tasks: we convert the SavedGame object into a sequence 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: it's not necessary to create the file in advance. If a file with the specified name doesn't exist, then it will be created automatically* And here are its contents: ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourceInfoq ~ [ territoryInfoq ~ xpur [Ljava.lang.String;¬ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций Oh, oh :( It seems that our program did not work : ( Actually, it worked. You remember that we sent a sequence of bytes, and not simply an object or text, to the file? Well, this is what this byte sequence looks like :) It 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. This is what it looks like for us:
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{territoryInfo=[Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourceInfo=[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 the state of our game to a file, and then to restore it from the file. Now let's try to do the same thing, but we'll remove the version identifier from our SavedGame class. We won't rewrite both of our classes. Their code will be the same. We'll just 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;[ resourceInfoq ~ [ territoryInfoq ~ 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 By the way, we missed something important. Obviously, strings and primitives are serialized easily: Java certainly has some built-in mechanism for 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 TerritoryInfo, ResourceInfo and DiplomacyInfo class to work with our SavedGame class.
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 + '\'' +
               '}';
   }
}
And now we face a question: Must all of these classes be Serializable if we want to serialize our SavedGame class?
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 +
               '}';
   }
}
Alright, then! Let's test it! For now, we'll 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(territoryInfo, resourceInfo, 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! So, here's the answer to our question. When an object is serialized, all the objects referenced by its instance variables are serialized. And if those objects also reference other objects, then they are also serialized. And on and on forever. 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. For example, what should we do if we don't need part of a class during serialization? Or what if we got our TerritoryInfo class 'through inheritance' as part of a library? And suppose further that it isn't Serializable and, accordingly, we can't change it. That would mean that we can't add a TerritoryInfo field to our SavedGame class, because then the whole SavedGame class would become unserializable! That's a problem: / In Java, this sort of problem is solved by the transient keyword. If you add this keyword to a field of your class, then that field won't be serialized. Let's try making one of the fields of our SavedGame class transient, and then we'll serialize and restore a single object.
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();


   }
}
And here's the result: SavedGame{territoryInfo=null, resourceInfo=ResourceInfo{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'}} That said, we got an answer to the question of what value will be assigned to a transient field. It is assigned the default value. For objects, this is null. You can read an excellent chapter on this topic in the book 'Head-First Java', Pay attention to it :)