星期一, 二月 23, 2004

Java 序列化(Serializeble) 说明.

Java: Serializable
(how to save instances)

Object Serialization.
Java provides a feature called object serialization that allows you take any object that implements the Serializable interface and turns it into a sequence of bytes that can later be fully restored into the original object.

"Objects are serialized with the ObjectOutputStream and they are deserialized with the ObjectInputStream. Both of these classes are part of the java.io package, and they function, in many ways, like DataOutputStream and DataInputStream because they define the same methods for writing and reading binary representations of Java primitive types to and from streams. What ObjectOutputStream and ObjectInputStream add, however, is the ability to write and read non-primitive object and array values to and from a stream." Java in a Nutshell



The ObjectInput and ObjectOutput interfaces are the basis for serialization within Java. The ObjectInputStream and ObjectOutputStream classes, respectively, implement the interfaces.

What Objects need to be serialized?
The basic idea is to save Instance information. Why Instances?

Classes are defined and "saved" in your .java files. However, for instances, they disappear when you exit the application. One might want to save Instance information.

What information might this be?

Well, it won't be methods, since methods are in Classes, not instances. Instances hold Instance Variables and can change Class Variables. Thus, static variables in an Instance can be a problem.. more later

To get an idea of what serialization is doing, let's consider more about the nature of objects. From, "Developing Java Beans" by Robert Englander.

"Most components maintain information that defines their appearance and behavior. This information is known as the state of the object. Some of this information is represented by the object's properties. For instance, the font or color properties of a visual component are usually considered to be part of that object's state. There may also be internal data used by an object that is not exposed as properties, but plays a part in defining the behavior of the object nevertheless.

...The state information of all the components, as well as the application or applet itself, must be saved on a persistent storage medium so that it can be used to recreate the overall application state at run-time. An important aspect of the application state is the definition of the components themselves: the persistent state of an application includes a description of the components being used, as well their collective state."

Note that we use ObjectStreams to save objects. You can only read and write objects not numbers. To write and read numbers, you use methods such as writeInt/readInt or writeDouble/readDouble. (The objectstream classes implement the DataInput/DataOutput interfaces.) (more later on why you would want to do this)

Of course, numbers inside objects (IVs) are saved and stored automatically (discussion on CV storage being static later).


When an object is saved all of its state is saved. This means that all handles and objects that the saved object refers to are saved.In its simplest application the programmer includes the phrase implements java.io.Serializable in the class definition as shown in the following example from "Developing Java Beans."

public class SimpleExample implements java.io.Serializable
{
protected int anInteger;
protected float aFloat;
protected java.awt.Button aButton;

public SimpleExample()
{
}
}

note implements java.io.Serializable could be implements Serializable but need to import java.io.*

In this example there are three data members. The first two, anInteger and aFloat are primitive data types and are therefore serializable. The third, aButton is an instance of type java.awt.Button, a subclass of java.awt.Component which itself implements java.io.Serializable. Therefore the class SimpleExample can be serialized without doing anything more than declaring that it implements java.io.Serializable.

Only classes that implement the Serializable or Externalizable interface can be written to or read from an object stream. Serializable is a marker interface - it doesn't define any methods and serves only to specify whether an object is allowed to be serialized (look in the API ... it is empty!). Read what it says about the readObject and writeObject methods. The Externalizable interface (which extends Serializable) does define methods and is used by objects that want advanced control.

Below is the save() method in the example 8.1 in Java in a Nutshell 2. Note the creation of the ObjectOutputStream and the use of writeObject(). Since it does not implement the writeObject method, it needs to instantiate the ObjectOutputStream explicitly. It is only saving one Object (specificially lines.) (See Java in a Nutshell, page 173 http://www.ecst.csuchico.edu/~amk/foo/javanut2/ch08/ScribbleFrame.java)


/**
* Prompt the user for a filename, and save the scribble in that file.
* Serialize the vector of lines with an ObjectOutputStream.
* Compress the serialized objects with a GZIPOutputStream.
* Write the compressed, serialized data to a file with a FileOutputStream.
* Don't forget to flush and close the stream.
*/
public void save() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Save Scribble", FileDialog.SAVE);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create the necessary output streams to save the scribble.
FileOutputStream fos = new FileOutputStream(filename);
// Save to file
GZIPOutputStream gzos = new GZIPOutputStream(fos);
// Compressed
ObjectOutputStream out = new ObjectOutputStream(gzos);
// Save objects
out.writeObject(lines); // Write the entire Vector of scribbles
out.flush(); // Always flush the output.
out.close(); // And close the stream.
}
// Print out exceptions. We should really display them in a dialog...
catch (IOException e) { System.out.println(e); }
}
}
/**
* Prompt for a filename, and load a scribble from that file.
* Read compressed, serialized data with a FileInputStream.
* Uncompress that data with a GZIPInputStream.
* Deserialize the vector of lines with a ObjectInputStream.
* Replace current data with new data, and redraw everything.
*/
public void load() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Load Scribble", FileDialog.LOAD);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create necessary input streams
FileInputStream fis = new FileInputStream(filename); // Read from
file
GZIPInputStream gzis = new GZIPInputStream(fis); // Uncompress
ObjectInputStream in = new ObjectInputStream(gzis); // Read objects
// Read in an object. It should be a vector of scribbles
Vector newlines = (Vector)in.readObject();
in.close(); // Close the stream.
lines = newlines; // Set the Vector of lines.
repaint(); // And redisplay the scribble.
}
// Print out exceptions. We should really display them in a dialog...
catch (Exception e) { System.out.println(e); }
}
}


Static or transient data
However, this "ease" is not true in all cases. As we shall see, serialization is not so easily applied to classes with static or transient data members.
Only data associated with a specific instance of a class is serialized, therefore static data, that is, data associated with a class as opposed to an instance, is not serialized automatically. To serialize data stored in a static variable one must provide class-specific serialization.

Similarly, some classes may define data members to use as scratch variables. Serializing these data members may be unnecessary. Some examples of transient data include runtime statistics or hash table mapping references. These data should be marked with the transient modifier to avoid serialization. Transient, by definition, is used to designate data members that the programmer does not want or need to be serialized. See Java in a Nutshell, page 174: mouse position, preferred size, file handles (machine specific (native code)).

When writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later. (when writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later)

To serialize an object, you create some sort of OutputStream object and then wrap it inside an ObjectOutputStream object. At this point you only need to call writeObject() and your object is magically serialized and sent to the OutputStream. To reverse the process, you wrap an InputStream inside an ObjectInputStream and call readObject(). What comes back is, as usual, a handle to an upcast Object, so you must downcast to set things straight.


If you need to dynamically query the type of the object, you can use the getClass method. Specifically dk.getClass.getName() returns the name of the class that dk is an instance of. I.e., this asks the object for the name of its corresponding class object. (Hmmm, True, but what about syntax? I still need to know what it is to declare it...too bad) (C++ can do this in one operation (dynamic_cast (gives null if wrong type)), java can use instanceof operator to check if it is what I think (see Core Java, Ch5 Inheritence, Casting section)

Custom Serialization
Note: a class can define custom serialization and deserialization behavior for its objects by implementing writeObject() and readObject() methods. These methods are not defined by any interface. The methods must be declared private (rather surprising since they are called from outside of the class during serialization and deserialization.)
The following example illustrates the serialization process. It is not necessarily what you need to do unless you need to customize; it is what is automatically done for an instances IVs


// save a string and double to the stream
String str = "Sample";
double d = 3.14;
FileOutputStream f = new FileOutputStream ("Beans.tmp")
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(str);
s.writeDouble(d);
s.flush();

// restore the string and double
FileInputStream f = new FileInputStream("Beans.tmp");
ObjectInputStream s = new ObjectInputStream(f);
String str = (String)s.readObject();
double d = s.readDouble();

Note the order: when reading back keep track of the number of objects, their order and their type. In Java, remember that strings and arrays are objects and can, therefore, be restored with the writeObject/readObject methods. (Why need to do this if they are objects? why not automatic? ... IS automatic if the object is serializable and it is an IV of an instance that is serializable... and it is not static or transient)

Another example: (This is Java in a Nutshell, page 175) (show use of transient data)


// This example is from the book "Java in a Nutshell, Second Edition".
// Written by David Flanagan. Copyright (c) 1997 O'Reilly & Associates.
// You may distribute this source code for non-commercial purposes only.
// You may study, modify, and use this example for any purpose, as long
// as this notice is retained. Note that this example is provided "as is",
// WITHOUT WARRANTY of any kind either expressed or implied.

import java.io.*;

/** A simple class that implements a growable array or ints, and knows
* how to serialize itself as efficiently as a non-growable array. */
public class IntList implements Serializable
{
private int[] nums = new int[8]; // An array to store the numbers.
private transient int size = 0; // Index of next unused element of nums[].

/** Return an element of the array */
public int elementAt(int index) throws ArrayIndexOutOfBoundsException
{
if (index >= size) throw new ArrayIndexOutOfBoundsException(index);
else return nums[index];
}

/** Add an int to the array, growing the array if necessary */
public void add(int x) {
if (nums.length == size) resize(nums.length*2); // Grow array, if needed.
nums[size++] = x; // Store the int in it.
}

/** An internal method to change the allocated size of the array */
protected void resize(int newsize) {
int[] oldnums = nums;
nums = new int[newsize]; // Create a new array.
System.arraycopy(oldnums, 0, nums, 0, size); // Copy array elements.
}

/** Get rid of unused array elements before serializing the array */
private void writeObject(ObjectOutputStream out) throws IOException {
if (nums.length > size) resize(size); // Compact the array.
out.defaultWriteObject(); // Then write it out normally.
}

/** Compute the transient size field after deserializing the array */
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Read the array normally.
size = nums.length; // Restore the transient field.
}
}

What Objects need to implement Serializable?
Component implements Serializable, so all AWT components can be serialized. If a Class is serializable, all of its subclasses are. Otherwise, you need to make your classes implement serializable.

Since in a given application, one probably does not inherit most classes (other than the AWT stuff) you often need to do this explicitly.

Why not serialize everything?

Security. A knowledgable hacker could exploit information and modify object files.
Safeguards: validation, authentication, and/or security measures for keeping private variables "unsaved". (see Core Java V2)
Consider IO, locations of files change depending on the system you are running on. If you want to keep your java classes cross-platform compliant, you should not store things that are machine specific.
Some things can't be serialized. See this example
If you use someone else's serialized classes, and depend on their serialization for yours, if their version ever changes, then your serialized code may not work anymore. I had a nasty example of this with some student's code that used Swing classes that were "provided" serializable. When a given JDK would change anything in Swing, then any serialized code that was built from the previous version would not work. Same source from my end, but the provided java classes changed.
This lesson is a good one. Do not serialize everything but rather make certain things transient and rebuild them when you retrieve the serialized object. Most GUIs would follow this rule
Back to serializing...
So, each Class that you have Instances that will need to be saved should implement Serializable and , possibly have custom readObject() and writeObject() methods.

If a class does not implement the method, the default serialization provided by defaultReadObject() will be used. In custom methods, call the default first.
(The default methods may be called only from a class's read/writeObject methods. If it is called from outside the writeObject method (for example), the NotActiveException is thrown)

(What does this readObject() probably do? Upon (1) reading the object type, (2) instantiates the (blank) object and (3) sets it variables to the values saved)

If you write a save() method for the top Object, then as Java tries to serialize it, it accesses its variables - which are possibly objects that need to be serialized, and performs this recursively. From specs "The writeObject method serializes the specified object and traverses its references to other objects in the object graph recursively to create a complete serialized represetnation of the graph."

As far as subclasses (from Englander): "When an object is serialized, the highest serializable class in its derivation hierachy is located and serialized first. Then the hierachy is walked, with each subclass being serialized in turn."

Specifically, readObject and writeObject methods only need to save and load their data fields; they should not concern themselves with superclass data or any other class information. (except changes to static variables)

Example 1-4 in Core Java (show how saved) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectFileTest.java On the SUNs at Chico (at least on Expert), the corejava package is at /opt/java/corejava, this would need to be in your CLASSPATH for this code (and other code from the CoreJava book) to run

/*
* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/

import java.io.*;
import corejava.Day;

class ObjectFileTest
{ public static void main(String[] args)
{ try
{ Employee[] staff = new Employee[3];

staff[0] = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15));
staff[2] = new Employee("Tony Tester", 38000,
new Day(1990,3,15));


ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test1.dat"));
out.writeObject(staff);
out.close();

ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test1.dat"));
Employee[] newStaff = (Employee[])in.readObject();

int i;
for (i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ System.out.print("Error: " + e);
System.exit(1);
}
}
}

class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}

public Employee() {}

public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}

public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}

public int hireYear()
{ return hireDay.getYear();
}

private String name;
private double salary;
private Day hireDay;
}

class Manager extends Employee
{ public Manager(String n, double s, Day d)
{ super(n, s, d);
secretaryName = "";
}

public Manager() {}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}

public void setSecretaryName(String n)
{ secretaryName = n;
}

public String getSecretaryName()
{ return secretaryName;
}

private String secretaryName;
}


Keep in mind that objects may contain references to their variables, not separate copies.

Specifically, consider the example below. Two managers can share the same secretary. One does not want to save three copies of Harry. (One wants to maintain consistent data and not worry about editing numerous copies.)


Employee harry = new Employee("Harry Hacker", ...);
Manager carl = new Manager("Carl Cracker", ...);
carl.setSecretary(harry);
Manager tony = new Manager("Tony Tester", ...);
tony.setSecretary(harry);

See Core Java, chapter on Streams and Files: Object Streams Fig.5, Fig.6, Fig.7, Fig.8
Thus the term serialization...


All objects that are saved are given a serial number (1,2,3...)

When saving an object to disk, find out if the object has already been stored

If it has been stored previously, then reference the serial number. If not, store all its data.
Example 1-5 in Core Java V2 (show how saved hierarchically) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectRefTest.java

* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/

/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/

import java.io.*;
import java.util.*;
import corejava.Day;

class ObjectRefTest
{ public static void main(String[] args)
{ try
{
Employee[] staff = new Employee[3];

Employee harry = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[0] = harry;
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15), harry);
staff[2] = new Manager("Tony Tester", 38000,
new Day(1990,3,15), harry);

ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test2.dat"));
out.writeObject(staff);
out.close();

ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test2.dat"));
Employee[] newStaff = (Employee[])in.readObject();

for (int i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (int i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)

{ e.printStackTrace();
System.exit(1);
}
}
}

class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}

public Employee() {}

public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}

public int hireYear()
{ return hireDay.getYear();
}

public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}

private String name;
private double salary;
private Day hireDay;
}

class Manager extends Employee
{ public Manager(String n, double s, Day d, Employee e)
{ super(n, s, d);
secretary = e;
}

public Manager() {}

public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}

public void print()
{ super.print();
System.out.print("Secretary: ");
if (secretary != null) secretary.print();
}

private Employee secretary;
}

Remember, objects contain references to its IV objects, not separate copies of objects. We want the object layout on disk to be exactly like the object layout in memory. This is persistance . Java achieves persistance through serialization .

In general:


the object stream output contains the types and data fields of all objects

each object is assigned a serial number

repeated occurences of the same object are stored as references to that serial number.
About Class Variables (static).

The problem here is when one wants to dynamically change static variables. Since these are defined in the class, when the class recreates the saved instance, it would put the old value for the static variable there.

One needs to customize (as above) to restore this information. Beware that it is an instance that is trying to save this new class variable. Specifically, if an object1 refers to a class (static) attribute of another class and object1 is to be serialized, to accurately save object1 the static attribute from the referenced class would also have to be saved and any state associated with that static attribute. However, if the referenced class is not serializable then the object1 should throw a NotSerializableException.


--------------------------------------------------------------------------------
Cautions:
While the model used for serialization is very simple, it has some drawbacks.

First, it's not as simple as marking serializable classes with the Serializable interface. It is possible for an object that can't be serialized to implement Serializable (either directly or by inheritance).

Ultimately, serialization has to do with the data members of the class, not the methods it contains; after all, Serializable is an empty interface and doesn't require you to implement any methods.

A class is serializable if, and only if, it has only members that are serializable--specifically: no static, transient members. By default, static and transient members are ignored when an object is serialized.

Generally speaking, classes that belong to the standard Java distribution are serializable unless serializing an object of that class would be a security risk. The problem is that there are many standard classes that would present security risks if serialized--for example, a FileInputStream can't be serialized, because when it is deserialized at a later time (and possibly on a different machine), you have an object that references some file handle that may no longer be meaningful, or that may point to a different file than it did originally.

You should make it a practice to check the class of any data members you add to a serializable class to make sure that data members can be serialized also. Don't make any assumptions; just look it up in the documentation.

Stating that a class implements Serializable is essentially a promise that the class can be successfully saved and restored using the serialization mechanism. The problem is that any subclass of that class automatically implements Serializable via inheritance, even if it adds some non-serializable members. Java throws a NotSerializableException (from the java.io package) if you try to save or restore a non-serializable object.

When you are writing Beans (or any class that you may want to serialize), you have to think carefully about what the class contains, and you also have to think about how the class will be used.

You can redesign almost any class so that it is serializable, but this redesign may have implications for the interface between your class and the rest of the world. Ultimately, that's the trick with object serialization. It's not as simple as marking a few classes Serializable; it has real implication for how you write code.


Versioning
The idea: you have a class and you have serialized objects made from this class. Now the class changes (you have a new version). What happens when you try to load old instance information to a newly created instance (from a newer class version)?
"When an object is serialized, some information about its class must obviously be serialized with it, so that the correct class file can be loaded when the object is deserialized. This information about the class is represented by the java.io.ObjectStreamClass class. It contains the fully-qualified name of the class and a version number. The version number is very important because an early version of a class may not be able to deserialize a serialized instance created by a later version of the same class." Java in a Nutshell, page 175

Core JavaV2, (In the 1.2 Core Java text, this information is in the Volume1) discusses what the files that are saved during serialization actually look like. Note (2) of the class description: "the serial version unique ID , which is a fingerprint of the data field types and method signatures".

Java gets this fingerprint by using their Secure Hash Algorithm SHA on the data of the class.

When a class definition changes in any way, so does this SHA fingerprint. So the idea is when you start serializing instances of a class, you should identify the fingerprint of the current version of the class.

See SUNs Stream Unique Identifiers page to see what all is in this fingerprint

To get the SHA fingerprint:

Do SHA to get the fingerprint (see Core Java about the use of serialver (a standalone program that generates) these numbers).
Once generated, put it as a definition in the class and later versions that will not break serialization compatibility
"breaking" serializations produces exceptions like:
java.io.InvalidClassException: Person; local class incompatible:
stream classdesc serialVersionUID = -2832314155938395448,
local class serialVersionUID = 480295508009809219


Situations:

if only the methods change, no problem
Program using version 1 objects: if data fields from version 1 are less than version 2, created and set to default values for type Fig.10
Program using version 1 objects: if data fields from version 1 are greater than version 2, ignore extra Fig.11
If you make larger changes that break serialization (2 and 3 above) compatibility, run serialver again to generate an updated version number. "It is up to the class designer to implement additional code in the readObject method to fix version incompatibilities or to make sure the methods are robust enough to handle null data." CoreJavaV2 pg.66 ( null is the default for un-instantiated objects)


--------------------------------------------------------------------------------
Do we get it? For a final overview see Serialization part of Bean tutorial
--------------------------------------------------------------------------------

To allow someone to check to see if the serialization process works, I have included an example demonstration. You should look at this to see a good way to allow a user to load and save materials. Hint: This could be useful for lab 1. Next, we will make use of a class that uses introspection techniques and also has a hash table. Since hash tables are usually set as transient I also have this example available to show how it was serialized. We will look at it in the next set of notes.

--------------------------------------------------------------------------------
There is obviously a lot more to Serializable. For further reference try (the online book I have mentioned) "Thinking in Java" and "Java in a Nutshell", for code and "Developing Java Beans" by Robert Englander published by O'Reilly (ISBN: 1-56592-289-1). The SUN web site to visit would be: at http://java.sun.com/j2se/1.4.2/docs/guide/serialization and specifically the Specs at http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/serialTOC.html
non-trivial