Serialization and deserialization are common techniques used in software development for data persistence and communication between applications. In Java, serialization refers to the process of converting an object into a stream of bytes so that it can be stored in a file or transferred over a network. Deserialization, on the other hand, is the process of reconstructing an object from its serialized form. In this article, we will explore how to serialize and deserialize objects in Java.
What is Serialization and Deserialization in Java?
Serialization is the process of converting an object into a byte stream so that it can be stored or transmitted. The byte stream can then be reconstructed back into the original object through deserialization.
Serialization is useful for scenarios where you need to persist object state or transfer object state between processes or over a network. Examples include saving the state of a game, sending data between different servers, or caching data in memory.
In Java, serialization is achieved through the use of the Serializable interface. The Serializable interface is a marker interface, which means it does not contain any methods or fields but serves as an indicator to the JVM that the class can be serialized.
When a class implements the Serializable interface, it is allowing its state to be saved and restored. To mark a class as serializable, simply add the "implements Serializable" keyword to the class declaration. However, not all classes can be serialized. Classes that cannot be serialized include those that contain static or transient fields, anonymous classes, and classes that inherit from non-serializable classes.
Deserialization is the process of reconstructing an object from its serialized form. This is achieved by reading the byte stream and recreating the object and its state. During deserialization, the JVM reads the byte stream and creates a new object of the same class as the original object. The state of the object is then restored by reading the object's fields from the byte stream. It's important to note that during deserialization, the object's constructor is not called. Instead, the object's state is restored using the readObject() method of the ObjectInputStream class.
Serialization in Java
As we discussed serialization in Java is the process of converting an object into a byte stream so that it can be stored or transmitted. This byte stream can then be reconstructed back into the original object through deserialization. Serialization is a fundamental feature of Java that allows you to persist object state or transfer object state between processes or over a network.
In Java, serialization is achieved through the use of the Serializable interface. This interface is a marker interface that doesn't contain any methods or fields but serves as an indicator to the Java Virtual Machine (JVM) that the class can be serialized. When a class implements the Serializable interface, it's indicating to the JVM that it's safe to serialize instances of that class.
To make a class serializable, you simply need to add the "implements Serializable" keyword to the class declaration. Here's an example:
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
In this example, the Person class implements the Serializable interface to indicate that it can be serialized. The class has two fields: name and age, which will be serialized along with the object.
Serialization Process in Java
The serialization process involves converting an object into a stream of bytes that can be written to a file or transmitted over a network. Here are the steps involved in serializing an object in Java:
- Create an ObjectOutputStream object that will write the serialized data to an OutputStream object (e.g. FileOutputStream, ByteArrayOutputStream).
- Call the writeObject() method of the ObjectOutputStream object and pass the object to be serialized as an argument.
- Close the ObjectOutputStream object.
Here's an example:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationExample {
public static void main(String[] args) {
try {
Person person = new Person("John", 30);
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in person.ser");
} catch (IOException i) {
i.printStackTrace();
}
}
}
Output:
Serialized data is saved in person.ser
Once you run the code, you will notice that a new file named person.ser
has been created where the serialized data is saved. For example, in my case here is the new file created.
In this example, we create a Person object and serialize it by writing it to a file named "person.ser" using an ObjectOutputStream object. The FileOutputStream object is used to write the serialized data to a file.
Explanation of the Code
Let's take a closer look at the code above. In the first line of the main() method, we create a new Person object and pass in two arguments: name and age. This object is then serialized using an ObjectOutputStream object:
Person person = new Person("John", 30);
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
We first create a FileOutputStream object and pass in the name of the file we want to save the serialized data to. Then, we create an ObjectOutputStream object and pass in the FileOutputStream object. Finally, we call the writeObject() method of the ObjectOutputStream object and pass in the Person object we want to serialize.
After serializing the Person object, we close the ObjectOutputStream and FileOutputStream objects:
out.close();
fileOut.close();
It's important to note that if the Person class had any non-serializable fields, we would need to mark them as transient to prevent them from being serialized.
Custom serialization
Custom serialization in Java refers to the process of overriding the default serialization behavior of an object to provide a customized way of serializing or deserializing an object.
By default, Java's object serialization mechanism serializes all non-transient instance variables of an object. However, there may be cases where the default serialization behavior is not sufficient or desirable, such as when an object contains sensitive data that should not be serialized, or when the object contains a complex data structure that cannot be serialized by the default mechanism.
To customize serialization behavior in Java, a class can implement the Serializable interface and define the readObject()
and writeObject()
methods. The readObject()
method is called when the object is deserialized, and the writeObject()
method is called when the object is serialized.
In these methods, the class can define how the object should be serialized or deserialized by manually reading or writing each non-transient instance variable. The class can also choose which instance variables to serialize or deserialized based on its needs, such as excluding sensitive data from serialization.
Here is an example of a custom serialization in Java:
public class MyClass implements Serializable {
private String name;
private int age;
private transient String password;
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
// getters and setters
}
In this example, the password variable is marked as transient, which means it will not be serialized. The writeObject()
method is defined to only serialize the name and age variables, and the readObject()
method is defined to read the name and age variables from the input stream.
Deserialization in Java
As we discussed, deserialization in Java is the process of converting a serialized object back into its original object form. This is done by reading the serialized data from an InputStream object and creating a new object with the same state as the original object.
In Java, deserialization is achieved through the use of the ObjectInputStream
class. This class reads a byte stream of serialized data and converts it back into an object.
Deserialization Process in Java
The deserialization process involves converting a stream of bytes back into an object. Here are the steps involved in deserializing an object in Java:
- Create an
ObjectInputStream
object that will read the serialized data from an InputStream object (e.g. FileInputStream, ByteArrayInputStream). - Call the
readObject()
method of theObjectInputStream
object to read the serialized object from the byte stream. - Close the
ObjectInputStream
object.
Now we will take an example and deserialize the data from the previous example where we have saved data a file using the serialization process.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
try {
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Deserialized data: " + person.getName() + " " + person.getAge());
} catch (IOException i) {
i.printStackTrace();
} catch (ClassNotFoundException c) {
System.out.println("Person class not found");
c.printStackTrace();
}
}
}
Output:
Deserialized data: John 30
In this example, we deserialize a Person object by reading it from a file named "person.ser
" using an ObjectInputStream
object. The FileInputStream object is used to read the serialized data from the file.
Explanation of the code
Let's take a closer look at the code above. In the first line of the main() method, we create a new FileInputStream object and pass in the name of the file we want to read the serialized data from:
FileInputStream fileIn = new FileInputStream("person.ser");
We then create an ObjectInputStream object and pass in the FileInputStream object:
ObjectInputStream in = new ObjectInputStream(fileIn);
Next, we call the readObject() method of the ObjectInputStream object to read the serialized Person object from the byte stream. Since the readObject() method returns an Object, we cast it to a Person object:
Person person = (Person) in.readObject();
After deserializing the Person object, we close the ObjectInputStream
and FileInputStream
objects:
in.close();
fileIn.close();
It's important to note that during deserialization, the object's constructor is not called. Instead, the object's state is restored using the readObject()
method of the ObjectInputStream
class. Additionally, any non-serializable fields will be set to their default values during deserialization. If you want to ensure that non-serializable fields are properly initialized, you can use the readObject()
and writeObject()
methods to provide your own custom serialization and deserialization logic.
Example-1: Basic serialization and deserialization
In this example, we create a Person object and serialize it to a file using ObjectOutputStream
. We then deserialize the object from the file using ObjectInputStream
and print its properties to the console.
import java.io.*;
public class SerializationExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// Create an object to be serialized
Person person = new Person("John", 25);
// Serialize the object to a file
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("Object serialized to person.ser");
// Deserialize the object from the file
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person deserializedPerson = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Object deserialized from person.ser");
System.out.println("Name: " + deserializedPerson.getName());
System.out.println("Age: " + deserializedPerson.getAge());
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Example-2: Custom serialization and deserialization
In this example, we create a PersonWithPassword
object that contains a password variable marked as transient, which means it will not be serialized. We then define custom serialization and deserialization methods that only serialize and deserialize the name and age variables. When we deserialize the object, the password variable will not be restored from the input stream. Therefore, the getPassword()
method will return null for the deserialized object.
import java.io.*;
public class CustomSerializationExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// Create an object to be serialized
PersonWithPassword person = new PersonWithPassword("John", 25, "mypassword");
// Serialize the object to a file
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("Object serialized to person.ser");
// Deserialize the object from the file
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
PersonWithPassword deserializedPerson = (PersonWithPassword) in.readObject();
in.close();
fileIn.close();
System.out.println("Object deserialized from person.ser");
System.out.println("Name: " + deserializedPerson.getName());
System.out.println("Age: " + deserializedPerson.getAge());
System.out.println("Password: " + deserializedPerson.getPassword());
}
}
class PersonWithPassword implements Serializable {
private String name;
private int age;
private transient String password;
public PersonWithPassword(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getPassword() {
return password;
}
}
Summary
Serialization and deserialization of objects are important features of Java that allow objects to be stored and retrieved in a persistent manner. Java provides built-in support for serialization and deserialization through the Serializable interface and the ObjectInputStream and ObjectOutputStream classes.
Custom serialization and deserialization can also be implemented by overriding the default serialization and deserialization methods in Java. This allows for more control over the serialization and deserialization process, such as excluding sensitive data from serialization or converting data to a different format.
Serialization and deserialization can be used for a variety of purposes, including storing application state, sending data over a network, and caching data. However, it is important to follow best practices when using serialization and deserialization, such as handling versioning and backward compatibility, avoiding serialization of sensitive data, and ensuring that serialized objects are thread-safe.
Further Readings
Serialization and deserialization in java