Serialization in Java refers to the process of converting an object's current state into a stream of bytes. This capability is widely employed in technologies such as Hibernate, RMI, JPA, EJB, and JMS.
Deserialization is the process that reverses serialization, converting a byte stream back into an object. This serialization and deserialization mechanism is not bound to a specific platform, allowing objects to be serialized on one platform and deserialized on another.
To serialize an object, the writeObject method of the ObjectOutputStream class is invoked, while for deserialization, the readObject method of the ObjectInputStream class is utilized.
Serialization in Java converts an object into a byte stream, facilitating its storage or transmission across a network. Various technologies like Hibernate, RMI (Remote Method Invocation), JPA (Java Persistence API), EJB (Enterprise JavaBeans), and JMS (Java Message Service) extensively make use of this functionality.
In order to serialize an object, it is necessary to incorporate the Serializable interface into the class.
Advantages of Java Serialization
Its primary purpose is to transmit the state of an object over a network, a process commonly referred to as marshalling.
- Platform Independence: There are no compatibility problems when transferring serialized objects between separate Java virtual machines (JVMs) running on different platforms. Java Serialization is an effective technique for inter-system communication because of its platform independence.
- Network Communication: Java Serialization facilitates the transmission of object data over a network. This process, known as marshalling, allows objects to be serialized into a byte stream and sent across a network to be reconstructed on another machine. It is crucial for applications involving distributed systems, such as client-server architectures and web services.
- Object Persistence: Serialization allows for indefinite storage of object state. Data can be kept between program executions by using serialized objects, which can be saved to a disc or a database and then retrieved.
java.io.Serializable interface
In Java, the Serializable interface serves as a marker interface, lacking data members and methods. Its purpose is to designate Java classes, enabling objects of these classes to possess a specific capability. Similar to Serializable, Cloneable and Remote are also examples of marker interfaces.
Any class that requires its object to be stored must have the Serializable interface implemented.
By default, the java.io.Serializable interface is implemented by both the String class and all wrapper classes in Java.
Let's see the example given below:
Student.java
import java.io.Serializable;
public class Student implements Serializable{
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
Explanation
In this script, a Java class called Student is created, which implements the Serializable interface. The purpose of the Serializable interface is to act as a flag, signaling that objects of the Student class are capable of serialization. Serialization involves transforming the object's state into a byte stream, allowing it to be stored or transmitted.
The class named Student contains two instance variables: id and name, which hold the student's ID and name correspondingly. It features a constructor that sets these variables using provided values. Through the implementation of the Serializable interface, objects of the Student class can be serialized and deserialized smoothly, enabling their application in tasks like object persistence and network communication.
ObjectOutputStream Class
The ObjectOutputStream class is employed for composing primitive data types and Java objects to an OutputStream. It is essential that only objects which implement the java.io.Serializable interface are capable of being written to streams.
It serves as a connection between byte streams and Java objects, facilitating the serialization and storing of objects to a file or network link, among various other destinations. It is important to note that only objects belonging to the java.io package can be implemented. ObjectOutputStream is utilized for writing serializable interfaces to streams. This interface guarantees that objects can be serialized and their state can be converted into a byte stream.
Constructors
| public ObjectOutputStream(OutputStream out) throws IOException | It creates an ObjectOutputStream that writes to the specified OutputStream. |
|---|---|
| public ObjectOutputStream(OutputStream out, int bufferSize) throws IOException | It creates an ObjectOutputStream that writes to the specified OutputStream with the given buffer size. |
| protected ObjectOutputStream() throws IOException, SecurityException | It is used for subclass construction. Creates an ObjectOutputStream instance. |
| protected ObjectOutputStream(OutputStream out) throws IOException | It is used for subclass construction. Creates an ObjectOutputStream that writes to the specified OutputStream. |
Important Methods
| Method | Description |
|---|---|
| public final void writeObject(Object obj) throws IOException {} | It writes the specified object to the ObjectOutputStream. |
| public void flush() throws IOException {} | It flushes the current output stream. |
| public void close() throws IOException {} | It closes the current output stream. |
ObjectInputStream class
The ObjectInputStream class is responsible for converting serialized objects and primitive data that were previously written using an ObjectOutputStream.
Constructor
| 1) public ObjectInputStream(InputStream in) throws IOException {} | It creates an ObjectInputStream that reads from the specified InputStream. |
|---|
Important Methods
| Method | Description |
|---|---|
| 1) public final Object readObject() throws IOException, ClassNotFoundException{} | It reads an object from the input stream. |
| 2) public void close() throws IOException {} | It closes ObjectInputStream. |
Example of Java Serialization
In this instance, we will demonstrate the serialization of an object belonging to the Student class in the preceding code. The ObjectOutputStream class's writeObject method facilitates object serialization. The object's state is being stored in a file named f.txt.
Persist.java
import java.io.*;
class Persist{
public static void main(String args[]){
try{
//Creating the object
Student s1 =new Student(211,"ravi");
//Creating stream and writing the object
FileOutputStream fout=new FileOutputStream("f.txt");
ObjectOutputStream out=new ObjectOutputStream(fout);
out.writeObject(s1);
out.flush();
//closing the stream
out.close();
System.out.println("success");
}catch(Exception e){System.out.println(e);}
}
}
Output:
success
Explanation
The Java code shown above demonstrates the utilization of the ObjectOutputStream class for object serialization. In the main function, a Student object is instantiated with an ID of 211 and the name "ravi". Subsequently, data is encoded into a file named "f.txt" through a FileOutputStream instance named fout. The Student object s1 is serialized and saved to the file by creating an ObjectOutputStream named out, which encapsulates fout.
In order to free up associated system resources, the streams are closed using the close method after ensuring that any buffered data has been written to the file by invoking flush. If the operation is successful, the application will output "success" to the console.
Example of Java Deserialization
Deserialization involves rebuilding an object from its serialized form, serving as the inverse of serialization. An illustration of this concept is when we retrieve data from a deserialized object.
Deserialization involves reconstructing an object from its serialized state, which is the opposite of serialization. Below is an example demonstrating how data is retrieved from a deserialized object.
Depersist.java
import java.io.*;
class Depersist{
public static void main(String args[]){
try{
//Creating stream to read the object
ObjectInputStream in=new ObjectInputStream(new FileInputStream("f.txt"));
Student s=(Student)in.readObject();
//printing the data of the serialized object
System.out.println(s.id+" "+s.name);
//closing the stream
in.close();
}catch(Exception e){System.out.println(e);}
}
}
Output:
211 ravi
Explanation
The following Java code snippet demonstrates the utilization of the ObjectInputStream class for the process of deserializing objects. An instance of FileInputStream reading data from the file named "f.txt" is enclosed within an ObjectInputStream named 'in' within the main method. The object that was serialized and stored in the file is then retrieved using this input stream. Subsequently, the object is deserialized and converted into the Student class by employing the readObject method.
The Student object's ID and name, which have been deserialized, are displayed on the console along with additional relevant data. To release system resources connected to this operation, the stream is closed by invoking the close method. Exception handling is in place during deserialization to capture any errors and display their respective error messages.
Java Serialization with Inheritance (IS-A Relationship)
When a class implements the Serializable interface, this means that all its derived classes will also inherit serializability. An example illustrating this concept is provided below:
SerializeISA.java
import java.io.Serializable;
class Person implements Serializable{
int id;
String name;
Person(int id, String name) {
this.id = id;
this.name = name;
}
}
class Student extends Person{
String course;
int fee;
public Student(int id, String name, String course, int fee) {
super(id,name);
this.course=course;
this.fee=fee;
}
}
public class SerializeISA
{
public static void main(String args[])
{
try{
//Creating the object
Student s1 =new Student(211,"ravi","Engineering",50000);
//Creating stream and writing the object
FileOutputStream fout=new FileOutputStream("f.txt");
ObjectOutputStream out=new ObjectOutputStream(fout);
out.writeObject(s1);
out.flush();
//closing the stream
out.close();
System.out.println("success");
}catch(Exception e){System.out.println(e);}
try{
//Creating stream to read the object
ObjectInputStream in=new ObjectInputStream(new FileInputStream("f.txt"));
Student s=(Student)in.readObject();
//printing the data of the serialized object
System.out.println(s.id+" "+s.name+" "+s.course+" "+s.fee);
//closing the stream
in.close();
}catch(Exception e){System.out.println(e);}
}
}
Output:
success
211 ravi Engineering 50000
Explanation
The Java code given demonstrates the process of object serialization and deserialization, involving inheritance. Within the code, there is a superclass called Person that establishes fundamental characteristics like id and name, and it also implements the Serializable interface. Extending the Person class, the Student class introduces additional features such as course and fee, and it includes a constructor that sets values for all properties, including those inherited from the superclass.
The SerializeISA class showcases serialization by instantiating a Student object (s1), saving it to a file called "f.txt" with an ObjectOutputStream, and ensuring the stream is flushed and closed. Following this, deserialization takes place by retrieving the serialized object from "f.txt" using an ObjectInputStream and displaying its properties on the screen. Robust exception handling is in place to address any potential issues that may arise during file handling or object processing.
Java Serialization with Aggregation (HAS-A Relationship)
When a class contains a reference to another class, it is essential for all references to be Serializable to enable the serialization process. Failure to do so will result in the runtime throwing a NotSerializableException.
Address.java
class Address{
String addressLine,city,state;
public Address(String addressLine, String city, String state) {
this.addressLine=addressLine;
this.city=city;
this.state=state;
}
}
Student.java
import java.io.Serializable;
public class Student implements Serializable{
int id;
String name;
Address address;//HAS-A
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
Because the Address class lacks serializability, it is not possible to serialize an object of the Student class.
Note: All the objects within an object must be Serializable.
Explanation
Within the provided code, there are two classes named Location and Learner. The Location class, upon instantiation, contains details such as addressLine, city, and state to represent an address. On the other hand, the Learner class establishes an association by encapsulating properties like name and id to signify a student and by including a connection to an instance of the Location class.
In order for Student instances to be serialized, the class utilizes the Serializable interface. However, the absence of Serializable implementation in the Address class may lead to serialization issues. To address this, it is essential for the Address class to also implement Serializable. By doing so, the serialization process of Student objects containing Address references will be smooth and error-free.
Java Serialization with the Static Data Member
Serialization excludes static data members present within a class.
Put differently, when a class is serialized, its static variables are excluded from the serialization process. Only the instance variables of the class are serialized. Static variables are associated with the class as a whole, rather than with any specific instance of the class, and therefore they are not considered part of the object's state for serialization purposes.
Consequently, upon deserialization, an object's static variables are not initialized from the serialized stream. Instead, they are initialized with their default values or the values they had when the class was loaded into memory. This approach aims to ensure uniformity and avoid potential issues stemming from the shared characteristics of static variables across all instances in a class.
Employee.java
class Employee implements Serializable{
int id;
String name;
static String company="SSS IT Pvt Ltd";//it won't be serialized
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
Explanation
Within the Employee class given, there exist three attributes: id, name, and company. While id and name hold unique data for individual employees and are included in the serialization process of an Employee object, the company attribute is defined as static. This static nature designates company as a class-level property shared across all instances of the Employee class. Static variables are excluded from serialization as they are not reflective of the object's specific state; instead, they pertain to the class structure.
In the serialization procedure, solely instance variables get serialized, while static variables get disregarded. Consequently, when serializing and deserializing an Employee object, the id and name attributes will be reverted to their initial values, whereas the company attribute will preserve the value specified in the class declaration rather than the serialized stream. This functionality guarantees that static variables uphold their uniformity among all instances and remain unaffected by object serialization.
Java Serialization with Array or Collection
Guideline: Every object within an array or collection needs to be serializable for successful serialization. Failure to serialize will occur if any object lacks serializability.
In Java serialization, it is essential to ensure that all elements within arrays or collections are serializable. This requirement means that each object contained in an array or a collection like an ArrayList must implement the Serializable interface.
When trying to serialize an array or a collection, if any of the objects within them are not serializable, the process will result in failure and typically raise a NotSerializableException. This occurs because serialization operates recursively, requiring every object within the array or collection to be serializable for successful serialization to take place.
In order to prevent serialization issues when working with arrays or collections, it is important to confirm that all items stored in them are capable of being serialized. If there is a need to store an object that cannot be serialized, it is advisable to explore options like transient fields or personalized serialization techniques to manage its serialization and deserialization process effectively. Furthermore, it is essential to consistently manage serialization exceptions in a way that offers proper error handling and informative responses to users.
Externalizable in Java
The Externalizable interface enables the serialization of an object's state into a compact byte stream. Unlike a marker interface, it offers the capability to write object state.
If a class implements the Externalizable interface, it is required to define implementations for two specific methods: writeExternal and readExternal. These methods are called upon by the serialization mechanism to record the state of the object into an ObjectOutput stream and to retrieve the object's state from an ObjectInput stream, respectively.
The Externalizable interface provides two methods:
- public void writeExternal(ObjectOutput out) throws IOException
- public void readExternal(ObjectInput in) throws IOException
- writeExternal(ObjectOutput out): This method is called during serialization and is responsible for writing the object's state to the specified ObjectOutput stream. Developers can customize the serialization process by writing specific fields or data to the stream in any desired format. It can be used to optimize serialization by only writing essential data or by compressing the output, as mentioned in your explanation.
- readExternal(ObjectInput in): This method is called during deserialization and is responsible for reconstructing the object's state from the specified ObjectInput stream. Developers must read the data from the stream in the same order and format as it was written during serialization. It allows for custom deserialization logic, such as handling backward compatibility or performing additional validation checks on the input data.
Java transient Keyword
To prevent the serialization of any data member in a class, we can designate it as transient.
Employee.java
class Employee implements Serializable{
transient int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
In this scenario, the identifier (id) will not undergo serialization. Consequently, upon deserialization of the object that has been serialized, the id value will not be retrieved, resulting in a default value being returned consistently. In this particular instance, the default value returned will be 0 due to the fact that the data type of id is an integer.
Visit next page for more details.
SerialVersionUID
During runtime, the serialization process assigns a unique identifier to each Serializable class, which is referred to as SerialVersionUID. This identifier plays a crucial role in ensuring that the sender and receiver of a serialized object match. It is essential for the SerialVersionUID of both parties to be identical. Failure to match these identifiers will result in the generation of an InvalidClassException error during the deserialization of the object. Programmers also have the option to define their own SerialVersionUID within the Serializable class. To achieve this, they must create a SerialVersionUID field and provide it with a value. This field should be of type long, static, and final. It is recommended to explicitly declare the serialVersionUID field in the class and mark it as private.
When the serialVersionUID values are in sync, the deserialization process proceeds smoothly. In cases where they do not align, an InvalidClassException is triggered, stopping the deserialization of the object. This discrepancy indicates a potential difference in the class configuration between the sender and receiver. This mechanism serves to prevent unforeseen issues stemming from inconsistent class versions and plays a role in upholding data integrity.
It is advisable to explicitly declare a static final serialVersionUID field in Serializable classes, despite Java's automatic generation of serialVersionUID for Serializable classes using factors such as class composition, field data types, and method declarations. By defining the serialVersionUID directly, developers ensure consistent serialization functionality, even when there are alterations to class signatures like the addition or deletion of fields.
For example:
private static final long serialVersionUID=1L;
Now, the Serializable class will look like this:
Employee.java
import java.io.Serializable;
class Employee implements Serializable{
private static final long serialVersionUID=1L;
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
Explanation
It is noted that objects of this class are serializable due to the implementation of the Serializable interface by the provided Employee class. Inside the class, there is a static final variable named serialVersionUID, which serves as a versioning tool for serialization.
This aspect ensures consistency in the serialized form of the class across different versions. By explicitly assigning a value (such as 1L) to serialVersionUID, developers can effectively manage versioning and maintain authority over the serialization procedure.
Within the Employee class, there exist two instance variables: 'name' representing the employee's name and 'id' representing the identification number. During the serialization process of an Employee object, these variables will be serialized along with the object's overall state.