UNPKG

epubjs

Version:

Render ePub documents in the browser, across many devices

142 lines (138 loc) 18.4 kB
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <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>