Understanding records serialization – Record and record pattern

0 Comments 5:41 AM

89. Understanding records serialization

In order to understand how Java records are serialized/deserialized, let’s have a parallel between a classical code based on plain Java classes and the same code but expressed via the Java record’s syntactical sugar.So, let’s consider the following two plain Java classes (we have to explicitly implement the Serializable interface because, in the second part of this problem, we want to serialize/ deserialize these classes):

public class Melon implements Serializable {
  private final String type;
  private final float weight;
  public Melon(String type, float weight) {
    this.type = type;
    this.weight = weight;
  }
  // getters, hashCode(), equals(), and toString()
}

And, the MelonContainer class that uses the previous Melon class:

public class MelonContainer implements Serializable {
  private final LocalDate expiration;
  private final String batch;
  private final Melon melon;
  public MelonContainer(LocalDate expiration,
      String batch, Melon melon) {
    …
    if (!batch.startsWith(“ML”)) {
      throw new IllegalArgumentException(
        “The batch format should be: MLxxxxxxxx”);
    }
    …
    this.expiration = expiration;
    this.batch = batch;
    this.melon = melon;
  }
  // getters, hashCode(), equals(), and toString()
}

If we express this code via Java records then we have the following code:

public record MelonRecord(String type, float weight)
  implements Serializable {}
public record MelonContainerRecord(
  LocalDate expiration, String batch, Melon melon)
  implements Serializable {
  public MelonContainerRecord {
    …
    if (!batch.startsWith(“ML”)) {
      throw new IllegalArgumentException(
        “The batch format should be: MLxxxxxxxx”);
    }      
    …
  }
}

Notice that we have explicitly implemented the Serializable interface since by default Java records are not serializable.Next, let’s create a MelonContainer instance:

MelonContainer gacContainer = new MelonContainer(
  LocalDate.now().plusDays(15), “ML9000SQA0”,
    new Melon(“Gac”, 5000));

And, a MelonContainerRecord instance:

MelonContainerRecord gacContainerR = new MelonContainerRecord(
  LocalDate.now().plusDays(15), “ML9000SQA0”,
    new Melon(“Gac”, 5000));

To serialize these objects (gacContainer, and gacContainerR) we can use the following code:

try ( ObjectOutputStream oos = new ObjectOutputStream(
   new FileOutputStream(“object.data”))) {
     oos.writeObject(gacContainer);
}
try ( ObjectOutputStream oos = new ObjectOutputStream(
   new FileOutputStream(“object_record.data”))) {
     oos.writeObject(gacContainerR);
}

And, the deserialization can be accomplished via the following code:

MelonContainer desGacContainer;
try ( ObjectInputStream ios = new ObjectInputStream(
  new FileInputStream(“object.data”))) {
  desGacContainer = (MelonContainer) ios.readObject();
}
MelonContainerRecord desGacContainerR;
try ( ObjectInputStream ios = new ObjectInputStream(
  new FileInputStream(“object_record.data”))) {
  desGacContainerR = (MelonContainerRecord) ios.readObject();
}

Before exploiting these snippets of code for a practical examination of serialization/deserialization, let’s try a theoretical approach meant to provide some hints of these operations.

How serialization/deserialization works?

The serialization/deserialization operations can be represented in the following diagram:

Figure 4.2 – Java serialization/deserialization operations

In a nutshell, serialization (or serializing an object) is the operation of extracting the state of an object as a byte stream and representing it as a persistent format (a file, a database, in memory, over the network, and so on). The reverse operation is called deserialization (or deserializing an object) and represents the steps of reconstructing the object state from the persistent format.In Java, an object is serializable if it implements the Serializable interface. This is an empty interface with no state or behavior that acts as a marker for the compiler. In the absence of this interface, the compiler assumes that the object is not serializable.The compiler uses its internal algorithm for the serialization of objects. This algorithm relies on every trick in the book like special privileges (ignoring accessibility rules) to access objects, malicious reflection, constructors bypassing, and so on. It is beyond our purpose to bring light in this dark magic, so as a developer is enough to know that:

If a part of an object is not serializable then you’ll get a runtime error You can alter the serialization/ deserialization operations via writeObject()/readObject() API

Ok, now let’s see what’s going on when an object is serialized.

Leave a Reply

Your email address will not be published. Required fields are marked *

Covering Vector API structure and terminology 2 – Arrays, collections and data structuresCovering Vector API structure and terminology 2 – Arrays, collections and data structures

The Vector lanes A Vector<E> is like a fixed-sized Java array made of lanes. The lane count is returned by the length() method and is called VLENGTH. The lane count

Adding more artifacts in a record Certification Exams of Java Java Exams Tackling records in Spring Boot Understanding records serialization

Introducing the canonical and compact constructors for records 2 – Record and record patternIntroducing the canonical and compact constructors for records 2 – Record and record pattern

Reassigning components Via an explicit canonical/compact constructor we can reassign components. For instance, when we create a MelonRecord we provide its type (for instance, Cantaloupe) and its weight in grams

Adding more artifacts in a record Certification Exams of Java Getting a list from a stream Java Exams Tackling guarded record patterns Tackling records in Spring Boot Understanding records serialization