The Serializable Interface

Unlimited serialization would introduce security problems. For one thing, it allows unrestricted access to an object's private fields. By chaining an object output stream to a byte array output stream, a hacker can convert an object into a byte array. The byte array can be manipulated and modified without any access protection or security manager checks. Then the byte array can be reconstituted into a Java object by using it as the source of a byte array input stream.

Security isn't the only potential problem. Some objects exist only as long as the current program is running. A java.net.Socket object represents an active connection to a remote host. Suppose a socket is serialized to a file, and the program exits. Later the socket is deserialized from the file in a new programbut the connection it represents no longer exists. Similar problems arise with file descriptors, I/O streams, and many other classes.

For these and other reasons, Java does not allow instances of arbitrary classes to be serialized. You can only serialize instances of classes that implement the java.io.Serializable interface. By implementing this interface, a class indicates that it may be serialized without undue problems.

public interface Serializable

This interface does not declare any methods or fields; it is a marker interface that serves purely to indicate that a class may be serialized. However, subclasses of a class that implements a particular interface also implement that interface. Thus, many classes that do not explicitly declare that they implement Serializable are in fact serializable. For instance, java.awt.Component implements Serializable. Therefore, its direct and indirect subclasses, including Button, Scrollbar, TextArea, List, Container, Panel, Applet, and all Swing components may be serialized. java.lang.Throwable implements Serializable. Therefore, all exceptions and errors are serializable.

You can glean some general principles about what classes are and are not likely to be serializable. For instance, exceptions, errors, and other throwable objects are always serializable. Streams, readers and writers, and most other I/O classes are not serializable. AWT and Swing components, containers, and events are serializable, but event adapters, image filters, and AWT classes that abstract OS-dependent features are not. java.beans classes are not serializable. Type wrapper classes are serializable except for Void; most other java.lang classes are not. Reflection classes are not serializable. java.math classes are serializable. URL objects are serializable. Socket, URLConnection, and most other java.net classes are not. Container classes are serializable (though see the next section). Compression classes are not serializable. Nonstatic inner classes (including your own inner classes) are almost never serializable.

Overall, there are seven common reasons why a class may not be serializable:

  1. It is too closely tied to native code (java.util.zip.Deflater).
  2. The object's state depends on the internals of the virtual machine or the runtime environment and thus may change from run to run (java.lang.Thread, java.io.InputStream, java.io.FileDescriptor, java.awt.PrintJob).
  3. Serializing it is a potential security risk (java.lang.SecurityManager, java.security.MessageDigest).
  4. The class is mostly a holder for static methods without any real internal state (java.beans.Beans, java.lang.Math).
  5. The class is a nonstatic inner class. Serialization just doesn't work well with nonstatic inner classes. (Static inner classes have no problem being serialized.)
  6. The programmer who wrote the class simply didn't think about serialization.
  7. An alternate serialization format is preferred in a particular context. (XOM node classes are not serializable because the proper serialization format for XML is XML.)

13.5.1. Classes That Implement Serializable but Aren't

Just because a class may be serialized does not mean that it can be serialized. Several problems can prevent a class that implements Serializable from actually being serialized. Attempting to serialize such a class throws a NotSerializableException, a kind of IOException:

public class NotSerializableException extends ObjectStreamException

 

13.5.1.1. Problem 1: References to nonserializable objects

The first common problem that prevents a serializable class from being serialized is that its graph contains objects that do not implement Serializable. The graph of an object is the collection of all objects that the object holds references to, and all the objects those objects hold references to, and all the objects those objects hold references to, and so on, until there are no more connected objects that haven't appeared in the collection. For an object to be serialized, all the objects it holds references to must also be serializable, and all the objects they hold references to must be serializable, and so on. For instance, consider this skeleton of a class:

import java.applet.*; import java.net.*; public class NetworkApplet extends Applet { private Socket theConnection; //... }

NetworkApplet extends Applet, which extends Panel, which extends Container, which extends Component, which implements Serializable. Thus, NetworkApplet should be serializable. However, NetworkApplet contains a reference to a java.net.Socket object. Socket is not a serializable class. Therefore, if you try to pass a NetworkApplet instance to writeObject( ), a NotSerializableException is thrown.

The situation is even worse for container classes like HashMap and Vector. Since serialization performs a deep copy to the output stream, storing even a single nonserializable class inside a container prevents it from being serialized. Since the objects stored in a container can vary from program to program or run to run, there's no sure-fire way to know whether or not a particular instance of a container class can be serialized, short of trying it.

13.5.1.2. Problem 2: Missing a no-argument constructor in superclass

The second common problem that prevents a serializable class from being deserialized is that a superclass of the class is not serializable and does not contain a no-argument constructor. java.lang.Object does not implement Serializable, so all classes have at least one superclass that's not serializable. When an object is deserialized, the no-argument constructor of the closest superclass that does not implement Serializable is invoked to establish the state of the object's nonserializable superclasses. If that class does not have a no-argument constructor, the object cannot be deserialized. For example, consider the java.io.ZipFile class introduced in Chapter 10. It does not implement Serializable:

public class ZipFile extends Object implements java.util.zip.ZipConstants

Furthermore, it has only these two constructors, both of which take arguments:

public ZipFile(String filename) throws IOException public ZipFile(File file) throws ZipException, IOException

Suppose you want to subclass it to allow the class to be serialized, as shown in Example 13-2.

Example 13-2. A SerializableZipFileNot

import java.io.*; import java.util.zip.*; public class SerializableZipFileNot extends ZipFile implements Serializable { public SerializableZipFileNot(String filename) throws IOException { super(filename); } public SerializableZipFileNot(File file) throws IOException { super(file); } public static void main(String[] args) { try { SerializableZipFileNot szf = new SerializableZipFileNot(args[0]); ByteArrayOutputStream bout = new ByteArrayOutputStream( ); ObjectOutputStream oout = new ObjectOutputStream(bout); oout.writeObject(szf); oout.close( ); System.out.println("Wrote object!"); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray( )); ObjectInputStream oin = new ObjectInputStream(bin); Object o = oin.readObject( ); System.out.println("Read object!"); } catch (Exception ex) {ex.printStackTrace( );} } }

The main( ) method attempts to create an instance of this class, serialize it to a byte array output stream, and read it back in from a byte array input stream. However, here's what happens when you run it:

D:JAVA> java SerializableZipFileNot test.zip Wrote object! java.io.InvalidClassException: java.util.zip.ZipFile; at java.io.ObjectInputStream.inputObject(Compiled Code) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:363) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:226) at SerializableZipFileNot.main(SerializableZipFileNot.java:28)

Since the superclass, ZipFile, is not itself serializable and cannot be instantiated with a no-argument constructor, the subclass cannot be deserialized. It can be serialized, but that isn't much use unless you can get the object back again. Later, you'll see how to make a SerializableZipFile class that can be both written and read. However, to do this, you'll have to give up something else, notably the ZipFile type.

13.5.1.3. Problem 3: Deliberate throwing of NotSerializableException

A few classes appear to be unserializable out of pure spite (though normally there's more reason to it than that). Sometimes it's necessary, for security or other reasons, to make a class or even a particular object not serializable, even though one of its superclasses does implement Serializable. Since a subclass can't unimplement an interface implemented in its superclass, the subclass may choose to deliberately throw a NotSerializableException when you attempt to serialize it. You'll see exactly how this is done shortly.

13.5.1.4. Locating the offending object

When you encounter a class that you think should be serializable but isn't (and this happens all too frequently, often after you've spent two hours adjusting and customizing several dozen beans in a builder tool that now can't save your work), you'll need to locate the offending class. The detailMessage field of the NotSerializableException contains the name of the unserializable class. This can be retrieved with the getMessage( ) method of java.lang.Throwable or as part of the string returned by toString( ):

try { out.writeObject(unserializableObject); } catch (NotSerializableException ex) { System.err.println(ex.getMessage( ) + " could not be serialized"); }

It is not always obvious where the offending class sneaked in. For example, if you're trying to serialize a hash table that contains seven lists, each of which contains many different objects of different classes, a single nonserializable object in one of the lists causes a NotSerializableException. You'll need to walk through the object graph in a debugger to determine which object caused the problem.

13.5.1.5. Making nonserializable fields transient

Once you've identified the problem object, the simplest solution to is to mark the field that contains the object TRansient. For example, we can mark the Socket field transient in the networking applet:

import java.applet.*; import java.net.*; public class NetworkApplet extends Applet { private transient Socket theConnection; //... }

The TRansient keyword tells the writeObject( ) method not to serialize the Socket object theConnection onto the underlying output stream. Instead, it's just skipped. When the object is deserialized, you still need to ensure that the state is consistent with what you expect. It may be enough to make sure theConnection is nonnull before accessing it.

Категории