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
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.
As you can see, we created 2 streams:
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:
And here's the result!
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
But look at what happens when we try to deserialize it:
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
And now a question rises: do all these classes need to be
Well, let's test it! Let's leave everything as it is and try to serialize a
Result:
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
And here's the result:
Additionally, we got an answer to our question about what value gets assigned to a
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:
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!
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!
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:
And here's the result!
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:
But look at what happens when we try to deserialize it:
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.
And now a question rises: do all these classes need to be
Serializable
if we want to serialize our altered SavedGame
class?
Well, let's test it! Let's leave everything as it is and try to serialize a
SavedGame
object:
Result:
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 :/
In 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.
And here's the result:
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.