Record Serialization - Update

Chris Hegarty chris.hegarty at oracle.com
Fri Aug 23 14:39:54 UTC 2019


As a follow up to the message posted on amber-spec-experts [1], it turns
out that supporting record serialization directly in the serialization
runtime is relatively straight forward ( see draft specification changes
below (*) ), and provides seamless migration from record-like classes to
records ( and vica versa ). This is somewhat similar(ish) to how enums
are supported, but without any change to the protocol format.

Summary:
  - For a record to be serializable, it must `implements Serializable`.
  - The serial form of a record is it's state components. It is not
    customizable (*).
  - Serialization will only construct a record through its canonical
    constructor ( no other reflection / unsafe magic ).
  - The record components are, obviously, deserialized prior to the
    constructor being invoked.
  - Serialization will ignore pretty much all of the magic methods, if
    they appear in a record (**).
  - Record-like class to record migration is supported (and vice versa),
    since the stream format is not changed.
  - No need for "best match" constructor, just invoke the canonical
    constructor with the appropriate stream field values, or the the
    default value of the component's declared type if absent from the
    stream.
  - Apart from helpful warnings/errors, javac can almost forget about
    Serialization.

Minimal specification changes are required to ObjectInputStream and
ObjectOutputStream ( there will of course be equivalent wording in the
separate Serialization spec [2].

(*)
--- a/src/java.base/share/classes/java/io/ObjectOutputStream.java
+++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java
@@ -139,6 +139,18 @@
  * serialVersionUID field declarations are also ignored--all enum types have a
  * fixed serialVersionUID of 0L.
  *
+ * <p> When Preview Features are enabled...
+ * Record objects are serialized differently than ordinary serializable or
+ * externalizable objects.  The serialized form of a record object is its state
+ * components ( in the same format as that of an ordinary object ). Like other
+ * serializable or externalizable objects, record objects can function as the
+ * targets of back references appearing subsequently in the serialization stream.
+ * The process by which record objects are serialized cannot be customized; any
+ * class-specific writeObject, writeReplace, and writeExternal, methods defined
+ * by record classes are ignored during serialization. Similarly, a
+ * serialPersistentFields field declaration is also ignored -- all record
+ * classes have a fixed serial form. The serialVersionUID may be set.
+ *
  * <p>Primitive data, excluding serializable fields and externalizable data, is
  * written to the ObjectOutputStream in block-data records. A block data record
  * is composed of a header and data. The block data header consists of a marker

--- a/src/java.base/share/classes/java/io/ObjectInputStream.java
+++ b/src/java.base/share/classes/java/io/ObjectInputStream.java
  * Similarly, any serialPersistentFields or serialVersionUID field declarations
  * are also ignored--all enum types have a fixed serialVersionUID of 0L.
  *
+ * <p> When Preview Features are enabled...
+ * Record objects are deserialized differently than ordinary serializable or
+ * externalizable objects. The serialized form of a record object is its state
+ * components ( in the same format as that of an ordinary object ). During
+ * deserialization, if the local class equivalent of the specified stream class
+ * descriptor {@linkplain Class#isRecord() is a record}, ObjectInputStream reads
+ * the record's state components from the stream; the record object is then
+ * constructed by invoking its <i>canonical constructor</i> passing the state
+ * components ( or the default value of the component's declared type if absent
+ * from the stream ).  Like other serializable or externalizable objects, record
+ * objects can function as the targets of back references appearing subsequently
+ * in the serialization stream ( but there are clearly some limitations on the
+ * nature of the structures that can be represented, by virtue of the fact that
+ * the state components are deserialized prior to the invocation of the record
+ * constructor).  The process by which record objects are deserialized cannot
+ * be customized: any class-specific readObject, readObjectNoData, readResolve,
+ * and readExternal, methods defined by record classes are ignored during
+ * deserialization. Similarly, a serialPersistentFields field declaration is
+ * also ignored -- all record classes have a fixed serial form, that is their
+ * state components. The serialVersionUID may be set.
+ *

Wording will need to be agreed around how best to have the Serialization
specification refer to records as a Preview Feature.

(**) we want to allow `writeReplace` as an escape hatch, in case for
example, a non-serializable type ends up as a component in a
serializable record, or maybe if one wants to replace the serializable
record with a proxy that, say, refers to a singleton value, or similar.

-Chris.

[1] https://mail.openjdk.java.net/pipermail/amber-spec-experts/2019-July/001459.html
[2] https://docs.oracle.com/en/java/javase/12/docs/specs/serialization/index.html




More information about the amber-spec-experts mailing list