This chapter will answer the following questions:
Object serialization is the process of storing a complete object to disk or other storage system, ready to be restored at any time. The process of restoring the object, in contrast, is known as deserialization. In this section, you'll learn why serialization is useful and how Java implements serialization and deserialization.
An object that has been serialized is said to be persistent. Most objects in memory, in contrast, are transient, meaning that they go away when their references drop out of scope or the computer loses power. Persistent objects, on the other hand, exist as long as there is a copy of them stored somewhere on a disk, tape, or in ROM.
Traditionally, saving data to a disk or other storage device required that you define a special data format, write a set of functions to write and read that format, and create a mapping between the file format and the format of your data. The functions to read and write data were either simple and lacked extensibility or were complex and difficult to create and maintain.
As you should be aware by now, Java is completely based around objects and object-oriented programming. To this end, Java provides a storage mechanism for objects in the form of serialization. With the Java way of doing things, you no longer have to worry about details of file formats and I/O. Instead, you are free to concentrate on solving your real-world tasks by designing and implementing objects. If, for instance, you make a class persistent and later add new fields to it, you do not have to worry about modifying routines that read and write the data for you. All fields in a serialized object will automatically be written and restored.
Serialization is a new feature of JDK 1.1. Java's support for serialization consists of the Serializable interface, the ObjectOutputStream class and the ObjectInputStream class, as well as a few supporting classes and interfaces. We'll examine all three of these items as we demonstrate an application that can save user information to a disk and read it back.
Suppose, for instance, you wanted to save information about a particular user as in the figure below. After the user types in his or her name and password into the appropriate fields, the application should save information about this user to disk. Of course, this is a very simple example, but you can easily imagine saving data about user application preferences, the last document opened, etc.
Figure C.1 - Saving a user name and password
Let's create an object that represents the current user. It needs to have properties that represent both the user's name, as well as the password:
package Serialize; public class UserInfo implements java.io.Serializable { private String userName = ""; private String userPassword = ""; public String getUserName() { return userName; } public void setUserName(String s) { userName = s; } public String getUserPassword() { return userPassword; } public void setUserPassword(String s) { userPassword = s; } }
You'll note that the above class implements the java.io.Serializable interface. Serializable is known as a "tagging interface" because it specifies no methods to be implemented, but merely "tags" its objects as being of a particular type.
Any object that you expect to serialize should implement this interface. This is critical because the techniques used later in this chapter won't work otherwise. If, for instance, you try to serialize an object that does not implement this interface, a NotSerializableException will be raised.
Before you serialize the UserInfo object, you should set it up with the values that the user entered into the text fields. This is done when the user clicks the Serialize button:
void button1_mouseClicked(MouseEvent e) { u.setUserName (textFieldName.getText()); u.setUserPassword (textFieldPassword.getText());
Next, you will need to open a FileOutputStream to the file that will contain the serialized data. In this example, the file will be called C:\userInfo.txt:
try { FileOutputStream file = new FileOutputStream ("c:\\userInfo.txt");
Then, you need to create an ObjectOutputStream that will serialize the object and send it to the FileOutputStream.
ObjectOutputStream out = new ObjectOutputStream(file);
Now, you're ready to send the UserInfo object to the file. This is accomplished by calling the ObjectOutputStream's writeObject() method. Calling the flush() method will flush the output buffer to ensure that the object is actually written to the file.
out.writeObject(u); out.flush();
Finally, you need to close the output stream to free up any resources, such as file descriptors, used by the stream.
out.close(); }
Note that you get an IOException if there were any problems writing to the file or if the object does not support the Serializable interface.
catch (java.io.IOException IOE) { labelOutput.setText ("IOException"); } }
You can now verify that the object has been written by opening it in a text editor. (Don't try to edit it, or the file will probably be corrupted!) Notice that a serialized object contains a mixture of ASCII text and binary data:
Figure C.2 - The serialized object
The ObjectOutputStream class contains several useful methods for data to a stream. You are not restricted to writing objects. Calling writeInt(), writeFloat(), writeDouble()-will write any of the fundamental types to be written to a stream. If you want to write more than one object or fundamental type to the same stream, you can do so by repeatedly calling these methods against the same ObjectOutputStream object. When you do this, however, you must make sure to read the objects back in the same order.
The object now has been written to the disk, but how do you get if back? Once the user clicks the Deserialize button, you want to read the data back from the disk into a new object.
You can begin the process by creating a new FileInputStream object to read from the file you just wrote:
void button2_mouseClicked(MouseEvent e) { try { FileInputStream file = new FileInputStream ("c:\\userInfo.txt");
Next, you need to create an ObjectInputStream, which give you the capability to read objects from that file.
ObjectInputStream input = new ObjectInputStream(file);
After this, call the ObjectInputStream's readObject() method to read the first object from the file. readObject() returns type Object, so you'll want to cast it to the appropriate type (UserInfo).
UserInfo u = (UserInfo) input.readObject();
When you're done reading, remember to close the ObjectInputStream, so you free up any resources associated with it, such as file descriptors.
input.close();
Finally, you can use the u object as you would any other object of the UserInfo class:
labelOutput.setText ("Name is: "+u.getUserName()+", password is: "+ u.getUserPassword()); }
Reading from a file could cause an IOException, so you should handle this exception. You may also get a StreamCorruptedException (a subclass of IOException) if the file has been corrupted in any way:
catch (java.io.IOException IOE) { labelOutput.setText ("IOException"); }
There's another exception you need to deal with. The readObject() method can throw a ClassNotFoundException. This exception can occur if you attempt to read an object for which you have no implementation. For instance, if this object was written by another program, or you have renamed the UserInfo class since the file was written, you'll get a ClassNotFoundException.
catch (ClassNotFoundException cnfe) { labelOutput.setText ("ClassNotFoundException"); } }
Figure C.3 - The object restored
ObjectInputStream also has methods such as readDouble(), readFloat(), etc., which are the counterparts to the writeDouble(), writeFloat(), etc. methods. You need to call each method in sequence, the same way the objects were written to the stream.
You may be wondering what happens when an object you are serializing contains a field that refers to another object, rather than a primitive type. In this case, both the base object and the secondary object will be written to the stream. You should realize, however, that both objects written to the stream need to implement the Serializable interface. If they don't, a NotSerializableException will be thrown when the writeObject() method is called.
Recall that object serialization can create potential security problems. In the example above, we wrote a password to a serialized object. While it can be acceptable in many circumstances, keep this in mind when you choose to serialize an object.
Finally, if you want to create a persistent object, but do not want to use the default serialization mechanism, the Serializable interface documents two methods, writeObject() and readObject(), which you can implement to perform custom serialization. The Externalizable interface also provides a similar mechanism. Consult the JDK documentation for information about these techniques.
What was covered in this chapter: