Serialization with Inheritance and Object References

Naveen AnamaneniUpdated · 4 min read

Serialization saves more than single objects. It follows inheritance, saving fields from serializable superclasses, and it follows references, saving every object an object points to. Two rules surprise many developers. First, if a superclass is not serializable, its fields aren't saved; instead its no-argument constructor runs during deserialization. Second, an object referenced twice is saved only once, so shared references and even cycles are restored exactly. In this lesson, you will see both rules in action.

Rule 1: Superclasses

SuperclassIts fields after deserialization
Implements SerializableSaved and restored
Doesn't implement SerializableNot saved; set by the superclass's no-argument constructor, which must exist and be accessible

Example: A Non-Serializable Parent

Let's see an example where the parent class isn't serializable.

ParentDemo.java▶ Try it
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ParentDemo {
    static class Person {
        String school = "unknown";
 
        Person() {
            System.out.println("Person() called");
        }
    }
 
    static class Student extends Person implements Serializable {
        String name;
        int marks;
 
        Student(String name, int marks, String school) {
            this.name = name;
            this.marks = marks;
            this.school = school;
        }
    }
 
    public static void main(String[] args)
            throws IOException, ClassNotFoundException {
        Student s = new Student("Advitha", 85, "City High");
        System.out.println("Before: " + s.name + ", " + s.marks
            + ", " + s.school);
 
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
            out.writeObject(s);
        }
        try (ObjectInputStream in = new ObjectInputStream(
                new ByteArrayInputStream(bytes.toByteArray()))) {
            Student copy = (Student) in.readObject();
            System.out.println("After:  " + copy.name + ", " + copy.marks
                + ", " + copy.school);
        }
    }
}
Output
Person() called
Before: Advitha, 85, City High
Person() called
After:  Advitha, 85, unknown

Explanation: Person isn't serializable, so its field school wasn't saved. During deserialization, Java ran Person() again, which set school to "unknown". The Student fields, name and marks, were restored normally.

Rule 2: Shared References and Cycles

Serialization gives each object a number the first time it writes it. If the same object appears again, only a back-reference to that number is written. This keeps shared objects shared and lets cycles (an object that eventually refers back to itself) work without looping forever.

SharedDemo.java▶ Try it
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
 
public class SharedDemo {
    static class Teacher implements Serializable {
        String name;
        List<Student> students = new ArrayList<>();
        Teacher(String name) { this.name = name; }
    }
 
    static class Student implements Serializable {
        String name;
        Teacher teacher;
        Student(String name, Teacher teacher) {
            this.name = name;
            this.teacher = teacher;
            teacher.students.add(this);
        }
    }
 
    public static void main(String[] args)
            throws IOException, ClassNotFoundException {
        Teacher t = new Teacher("Ms. Anamaneni");
        new Student("Navika", t);
        new Student("Srihitha", t);
 
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
            out.writeObject(t);
        }
        try (ObjectInputStream in = new ObjectInputStream(
                new ByteArrayInputStream(bytes.toByteArray()))) {
            Teacher copy = (Teacher) in.readObject();
            Student first = copy.students.get(0);
            Student second = copy.students.get(1);
            System.out.println(copy.name + " teaches " + copy.students.size());
            System.out.println("Shared teacher kept? "
                + (first.teacher == second.teacher));
            System.out.println("Cycle restored? " + (first.teacher == copy));
        }
    }
}
Output
Ms. Anamaneni teaches 2
Shared teacher kept? true
Cycle restored? true

Explanation: The teacher refers to two students, and each student refers back to the teacher, a cycle. Serialization wrote the teacher once; each later reference to it became a back-reference. After reading, both students point to the same restored teacher, which is the object that was read.

Common Mistakes

A non-serializable superclass without a no-argument constructor. Deserialization fails with InvalidClassException: no valid constructor.

Expecting superclass fields to survive when the superclass isn't serializable. They're reset by its constructor.

Writing a changed object again on the same stream and expecting the new values. Use reset().

Practice

  1. Easy: Make Person in the first example serializable and confirm that "City High" now survives.
  2. Harder: Remove the no-argument constructor from Person (add one with a parameter instead) and observe the exception when reading.

Quick check: Inheritance and references

4 questions
1A superclass isn't Serializable. What happens to its fields?
2An object is referenced twice in a graph. How many times is it written?
3Can serialization handle cycles?
4Which method makes a stream forget objects it has already written?

Key Takeaways

Was this tutorial helpful?

Lesson 373 of 482