CS 696 Emerging Technologies: Java Distributed Computing Spring Semester, 1999 Serialization |
||
---|---|---|
© 1999, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 23-Feb-99 |
Marshaling
Marshaling is process of encoding object to put them on the wire (network)
Unmarshaling is the process of decoding from the wire and placing object in the address space
RMI uses serialization (deserialization) to perform marshaling (Unmarshaling)
All parameters passed by value between client and server are marshaled/unmarshaled
Serialization
Serialization allows objects to be converted to a sequence of bytes
The sequence of bytes can be stored in a file, database, sent to a remote machine etc.
The sequence of bytes can be used to recreate the original object
Serializing
Making an Object Serializable
The object's class must implement the interface
import java.io.Serializable; class Roger implements Serializable { public int lowBid; public Roger(int lowBid ) { this.lowBid = lowBid; } public String toString() { return " " + lowBid; } }
Serializing and Deserializing Objects
The writeObject method of ObjectOutputStream serializes objects
The readObject method of ObjectInputStream deserializes objects
Example import java.io.*;
class SerializeDeserialize { public static void main( String args[] ) throws IOException { serialize(); deserialize(); } public static void serialize() throws Exception { OutputStream outputFile = new FileOutputStream( "Serialized" ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( new Roger( 12)); cout.close(); } public static void deserialize() throws Exception { InputStream inputFile = new FileInputStream( "Serialized" ); ObjectInputStream cin = new ObjectInputStream( inputFile ); Roger test = (Roger) cin.readObject(); System.out.println( test.toString() ); } }Output 12
The Serialized file
0000000 254 355 \0 005 s r \0 005 R o g e r 212 217 260 126355 000005 071562 000005 051157 063545 071212 107660 0000020 262 c 0 ; 255 002 \0 001 I \0 006 l o w B i 131143 030073 126402 000001 044400 003154 067567 041151 0000040 d x p \0 \0 \0 \f 062170 070000 000000 006000 0000047The serialized file is stored in binary. Above is the contents of the file created using JDK 1.2. The file is viewed using the UNIX program "od". You can decode the above file if you read chapter 6 in Java Object Serialization Specification.
Some Rules A Serializable class must:
ObjectOutputStream
Methods
annotateClass(Class)
|
writeByte(int)
|
close()
|
writeBytes(String)
|
defaultWriteObject()
|
writeChar(int)
|
drain()
|
writeChars(String)
|
enableReplaceObject(boolean)
|
writeDouble(double)
|
flush()
|
writeFields() |
putFields() |
writeFloat(float)
|
replaceObject(Object)
|
writeInt(int)
|
reset()
|
writeLong(long)
|
useProtocolVersion() |
writeObject(Object)
|
write(byte[])
|
writeObjectOverride() |
write(byte[],
int, int)
|
writeShort(int)
|
write(int)
|
writeStreamHeader()
|
writeBoolean(boolean)
|
writeUTF(String)
|
public final void writeObject(Object obj) throws IOException
ObjectInputStream
Methods
available()
|
readInt()
|
close()
|
readLine()
deprecated
|
defaultReadObject()
|
readLong()
|
enableResolveObject()
|
readObject()
|
read()
|
readObjectOverride() |
read(byte[],
int, int)
|
readShort()
|
readBoolean()
|
readStreamHeader()
|
readByte()
|
readUnsignedByte()
|
readChar()
|
readUnsignedShort()
|
readDouble()
|
readUTF()
|
readFields() |
registerValidation()
|
readFloat()
|
resolveClass()
|
readFully(byte[])
|
resolveObject()
|
readFully(byte[],
int, int)
|
skipBytes()
|
public final Object readObject() throws OptionalDataException, ClassNotFoundException, IOException
Nesting Serializable Objects
import java.io.Serializable; import java.util.Vector; class Roger implements Serializable { public int lowBid; public int[] allBids = new int[10]; public Vector bidderNames = new Vector(); public Roger(int lowBid ) { this.lowBid = lowBid; bidderNames.addElement( "Bill"); } public String toString() { return " " + lowBid + " " + bidderNames; } }
Multiple Items on One Stream
If more than one object of the same type is put on the stream, the header information for the class is sent only once.
import java.io.*; class MultipleItems { public static void main( String args[] ) throws Exception { serialize(); deserialize(); } public static void serialize() throws IOException { OutputStream outputFile = new FileOutputStream( "Serialized" ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( new Roger( 2 )); cout.writeUTF( "Hi Mom" ); cout.writeObject( "Hi Dad" ); cout.writeFloat( 2.345F ); cout.writeObject( new Roger( 3 )); cout.close(); }
//Multiple Items - Continued
public static void deserialize() throws Exception { InputStream inputFile = new FileInputStream( "Serialized" ); ObjectInputStream cin = new ObjectInputStream( inputFile ); System.out.println( cin.readObject() ); System.out.println( cin.readUTF() ); System.out.println( cin.readObject() ); System.out.println( cin.readFloat() ); System.out.println( cin.readObject() ); } }Output 2 [Bill]
Hi Mom
Hi Dad
2.345
3 [Bill]
Saving and Recovering - A Simple Example
import java.io.*; import java.util.*; // Cheap demo class for something to save public class Student implements Serializable { String name; String address; public Student( String name, String address ) { this.name = name; this.address = address; } public String toString() { return name + "@" + address + "\n"; } }
//Saving and Recovering - A Simple Example public class StudentList implements Serializable { Vector list = new Vector(); String storageFileName; public StudentList( String fileForStorage) { storageFileName = fileForStorage; } public void addStudent( Student addToList ) { list.addElement( addToList ); } public String toString() { return list.toString(); } // Have the list save itself in a file public void save() throws IOException { OutputStream outputFile = new FileOutputStream( storageFileName ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( this ); cout.close(); }
//Saving and Recovering - A Simple Example //StudentList continued
// Recover a StudentList from a file public static StudentList fromFile( String studentListFile) throws OptionalDataException, ClassNotFoundException, IOException { InputStream inputFile = new FileInputStream( studentListFile ); ObjectInputStream cin = new ObjectInputStream( inputFile ); StudentList recoveredList = (StudentList) cin.readObject(); cin.close(); return recoveredList; } }
//Saving and Recovering - A Simple Example The driver program and Sample output
class Test public static void main( String args) throws Exception { StudentList cs535 = new StudentList( "cs535File"); cs535.addStudent( new Student( "Roger", "whitney@rohan")); cs535.addStudent( new Student( "Sam", "masc1232@rohan")); cs535.addStudent( new Student( "Ngu", "masc1111@rohan")); cs535.addStudent( new Student( "catMan", "masc43221@rohan")); System.out.println( cs535); cs535.save(); StudentList recoveredClass = StudentList.fromFile( "cs535File" ); System.out.println( recoveredClass); } }Output [Roger@whitney@rohan , Sam@masc1232@rohan , Ngu@masc1111@rohan , catMan@masc43221@rohan ] [Roger@whitney@rohan , Sam@masc1232@rohan , Ngu@masc1111@rohan , catMan@masc43221@rohan ]
Non-Serializable Objects
Some objects should not be serialized
transient Example import java.io.Serializable; import java.util.Vector; class Roger implements Serializable { private int lowBid; private transient float averageBid; private int highBid; public Roger(int lowBid, int highBid ) { this.lowBid = lowBid; this.highBid = highBid; averageBid = (lowBid + highBid) /2; } public String toString() { return "Low: " + lowBid + "Ave: " + averageBid; } }
// transient Example - Continued
class TransientExample { public static void main( String args[] ) throws Exception { serialize(); deserialize(); } public static void serialize() throws IOException { OutputStream outputFile = new FileOutputStream( "Serialized" ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( new Roger( 1, 5 )); cout.close(); } public static void deserialize() throws Exception { InputStream inputFile = new FileInputStream( "Serialized" ); ObjectInputStream cin = new ObjectInputStream( inputFile ); System.out.println( cin.readObject() ); } }Output Low: 1 Ave: 0.0
Customizing Deserialization
The readObject() method allows you to customize the deserialization of an object
The readObject() must have the signature given below
readObject must be implemented in the class of the object to be deserialized
private void readObject( ObjectInputStream in) { // this is normally called first in.defaultReadObject(); //Now you do the custom work }
defaultReadObject() throws the following exceptions
Throws: ClassNotFoundException
Example of readObject
import java.io.Serializable; import java.io.ObjectInputStream; import java.io.IOException; class Roger implements Serializable { private int lowBid; private transient float averageBid; private int highBid; public Roger(int lowBid, int highBid ) { this.lowBid = lowBid; this.highBid = highBid; averageBid = (lowBid + highBid)/2; } private void readObject( ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); averageBid = (lowBid + highBid)/2; } public String toString() { return "Low: " + lowBid + "Ave: " + averageBid; } }
// Example of readObject - Continued
class ReadObjectExample { public static void main( String args[] ) throws Exception { serialize(); deserialize(); } public static void serialize() throws IOException { OutputStream outputFile = new FileOutputStream( "Serialized" ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( new Roger( 1, 5 )); cout.close(); } public static void deserialize() throws Exception { InputStream inputFile = new FileInputStream( "Serialized" ); ObjectInputStream cin = new ObjectInputStream( inputFile ); System.out.println( cin.readObject() ); } }Output Low: 1 Ave: 3.0
Simple Customizing Serialization
The writeObject() method allows you to customize the serialization of an object
The writeObject() must have the signature given below
writeObject must be implemented in the class of the object to be deserialized
private void writeObject( ObjectOutputStream out ) { // normally do the custom work before // you call defaultWriteObject() out.defaultWriteObject(); }
defaultWriteObject() throws the following exceptions
Throws: IOException
Taking Complete Control It may make more sense to use Externalizable if you want to take complete control. Externalizable is covered later. The difference is that Serializable creates name-value pairs for the fields in the output. In the example below we are storing the name-value pairs, but not using them. Then we append the values at the end of the byte stream. Using we do not store name-value pairs that are not being used.
class Roger implements Serializable { private int lowBid; private float averageBid; private int highBid; public Roger(int lowBid, int highBid ) { this.lowBid = lowBid; this.highBid = highBid; averageBid = (lowBid + highBid)/2; } private void readObject( ObjectInputStream in) throws IOException { lowBid = in.readInt(); averageBid = in.readFloat(); highBid = in.readInt(); } private void writeObject( ObjectOutputStream out) throws IOException { out.writeInt( lowBid ); out.writeFloat( averageBid ); out.writeInt( highBid ); } public String toString() { return " " + lowBid + " " + averageBid; } }
readObject/writeObject & Inheritance
Parent is Serializable
If the parent is Serializable, then the parents fields will be serialized/deserialized automatically.
Parent is not Serializable
The parents no-argument constructor will be called to initialize the object.
If you wish the parent’s fields to the reset to the proper values you need to do it in the child’s write/readObject method.
Example public class Parent { String parentData = "In-line"; public Parent() { parentData = "Constructor"; System.out.println( "In parent"); } }
Child Class import java.io.Serializable; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.IOException; import java.lang.ClassNotFoundException; public class Roger extends Parent implements Serializable { private int lowBid; private int highBid; public Roger(int lowBid, int highBid ) { this.lowBid = lowBid; this.highBid = highBid; parentData = "Roger Constructor"; System.out.println( "In Roger Constructor"); } private void readObject( ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); parentData = in.readUTF(); } private void writeObject( ObjectOutputStream out) throws IOException, ClassNotFoundException { out.defaultWriteObject( ); out.writeUTF( parentData ); } public String toString() { return " parent:" + parentData; } }
Class Versions
The Problem
Start with a class:
class Unstable { double hereToday; }
Serialize an Unstable object to a file for later use
Now change the class
class Unstable { char goneTomorrow; }
After the class has changed, try to deserialize the object!
What should happen?
What could happen? (Hint nothing very good)
Class serialVersionUID
Java generates a serialVersionUID for each class
When deserializing an object, the serialVersionUID of the deserialized object and it class are checked
If they do not agree, an exception is thrown
Information Used to Compute the serialVersionUID
1. Class name
2. The class modifiers
3. The name of each interface
4. For each field of the class(except private static and private transient fields):
Controlling The Class Version
By declaring in your class a field of type and name
class Sam implements Serializable { private int lowBid; public Sam(int lowBid) { this.lowBid = lowBid; } }
Example of serialVersionUID
Start with this class:
class Roger implements Serializable { static final long serialVersionUID = 1L; private int lowBid; public Roger(int lowBid ) { this.lowBid = lowBid; } }
Serialize it with the following program. The rest of the example will read this serialized version of the Roger object
class SerializeRoger { public static void main( String args[] ) throws Exception { OutputStream outputFile = new FileOutputStream( "Serialized" ); ObjectOutputStream cout = new ObjectOutputStream( outputFile ); cout.writeObject( new Roger( 1, 5 )); cout.close(); } }
Example of serialVersionUID - Continued Change the class as follows and recompile
class Roger implements Serializable { static final long serialVersionUID = 1L; private int lowBid; public Roger(int lowBid ) { this.lowBid = lowBid; } public String toString() { return " " + lowBid + " " + averageBid; } }
Now deserialize the original Roger object with the program. There is no problem as the class has not changed any data
class ReadRogerObejct { public static void main( String args[] ) throws Exception { InputStream inputFile = new FileInputStream( "Serialized" ); ObjectInputStream cin = new ObjectInputStream( inputFile ); System.out.println( cin.readObject() ); } }
Example of serialVersionUID - Continued Now we add some new fields to the class. Since they are not in the saved Roger object, we add the readObject method to make some reasonable values for the new fields. Compiling the class and deserialize in the original Roger object with the ReadRogerObejct class. It will work.
class Roger implements Serializable { static final long serialVersionUID = 1L; private int lowBid; private transient float averageBid; private int highBid; public Roger(int lowBid, int highBid ) { this.lowBid = lowBid; this.highBid = highBid; averageBid = (lowBid + highBid)/2; } private void readObject( ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if ( highBid == 0) highBid = lowBid; averageBid = (lowBid + highBid)/2; } public String toString() { return " " + lowBid + " " + averageBid; } }
Example of serialVersionUID - Continued We can change the class to the following and it will still work to deserialize the original Roger object. Note that the field "lowBid" is not in this class.
class Roger implements Serializable { static final long serialVersionUID = 1L; private int highBid; public Roger(int highBid) { this.highBid= highBid; } }
Example of serialVersionUID - Continued ¿So what does not work?
This is actually three questions:
class Roger implements Serializable { static final long serialVersionUID = 1L; private float lowBid; public Roger(int lowBid) { this.lowBid = lowBid; } }
Serialver
When the class needs another version number use serialver to generate a new one. Sun recommends using the program serialver to generate the serialVersionUID for your class. This recommendation is given in the context of Java's RMI for distributing objects over a network. This recommendation is made for security reasons
Using serialver
serialver is located in the same directory as javac
1. Compile your class, make sure that the variable:
static final long serialVersionUIDis not defined in the class
2. run serialver using the command line:
serialvar classname
3. The output of serialver is line of text to declare the variable serialVersionUID
Example of Using serialver
import java.io.Serializable; class Roger implements Serializable { public int lowBid; public Roger(int lowBid ) { this.lowBid = lowBid; } public String toString() { return " " + lowBid; } }
Put the above code in the file Roger.java
eli-> javac Roger.java eli-> serialver Roger Roger: static final long serialVersionUID = -8462350894591099987L;
Version Control & JDK 1.2
The serialization process given so far does not provide enough flexibility to handle changing classes. JDK 1.2 adds some new features to serialization.
ObjectStreamField
There are two ways to declare fields to be serializable. The first is just to declare the class Serializable. The second is to use ObjectStreamField. To do this you must have a field in the class defined as:
private static final ObjectStreamField[] serialPersistentFieldsThe final is optional, but all other aspects are required. ObjectOutputStream will automatically generate field data (type and name) for only those fields listed in the serialPersistentFields variable. The data is generated even if you do not write a value for the listed fields. No values of fields will be generated for you. You have to write the values out explicitly using the put() method of ObjectOutputStream.PutField. See example next slide. defaultWriteObject( ) of ObjectOutputStream does not work when using serialPersistentFields.
Example import java.util.ArrayList; import java.io.Serializable; import java.io.ObjectStreamField; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectInputStream.GetField; import java.io.IOException; import java.io.ObjectOutputStream; public class Roger implements Serializable { private int lowBid; private ArrayList info = new ArrayList(); private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField( "low", Integer.TYPE), new ObjectStreamField( "high", ArrayList.class) }; public Roger(int lowBid, String highBidData ) { this.lowBid = lowBid; info.add( highBidData ); } private void readObject( ObjectInputStream in) throws IOException { try { ObjectInputStream.GetField fields = in.readFields(); lowBid = (int) fields.get("low", 0); info = (ArrayList) fields.get("high", new ArrayList() ); } catch (Exception ClassNotFoundException) { throw new IOException(); } } private void writeObject( ObjectOutputStream out) throws IOException { ObjectOutputStream.PutField fields = out.putFields(); fields.put("low", lowBid); fields.put("high", info); out.writeFields( ); } }Note: the fields.get() and fields.put() can be done in any order.
Motivation
The following example is from the Sun documentation. The original can be found at: http://java.sun.com/products/jdk/1.2/docs/guide/serialization/examples/altimpl/index3.html The example starts with ARectangle class that stores its corners as four ints. The class evolves to store its corners as two Point objects. We want to serialize an object of either version of the class and deserialize that object using either version of the class. The second version of the class has the serialVersionUID of the first class, so to the serialize/deserialize mechanism the two classes are the same. Then the second class uses the serialPersistentFields mechanism to read/write the object’s state using the exact format of the first class.
Orginal Rectangle Class /* * @(#)OriginalClass.java 1.4 98/10/01 * * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved. */ import java.io.*; import java.awt.*; class ARectangle implements java.io.Serializable { int x1; int y1; int x2; int y2; ARectangle(int xone, int yone, int xtwo, int ytwo) { x1 = xone; y1 = yone; x2 = xtwo; y2 = ytwo; } public String toString() { return("x1: " + x1 + "\ny1: " + y1 + "\nx2: " + x2 + "\ny2: " + y2); } }
Revised Rectangle Class /* * @(#)EvolvedClass.java 1.7 98/10/01 * * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved. */ import java.io.*; import java.awt.*; class ARectangle implements java.io.Serializable { Point point1; Point point2; static final long serialVersionUID = 9030593813711490592L; private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("x1", Integer.TYPE), new ObjectStreamField("y1", Integer.TYPE), new ObjectStreamField("x2", Integer.TYPE), new ObjectStreamField("y2", Integer.TYPE) }; ARectangle(int x1, int y1, int x2, int y2) { point1 = new Point(x1, y1); point2 = new Point(x2, y2); } private void writeObject(ObjectOutputStream s) throws IOException { ObjectOutputStream.PutField fields = s.putFields(); fields.put("x1", point1.x); fields.put("y1", point1.y); fields.put("x2", point2.x); fields.put("y2", point2.y); s.writeFields(); }
private void readObject(ObjectInputStream s) throws IOException { ObjectInputStream.GetField fields = null; try { fields = s.readFields(); } catch (Exception ClassNotFoundException) { throw new IOException(); } int x1 = (int)fields.get("x1", 0); int y1 = (int)fields.get("y1", 0); int x2 = (int)fields.get("x2", 0); int y2 = (int)fields.get("y2", 0); point1 = new Point(x1, y1); point2 = new Point(x2, y2); } public String toString() { return("point1.x: " + point1.x + "\npoint1.y: " + point1.y + "\npoint2.x: " + point2.x + "\npoint2.y: " + point2.y); } }
Externalizable
The java.io.Externalizable interface allows you to take complete control of the serialization process. You need to do the following:
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException public void writeExternal(ObjectOutput out) throws IOException
Externalizable Example import java.io.Externalizable; import java.io.ObjectOutput; import java.io.ObjectInput; import java.io.IOException; public class Roger implements Externalizable { private int lowBid; private String highBid; public Roger() { lowBid = -1; highBid = "none"; } public Roger(int lowBid, String highBidData ) { this.lowBid = lowBid; this.highBid = highBidData; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { lowBid = in.readInt(); highBid = (String) in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt( lowBid ); out.writeObject( highBid ); } public String toString() { return " " + lowBid + "high: " + highBid; } }
Javadoc & Serialization
There are three tags used to document Serialization/Externalizable. In JDK 1.2 if you do not use these tags, javadoc will complain.
@serial
Protocol Changes JDK 1.1 to JDK 1.2
The JDK 1.2 uses an improved protocol to serialize objects. You can use ObjectOutputStream.useProtocolVersion to tell JDK 1.2 to write with the JDK 1.1 protocol. For more details see section 6.3 Stream Protocol Versions of Java Object Serialization Specification at: http://java.sun.com/products/jdk/1.2/docs/guide/serialization/spec/protocol.doc3.html#5849
Copyright ©, All rights reserved.
1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.
Previous    visitors since 23-Feb-99    Next