Memra

Serialization & deserialization

◈ 7 cards

Serializable, ObjectOutput/InputStream, transient, serialVersionUID, the object graph.

Persisting a whole object

Serialization turns a live object — and everything it references — into a byte stream you can write to a file or send over a socket. Deserialization reverses it: read the bytes back and reconstruct an equal object graph. This is how the Greenhouse project implements save and restore: when a fault occurs it serializes the entire GreenhouseControls object to dump.out, and later the Restore program reads dump.out back to resume exactly where it left off.

The four pieces

  1. implements Serializable — a marker interface (no methods). It is permission: "the JVM may serialize instances of this class." Without it, trying to serialize throws NotSerializableException.
  2. ObjectOutputStream.writeObject(obj) — writes the object graph.
  3. ObjectInputStream.readObject() — reads it back; returns Object, so you cast to the real type.
  4. The object graphwriteObject follows every reference and serializes the whole reachable graph (and de-duplicates shared objects), so each referenced object must itself be Serializable.

### Writing

GreenhouseControls gc = new GreenhouseControls();
try (ObjectOutputStream out =
         new ObjectOutputStream(new FileOutputStream("dump.out"))) {
    out.writeObject(gc);   // whole object graph -> bytes
}

### Reading back

try (ObjectInputStream in =
         new ObjectInputStream(new FileInputStream("dump.out"))) {
    GreenhouseControls gc = (GreenhouseControls) in.readObject();
}

readObject() does not call the class's constructor — it rebuilds the object's fields directly from the stream. (Any logic you keep only in a constructor, like a timer initialized from the clock, will not re-run on restore.)

transient: fields you do NOT save

Mark a field transient to exclude it from serialization. Use it for (a) data that should not persist (a password, a cache), and (b) fields whose type is not serializable. On deserialization a transient field comes back as its default (0, false, null).

private String name;             // serialized
private transient Socket conn;   // skipped; comes back null

serialVersionUID: the version stamp

Each serializable class carries a serialVersionUID — a version number written into the stream. On read, the JVM compares the stream's UID to the loaded class's UID; a mismatch throws InvalidClassException. If you do not declare one, the compiler computes it from the class's exact shape, so any change (add a field, change a signature) silently breaks old data. Declare it explicitly so you control compatibility:

private static final long serialVersionUID = 1L;
passed toFileOutputStreamFileInputStreamreturns ObjectGreenhouseControls gclive object graphwriteObject(gc)ObjectOutputStreamdump.outbytes on diskreadObject()ObjectInputStream(GreenhouseControls)ctor NOT called
One object out, one object back. The two <code>Object…Stream</code> decorators do the encoding; the <code>File…Stream</code> underneath them moves the bytes. The last step is where the classic surprise lives — the restored object never ran a constructor.
fieldwritten?after readObject()private String nameyesthe value that was savedprivate transient Socketconnnonull (the type default)any static fieldnowhatever the class holdsnownon-Serializable, nottransientwrite failsNotSerializableExceptionA UID mismatch on read throws InvalidClassException.
Only non-<code>transient</code>, non-<code>static</code> instance fields travel. A field of a non-serializable type is the dangerous case: it does not quietly vanish, it kills the whole save.
NORMAL ~/memra/learn/comp-308/serialization utf-8 LF