epubjs
Version:
Render ePub documents in the browser, across many devices
142 lines (138 loc) • 18.4 kB
HTML
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Serialization</title><link rel="stylesheet" href="core.css" type="text/css"/><meta name="generator" content="DocBook XSL Stylesheets V1.74.0"/></head><body><div class="sect1" title="Serialization"><div class="titlepage"><div><div><h1 class="title"><a id="learnjava3-CHP-12-SECT-3"/>Serialization</h1></div></div></div><p>Using a <code class="literal">DataOutputStream</code>, you
could write an application that saves the data content of your objects one
at a time as simple types. However, Java provides an even more powerful
mechanism called object serialization that does almost all the work for
you. In its simplest form, <span class="emphasis"><em>object serialization</em></span> is an
automatic way to save and load the state of an object. However, object
serialization has greater depths that we cannot plumb within the scope of
this book, including complete control over the serialization process and
interesting twists such as class versioning.</p><p>Basically, an instance of any class that implements the <a id="I_indexterm12_id760039" class="indexterm"/><code class="literal">Serializable</code> interface
can be saved to and restored from a stream. The stream subclasses,
<a id="I_indexterm12_id760050" class="indexterm"/><code class="literal">ObjectInputStream</code> and
<a id="I_indexterm12_id760061" class="indexterm"/><code class="literal">ObjectOutputStream</code>, are
used to serialize primitive types and objects. Subclasses of <code class="literal">Serializable</code> classes are also serializable. The
default serialization mechanism saves the value of all of the object’s
fields (public and private), except those that are static and those marked
<span class="emphasis"><em>transient</em></span>.</p><p>One of the most important (and tricky) things about serialization is
that when an object is serialized, any object references it contains are
also serialized. Serialization can capture entire “graphs” of
interconnected objects and put them back together on the receiving end
(we’ll demonstrate this in an upcoming example). The implication is that
any object we serialize must contain only references to other <code class="literal">Serializable</code> objects. We can prune the tree and
limit the extent of what is serialized by marking nonserializable
variables as <code class="literal">transient</code> or overriding
the default serialization mechanisms. The <code class="literal">transient</code> modifier can be applied to any
instance variable to indicate that its contents are not useful outside of
the current context and should not be saved.</p><p>In the following example, we create a <code class="literal">Hashtable</code> and write it to a disk file called
<span class="emphasis"><em>hash.ser</em></span>. The <code class="literal">Hashtable</code> object is already serializable because
it implements the <code class="literal">Serializable</code>
interface.</p><a id="I_12_tt804"/><pre class="programlisting"> <code class="kn">import</code> <code class="nn">java.io.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">java.util.*</code><code class="o">;</code>
<code class="kd">public</code> <code class="kd">class</code> <code class="nc">Save</code> <code class="o">{</code>
<code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code><code class="n">String</code><code class="o">[]</code> <code class="n">args</code><code class="o">)</code> <code class="o">{</code>
<code class="n">Hashtable</code> <code class="n">hash</code> <code class="o">=</code> <code class="k">new</code> <code class="n">Hashtable</code><code class="o">();</code>
<code class="n">hash</code><code class="o">.</code><code class="na">put</code><code class="o">(</code><code class="s">"string"</code><code class="o">,</code> <code class="s">"Gabriel Garcia Marquez"</code><code class="o">);</code>
<code class="n">hash</code><code class="o">.</code><code class="na">put</code><code class="o">(</code><code class="s">"int"</code><code class="o">,</code> <code class="k">new</code> <code class="n">Integer</code><code class="o">(</code><code class="mi">26</code><code class="o">));</code>
<code class="n">hash</code><code class="o">.</code><code class="na">put</code><code class="o">(</code><code class="s">"double"</code><code class="o">,</code> <code class="k">new</code> <code class="n">Double</code><code class="o">(</code><code class="n">Math</code><code class="o">.</code><code class="na">PI</code><code class="o">));</code>
<code class="k">try</code> <code class="o">{</code>
<code class="n">FileOutputStream</code> <code class="n">fileOut</code> <code class="o">=</code> <code class="k">new</code> <code class="n">FileOutputStream</code><code class="o">(</code> <code class="s">"hash.ser"</code> <code class="o">);</code>
<code class="n">ObjectOutputStream</code> <code class="n">out</code> <code class="o">=</code> <code class="k">new</code> <code class="n">ObjectOutputStream</code><code class="o">(</code> <code class="n">fileOut</code> <code class="o">);</code>
<code class="n">out</code><code class="o">.</code><code class="na">writeObject</code><code class="o">(</code> <code class="n">hash</code> <code class="o">);</code>
<code class="n">out</code><code class="o">.</code><code class="na">close</code><code class="o">();</code>
<code class="o">}</code>
<code class="k">catch</code> <code class="o">(</code><code class="n">Exception</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code><code class="n">e</code><code class="o">);</code>
<code class="o">}</code>
<code class="o">}</code>
<code class="o">}</code></pre><p>First, we construct a <code class="literal">Hashtable</code>
with a few elements in it. Then, in the lines of code inside the <code class="literal">try</code> block, we write the <code class="literal">Hashtable</code> to a file called
<span class="emphasis"><em>hash.ser</em></span>, using the <a id="I_indexterm12_id760172" class="indexterm"/><code class="literal">writeObject()</code> method of
<code class="literal">ObjectOutputStream</code>. The <code class="literal">ObjectOutputStream</code> class is a lot like the <code class="literal">DataOutputStream</code> class, except that it includes
the powerful <code class="literal">writeObject()</code>method.</p><p>The <code class="literal">Hashtable</code> that we created has
internal references to the items it contains. Thus, these components are
automatically serialized along with the <code class="literal">Hashtable</code>. We’ll see this in the next example
when we deserialize the <code class="literal">Hashtable</code>.</p><a id="I_12_tt805"/><pre class="programlisting"> <code class="kn">import</code> <code class="nn">java.io.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">java.util.*</code><code class="o">;</code>
<code class="kd">public</code> <code class="kd">class</code> <code class="nc">Load</code> <code class="o">{</code>
<code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code><code class="n">String</code><code class="o">[]</code> <code class="n">args</code><code class="o">)</code> <code class="o">{</code>
<code class="k">try</code> <code class="o">{</code>
<code class="n">FileInputStream</code> <code class="n">fileIn</code> <code class="o">=</code> <code class="k">new</code> <code class="n">FileInputStream</code><code class="o">(</code><code class="s">"hash.ser"</code><code class="o">);</code>
<code class="n">ObjectInputStream</code> <code class="n">in</code> <code class="o">=</code> <code class="k">new</code> <code class="n">ObjectInputStream</code><code class="o">(</code><code class="n">fileIn</code><code class="o">);</code>
<code class="n">Hashtable</code> <code class="n">hash</code> <code class="o">=</code> <code class="o">(</code><code class="n">Hashtable</code><code class="o">)</code><code class="n">in</code><code class="o">.</code><code class="na">readObject</code><code class="o">();</code>
<code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code> <code class="n">hash</code><code class="o">.</code><code class="na">toString</code><code class="o">()</code> <code class="o">);</code>
<code class="o">}</code>
<code class="k">catch</code> <code class="o">(</code><code class="n">Exception</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">System</code><code class="o">.</code><code class="na">out</code><code class="o">.</code><code class="na">println</code><code class="o">(</code><code class="n">e</code><code class="o">);</code>
<code class="o">}</code>
<code class="o">}</code>
<code class="o">}</code></pre><p>In this example, we read the <code class="literal">Hashtable</code> from the <span class="emphasis"><em>hash.ser</em></span>
file, using the <code class="literal">readObject()</code> method of
<code class="literal">ObjectInputStream</code>. The <code class="literal">ObjectInputStream</code> class is a lot like <code class="literal">DataInputStream</code>, except that it includes the
<code class="literal">readObject()</code> method. The return type of
<code class="literal">readObject()</code> is <code class="literal">Object</code>, so we need to cast it to a <code class="literal">Hashtable</code>. Finally, we print the contents of the
<code class="literal">Hashtable</code> using its <code class="literal">toString()</code> method.</p><div class="sect2" title="Initialization with readObject()"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-12-SECT-3.1"/>Initialization with readObject()</h2></div></div></div><p><a id="idx10719" class="indexterm"/> <a id="idx10746" class="indexterm"/> <a id="idx10748" class="indexterm"/>Often, simple deserialization alone is not enough to
reconstruct the full state of an object. For example, the object may
have had transient fields representing state that could not be
serialized, such as network connections, event registration, or decoded
image data. Objects have an opportunity to do their own setup after
deserialization by implementing a special method named <code class="literal">readObject()</code>.</p><p>Not to be confused with the <code class="literal">readObject()</code> method of the <code class="literal">ObjectInputStream</code>, this method is implemented
by the serializable object itself. To be recognized and used, the
<code class="literal">readObject()</code> method must have a
specific signature, and it must be private. The following snippet is
taken from an animated JavaBean that we’ll talk about in <a class="xref" href="ch22.html" title="Chapter 22. JavaBeans">Chapter 22</a>:</p><a id="I_12_tt806"/><pre class="programlisting"> <code class="kd">private</code> <code class="kt">void</code> <code class="nf">readObject</code><code class="o">(</code><code class="n">ObjectInputStream</code> <code class="n">s</code><code class="o">)</code>
<code class="kd">throws</code> <code class="n">IOException</code><code class="o">,</code> <code class="n">ClassNotFoundException</code>
<code class="o">{</code>
<code class="n">s</code><code class="o">.</code><code class="na">defaultReadObject</code><code class="o">();</code>
<code class="n">initialize</code><code class="o">();</code>
<code class="k">if</code> <code class="o">(</code> <code class="n">isRunning</code> <code class="o">)</code>
<code class="n">start</code><code class="o">();</code>
<code class="o">}</code></pre><p>When the <code class="literal">readObject()</code> method
with this signature exists in an object, it is called during the
deserialization process. The argument to the method is the <code class="literal">ObjectInputStream</code> doing the object
construction. We delegate to its <code class="literal">defaultReadObject()</code> method to do the normal
deserialization from the stream and then do our custom setup. In this
case, we call one of our methods named <code class="literal">initialize()</code> and, depending on our state, a
method called <code class="literal">start()</code>.</p><p>Using a custom implementation of <code class="literal">readObject()</code> and a corresponding <code class="literal">writeObject()</code> method, we could take complete
control of the serialized form of the object by reading and writing to
the stream using lower-level write operations (bytes, strings, etc.)
instead of delegating to the default implementation as we did
before.</p><p>We’ll talk a little more about serialization in <a class="xref" href="ch22.html" title="Chapter 22. JavaBeans">Chapter 22</a> when we discuss JavaBeans.<a id="I_indexterm12_id760459" class="indexterm"/><a id="I_indexterm12_id760466" class="indexterm"/><a id="I_indexterm12_id760474" class="indexterm"/></p></div><div class="sect2" title="SerialVersionUID"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-12-SECT-3.2"/>SerialVersionUID</h2></div></div></div><p><a id="idx10720" class="indexterm"/> <a id="idx10749" class="indexterm"/>Java object serialization was designed to accommodate
certain kinds of <span class="emphasis"><em>compatible class changes</em></span> or
evolution in the structure of classes. For example, changing the methods
of a class does not necessarily mean that its serialized representation
must change because only the data of variables is stored. Nor would
simply adding a new field to a class necessarily prohibit us from
loading an old serialized version of the class. We could simply allow
the new variable to take its default value. By default, however, Java is
very picky and errs on the side of caution. If you make any kind of
change to the structure of your class, by default you’ll get an <code class="literal">InvalidClassException</code> when trying to read
previously serialized forms of the class.</p><p>Java detects these versions by performing a hash function on the
structure of the class and storing a 64-bit value called the
<span class="emphasis"><em>Serial Version UID</em></span> (SUID), along with the
serialized data. It can then compare the hash to the class when it is
loaded.</p><p>Java allows us to take control of this process by looking for a
special, magic field in our classes that looks like the
following:</p><a id="I_12_tt807"/><pre class="programlisting"> <code class="kd">static</code> <code class="kd">final</code> <code class="kt">long</code> <code class="n">serialVersionUID</code> <code class="o">=</code> <code class="o">-</code><code class="mi">6849794470754667710L</code><code class="o">;</code></pre><p>(The value is, of course, different for every class.) If it finds
this static <code class="literal">serialVersionUID long</code>
field in the class, it uses its value instead of performing the hash on
the class. This value will be written out with serialized versions of
the class and used for comparison when they are deserialized. This means
that we are now in control of which versions of the class are compatible
with which serialized representations. For example, we can create our
serializable class from the beginning with our own SUID and then only
increment it if we make a truly incompatible change and want to prevent
older forms of the class from being loaded:</p><a id="I_12_tt808"/><pre class="programlisting"> <code class="kd">class</code> <code class="nc">MyDataObject</code> <code class="kd">implements</code> <code class="n">Serializable</code> <code class="o">{</code>
<code class="kd">static</code> <code class="kd">final</code> <code class="kt">long</code> <code class="n">serialVersionUID</code> <code class="o">=</code> <code class="mi">1</code><code class="o">;</code> <code class="c1">// Version 1</code>
<code class="o">...</code>
<code class="o">}</code></pre><p>A utility called <a id="I_indexterm12_id760582" class="indexterm"/><span class="emphasis"><em>serialver</em></span> that comes with the JDK
allows you to calculate the hash that Java would otherwise use for the
class. This is necessary if you did not plan ahead and already have
serialized objects stored and need to modify the class afterward.
Running the <span class="emphasis"><em>serialver</em></span> command on the class displays
the SUID that is necessary to match the value already stored:</p><a id="I_12_tt809"/><pre class="programlisting"> <code class="o">%</code> <strong class="userinput"><code><code class="n">serialver</code> <code class="n">SomeObject</code></code></strong>
<code class="err"> </code>
<code class="kd">static</code> <code class="kd">final</code> <code class="kt">long</code> <code class="n">serialVersionUID</code> <code class="o">=</code> <code class="o">-</code><code class="mi">6849794470754667710L</code><code class="o">;</code></pre><p>By placing this value into your class, you can “freeze” the SUID
at the specified value, allowing the class to change without affecting
versioning.<a id="I_indexterm12_id760617" class="indexterm"/><a id="I_indexterm12_id760624" class="indexterm"/></p></div></div></body></html>