Concepts / Reading Objects with ObjectInputStream

Reading Objects with ObjectInputStream

ObjectOutputStream is optimized to avoid writing the same object more than once; on a second encounter it writes only a reference to the first occurrence.

  • Programming
Interactive lab

Try it: Names and Objects

How Python variables are names bound to objects: assignment never copies, mutating a list is seen through every name that points at it, and ints are replaced rather than changed.

How it works

  1. name = object binds a name; b = a makes b point at the same object.
  2. Mutating a list (append, +=) changes the one shared object.
  3. a = a + [x] and y = y + 1 create NEW objects and re-point one name.
  4. Passing a list to a function passes the reference, so the function can change it.

Default run (7 steps): Two names, one list. Every name is a label that points at an object. … Printed: [1, 2, 3] / True

Simplified: Six fixed scripts on a tiny heap; you choose the values. Object numbers are illustrative, not real id() values.

Educational simulation

Loading the simulation…

A Surprise When You Write Twice

Imagine you write a student object to a stream, change that student's grade, and write the same object again. Most people expect the stream to record the new grade. After all, the stream simply receives an object, so why would it care what the object looked like earlier? The answer is that ObjectOutputStream does not treat every write as a fresh, independent copy of the object's current contents. It has a built-in optimization that changes what happens on a second write, and that optimization is the source of a limitation every stream user must understand.

What do you think happens?

You write a student object with grade 80, change its grade to 95, and write the same object again. When the file is read back, what grade does the reader see?

  • grade 95

  • grade 80

  • the stream raises an error

Reveal answer
Answer:

grade 80

The stream does not write the new contents on the second write. It writes only a reference back to the first occurrence, so the reader gets the first recorded version. The stream gives no error to signal this.

Tracing the First and Second Writes

Follow what happens when the same object is passed to writeObject twice. On the first write, the stream meets an object it has not handled before, so it serializes that object's contents into the output. On the second write, the stream recognizes the object as one it has already handled. It does not serialize the contents again. Instead, it writes only a reference pointing back to the first occurrence. The diagram below traces these two calls and the output each one produces.

ProgramObjectOutputStreamStream output1st writeObject(student)full contents of student2nd writeObject(student)reference to 1st occurrence
When the same object is written a second time, what goes into the stream, and how does it point back to the first copy?

Notice that the second entry in the stream is not a second copy of the student. It is a pointer to the earlier entry. The output stays smaller, and the stream avoids redundant work when the same object appears more than once in what is written.

Why the Stream Remembers Identity, Not Current State

The optimization works on identity. When the stream meets an object it has already handled, it checks whether that is the same object, and if so it does not serialize the contents again. The key consequence is that the stream remembers the object by its earlier occurrence, not by its present state. If the object has changed in the meantime, the reference still points to the first recorded version, and the new data never reaches the stream.

Definition

A back-reference is the short entry an ObjectOutputStream writes, in place of an object's contents, when that same object has already been written earlier in the stream. It points to the first occurrence rather than repeating the data.

Modifying an Object Between Two Writes

The following illustration is generated for teaching purposes. Suppose a class stores a student's name and grade in ordinary, changeable fields. Write one student object, update its grade, and write that same object again. The diagram compares the state before the second write with the state after it, and marks what differs.

Object grade 80Object grade 95Record 1 grade 80Record 2 referenceto record 1Reader sees grade 80setGrade(95)second writeObjectreader follows reference
If the object changes between two writeObject calls, what does the stream hold, and what version does the reader see after deserializing both writes?
Worked example

Predicting the stored version

A student object is created with grade 80 and written to the stream. Its grade is then changed to 95, and the same object is written again. What version does the reader recover for the second write?

  1. 1
    Step 1: first write

    The stream has not seen this object before, so it writes the full contents, including grade 80.

  2. 2
    Step 2: modify the object

    The object in memory now holds grade 95. Nothing is written to the stream at this point, so the stream's record is unchanged.

  3. 3
    Step 3: second write

    The stream recognizes the object from the first write and writes only a reference to that first occurrence. The new grade 95 is never serialized.

  4. 4
    Step 4: reading back

    The reader follows the reference to the first record, so the object it rebuilds carries grade 80.

Result

The reader sees grade 80 for the second write, even though the object in memory had grade 95 when it was written again.

Why Immutable Objects Suit the Stream

Because of the reuse behavior described above, ObjectOutputStreams are meant mainly for use with immutable objects. An immutable object cannot change between writes, so the first recorded version is always the correct version, and reusing a reference to it does no harm. The source's example of an immutable object is the String class, which cannot change after it is created.

An immutable design removes the trap entirely. Because the contents cannot be altered after construction, a cached reference never holds stale state, so the question of a stale copy never arises. A mutable object, by contrast, can be changed after its first write, and its earlier recorded version may then disagree with what the program believes the object is.

Forcing a Full Write with reset()

Sometimes you genuinely need to write a mutable object, and you may write the same object more than once. The source gives a direct remedy: call the stream's reset() method before writing the object. Calling reset() ensures that the full, correct version of the object is written, rather than a reference to an earlier occurrence. The following Java sketch is generated for illustration. It uses a Student class with a setGrade method and an output stream already opened on a file.

java
Notes on the code
  • The first writeObject serializes the full object because the stream has not seen it before.
  • setGrade changes the object in memory only; the stream is not told about the change.
  • reset() makes the second write carry the current contents instead of a back-reference.
  • Without the reset() call, the second writeObject would store only a reference to the grade 80 record.
ProgramObjectOutputStreamStream output1st writeObject grade 80full contents, grade 80reset()2nd writeObject grade 95full contents, grade 95
After calling reset(), how does the second writeObject differ from the one before it, and what is now written in full?

The second write now lands in the stream as complete data, carrying grade 95. The reset() call does not change the object itself. It changes what the stream does on the next write, so the record reflects the object's current state.

Mistakes When Reusing Mutable Objects

The following mistakes come from the same root cause: the stream remembers the first occurrence of an object, not its later state.

  • Common mistake

    Assuming a second writeObject records the object's new contents

    What it looks like

    Write a student with grade 80, change the grade to 95, then write the same student again.

    Why it fails

    On the second write the stream writes only a reference to the first occurrence, so the new grade is not stored.

    Do this instead

    Call reset() before the second write when the object may have changed and must be stored in full.

  • Common mistake

    Expecting an error when the stored data is out of date

    Why it fails

    The stream gives no error to signal that the data it holds is stale, so the problem stays hidden until the data is read back.

    Do this instead

    Treat any re-written mutable object as a possible stale record, and check the reading side against the expected current state.

  • Common mistake

    Relying on the reset() call for an object that is never written twice

    Why it fails

    reset() only matters when an object is encountered again. Calling it without a reason adds noise to the code without changing the output for a single write.

    Do this instead

    Use reset() specifically before rewriting a mutable object that has changed.

Practice: Predict the Stored Version

MEDIUM

A program writes a Student object with grade 70 to an ObjectOutputStream, then sets the grade to 88 and calls writeObject on the same object again with no reset() call. Without running the code, state what grade the reader recovers from the second write and explain why. Then describe the single change that would make the second write store grade 88.

Hints
  • Ask whether the second write stores the contents or a reference.
  • Think about what the stream remembers: identity or current state.
  • The fix is a call that tells the stream to stop reusing the earlier occurrence.

Key Takeaways

  • ObjectOutputStream avoids writing the same object twice. On a second encounter it writes only a reference to the first occurrence.
  • Because the stream remembers an object by identity rather than by current state, changes made between writes are not recorded, and the stream gives no error.
  • ObjectOutputStreams are meant mainly for immutable objects such as String, since their first recorded version is always correct.
  • For a mutable object that may be written more than once, call reset() before writing so the full, current version is stored.