|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 6
PersistentAnimation animation = new PersistentAnimation(10); FileOutputStream fos = ... ObjectOutputStream out = new ObjectOutputStream(fos); out.writeObject(animation);
All seems fine until we read the object back in with a call to the readObject() method. Remember, a constructor is called only when a new instance is created. We are not creating a new instance here, we
are restoring a persisted object. The end result is the animation object will work only once, when it is first instantiated.
Kind of makes it useless to persist it, huh?
Well, there is good news. We can make our object work the way we want it to; we can make the animation restart upon restoration
of the object. To accomplish that, we could, for example, create a startAnimation() helper method that does what the constructor currently does. We could then call that method from the constructor, after which
we read the object back in. Not bad, but it introduces more complexity. Now, anyone who wants to use that animation object
will have to know that method has to be called following the normal deserialization process. That does not make for a seamless
mechanism, something the Java Serialization API promises developers.
There is, however, a strange yet crafty solution. By using a built-in feature of the serialization mechanism, developers can enhance the normal process by providing two methods inside their class files. Those methods are:
private void writeObject(ObjectOutputStream out) throws IOException;private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;Notice that both methods are (and must be) declared private, proving that neither method is inherited and overridden or overloaded. The trick here is that the virtual machine will automatically
check to see if either method is declared during the corresponding method call. The virtual machine can call private methods
of your class whenever it wants but no other objects can. Thus, the integrity of the class is maintained and the serialization
protocol can continue to work as normal. The serialization protocol is always used the same way, by calling either ObjectOutputStream.writeObject() or ObjectInputStream.readObject(). So, even though those specialized private methods are provided, the object serialization works the same way as far as any
calling object is concerned.
Considering all that, let's look at a revised version of PersistentAnimation that includes those private methods to allow us to have control over the deserialization process, giving us a pseudo-constructor:
10 import java.io.Serializable;
20 public class PersistentAnimation implements Serializable, Runnable
30 {
40 transient private Thread animator;
50 private int animationSpeed;
60 public PersistentAnimation(int animationSpeed)
70 {
80 this.animationSpeed = animationSpeed;
90 startAnimation();
100 }
110 public void run()
120 {
130 while(true)
140 {
150 // do animation here
160 }
170 }
180 private void writeObject(ObjectOutputStream out) throws IOException
190 {
200 out.defaultWriteObject();
220 }
230 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
240 {
250 // our "pseudo-constructor"
260 in.defaultReadObject();
270 // now we are a "live" object again, so let's run rebuild and start
280 startAnimation();
290
300 }
310 private void startAnimation()
320 {
330 animator = new Thread(this);
340 animator.start();
350 }
360 }
Notice the first line of each of the new private methods. Those calls do what they sound like -- they perform the default
writing and reading of the flattened object, which is important because we are not replacing the normal process, we are only
adding to it. Those methods work because the call to ObjectOutputStream.writeObject() kicks off the serialization protocol. First, the object is checked to ensure it implements Serializable and then it is checked to see whether either of those private methods are provided. If they are provided, the stream class
is passed as the parameter, giving the code control over its usage.