| CS 535: Object-Oriented Programming & Design |
|
---|
Fall Semester, 1997
Doc 22, Serialization, Persistent Objects
To Lecture Notes Index
San Diego State University -- This page last updated 10-Nov-97
Contents of Doc 22, Serialization, Persistent Objects
- Reference
- Serialization
- Making an Object Serializable
- Serializable objects can contain other objects
- Saving and Recovering - A Simple Example
- Non-Serializable Fields - transient
- Customizing Deserialization
- Customizing Serialization
- Class Versions
- Serialver
- Saving Objects as Strings
- Properties
- Stringizable
- Properties Verses LabeledData
Java in a NutShell 2nd Ed, Flanagan, chapter 9
Java 1.1 API
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
- Creating the sequence of bytes from an object
Deserializing
- Recreating the object from the above generated bytes
-
- Actually creates a new object with the fields containing the same values as
the original object
-
- If the fields are also serializable object then they are also
"recreated"
The object's class must implement the interface
- java.io.Serializable
Serializable has no methods
Example
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( 1));
cout.close();
}
public static void deserialize() throws Exception {
InputStream inputFile =
new FileInputStream( "Serialized" );
ObjectInputStream cin =
new ObjectInputStream( inputFile );
System.out.println( cin.readObject() );
}
}
Output
1
ObjectOutputStream
Methods
annotateClass(Class)
writeByte(int) |
close()
writeBytes(String) |
defaultWriteObject()
writeChar(int) |
drain()
writeChars(String) |
enableReplaceObject(boolean)
writeDouble(double) |
flush()
writeFloat(float) |
replaceObject(Object)
writeInt(int) |
reset()
writeLong(long) |
write(byte[])
writeObject(Object) |
write(byte[], int, int)
writeShort(int) |
write(int)
writeStreamHeader() |
writeBoolean(boolean)
writeUTF(String) |
public final void writeObject(Object obj) throws IOException
Throws: InvalidClassException
- Something is wrong with a class used by serialization.
Throws: NotSerializableException
- Some object to be serialized does not implement the java.io.Serializable
interface.
Throws: IOException
- Any exception thrown by the underlying OutputStream.
ObjectInputStream
Methods
available() readInt()
close() readLine()
defaultReadObject() readLong()
enableResolveObject(boolean) readObject()
read() readShort()
read(byte[], int, int) readStreamHeader()
readBoolean() readUnsignedByte()
readByte() readUnsignedShort()
readChar() readUTF()
readDouble() registerValidation(ObjectInputValidation, int)
readFloat() resolveClass(ObjectStreamClass)
readFully(byte[]) resolveObject(Object)
readFully(byte[], int, int) skipBytes(int)
public final Object readObject() throws
OptionalDataException,
ClassNotFoundException,
IOException
Throws: ClassNotFoundException
- Class of a serialized object cannot be found.
Throws: InvalidClassException
- Something is wrong with a class used by serialization.
Throws: StreamCorruptedException
- Control information in the stream is inconsistent.
Throws: OptionalDataException
- Primitive data was found in the stream instead of objects.
Throws: IOException
- Any of the usual Input/Output related exceptions.
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]
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
- objects that hold system resources
Some values or objects can be recomputed or are not important to serialize
If an object has a field that should not be serialized declare the field
transient
That field will not be included in the serialization of the object
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 " " + lowBid + " " + 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
1 0.0
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
- if the class of a serialized object could not be found.
Throws: IOException
- if an I/O error occurs.
Throws: NotActiveException (subclass of IOException)
- if the stream is not currently reading objects.
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 " " + lowBid + " " + 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
1 3.0
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
- if an I/O error occurs.
Throws: NotActiveException (subclass of IOException)
- if the stream is not currently writing objects.
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):
- The name of the field
- The modifiers of the field
- The descriptor of the field
5. For each method including constructors, except private methods and
constructors:
- The name of the method
- The modifiers of the method
- The descriptor of the method
Controlling The Class Version
By declaring in your class a field of type and name
- static final long serialVersionUID
you override the default method of computing the classes version number.
This is done when changes are made to a class that don't break deserialization
of older objects.
For example start with the class:
class Sam implements Serializable
{
private int lowBid;
public Sam(int lowBid)
{
this.lowBid = lowBid;
}
}
Now add a toString method to the class. This will change the class version.
Objects serialized with the original Sam class will not be serializable with
the Sam class containing the toString(). But clearly we have not changed the
class in any way to make it impossible to deserialize the old objects. If we
added a serialVersionUID to the class and did not change its value, then
deserialization would work with the modified class and old objects!
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 two questions:
- When will the deserialization throw an exception due to changes in the
class?
-
- What changes in the class logical require a new version?
Changing the type of a field causes the deserialization to fail. The following
class will cause to the deserialization of the original Roger object to fail
class Roger implements Serializable
{
static final long serialVersionUID = 1L;
private float lowBid;
public Roger(int lowBid)
{
this.lowBid = lowBid;
}
}
The programmer will have to decide the answer to the second question on an
individual basis.
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 serialvar
serialvar is located in the same directory as javac
1. Compile your class, make sure that the variable:
static final long serialVersionUID
is
not defined in the class
2. run serialvar using the command line:
serialvar classname
3. The output of serialvar is line of text to declare the variable
serialVersionUID
Example of Using serialvar
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;
There are situations when you need to save objects in string format and restore
the object from the string
- Configuration files
Some classes that convert between strings and objects
- java.util.Properties - subclass of Hashtable
-
- sdsu.util.Stringizable
- sdsu.util.LabeledData - subclass of Properties
-
- sdsu.util.Table
-
- sdsu.util.LabeledTable - subclass of Table
-
- sdsu.util.List
-
- sdsu.io.StringizableWriter
-
- sdsu.io.StringizableReader
-
- sdsu.io.Repository
import java.util.*;
import java.io.*;
import sdsu.util.*;
class PropertiesExample
{
public static void main( String args[] ) throws Exception
{
Properties myHash = new Properties();
myHash.put("name", "Roger");
myHash.put("salary", "$10");
myHash.put("news=bad", "trouble");
FileOutputStream outputFile =
new FileOutputStream( "Property" );
myHash.save( outputFile, "This is the file header" );
outputFile.close();
InputStream inputFile =
new FileInputStream( "Property" );
Properties myHashRefried = new Properties();
myHashRefried.load( inputFile );
System.out.println( myHashRefried );
System.out.println(
myHashRefried.getProperty( "news"));
}
}
PropertiesExample ResultOutput
{salary=$10, news=bad=trouble, name=Roger}
bad=trouble
Contents of file "Property"
#This is the file header
#Tue Oct 21 20:50:41 ADT 1997
salary=$10
name=Roger
news=bad=trouble
Interface with five operations which address converting an object to an from a
string
Convert object to a string with or without header information
public String toString( );
public String toString( String header );
Convert output of toString back to an object
public void fromString( String objectString ) throws ConversionException;
MetaData sets characters used to separate data in the string version of an
object
public void setMetaData( LabeledData metaData );
public LabeledData getMetaData( );
LabeledData
import java.util.*;
import java.io.*;
import sdsu.util.*;
class LabeledDataExample
{
public static void main( String args[] ) throws Exception
{
LabeledData myHash = new LabeledData();
myHash.put("name", "Roger");
myHash.put("salary", "$10");
myHash.put("news=bad", "trouble");
FileOutputStream outputFile =
new FileOutputStream( "Property" );
myHash.save( outputFile, "This is the file header" );
outputFile.close();
InputStream inputFile =
new FileInputStream( "Property" );
LabeledData myHashRefried = new LabeledData();
myHashRefried.load( inputFile );
System.out.println( myHashRefried );
System.out.println(
myHashRefried.getProperty( "news"));
}
}
LabeledData Example ResultOutput
salary=$10;name=Roger;'news=bad'=trouble
null
Contents of file "Property"
#This is the file header
#Tue Oct 21 20:58:10 ADT 1997
salary=$10;name=Roger;'news=bad'=trouble
Properties
- Java standard for configuration files
-
- White space allowed between key and values
-
- # starts a comment, ends with end of line
LabeledData
- subclass of Properties
-
- Allows programmer to specify special characters
- char used to separate key and value
-
- char used to separate key-value pairs
-
- Escapes key or value if they contain special characters
-
- White space allowed between key and values
-
- Programmer can define whitespace
-
- # starts a comment, ends with end of line
Table
A two dimensional array that is Stringizable
LabeledTable
A two dimensional array that is Stringizable and indexed by objects