writeObject() and readObject() methods, and look at a new example of how they may be used. And, we'll take a look at another feature of the Java Object
Serialization Specification, object validation, which checks an object after deserialization to ensure that it's valid.This month I received the following reader comment:
... transient initializers aren't instantiated during object deserialization; that is, if you have:transient int fred = 4;, you would expect that this value would not be serialized during the saving process (it doesn't) but that when deserializing, it would assume a default value of 4. However, it doesn't. It assumes a value of 0 because that's the default value for things of typeint. I have found that the sensible solution to this plan is:
- Don't have any initializers in the class definition
- Have a method called
init(), which sets up the default values- Invoke
init()after the constructor has been called
However, I have had a number of problems with this depending on how the invocation of the constructor has been called, and vice versa.
I would be interested to hear if you have any other ways of achieving this.
So, the complaint is that each transient variable in a deserialized object is initialized to the default value for its type, even if static initializers for those variables appear in the source file. I agree that this seems counterintuitive.
Even though transient variables are (by definition) not part of an object's state, it would be reasonable to expect that the class initializes them with the static initializer when the instance is created. But such is not the case. Transient variables are instantiated (despite what the letter says), but their static initializations are never applied during deserialization.
Let's explore this situation a bit further. We'll try to reproduce the problem by creating a serializable class containing
a transient field, then serialize and deserialize it and see what results. Let's define a class called SerTest that contains an int instance field, a String instance field, and a private transient int instance field. Then, we'll put print() statements in the code at specific places to see what the variable values are.
Just for fun, we'll give SerTest a base class called SerTestBase, which is not serializable but which also contains a transient field. (You'll see why shortly.) SerTest's transient field has a static initializer expression that initializes it to 12, and SerTestBase statically initializes its transient to 99. Here's the source code for the test class and its main routine:
001
002 import java.lang.*;
003 import java.io.*;
004
005 // Note that the base class is not serializable
006 class SerTestBase extends java.lang.Object
007 {
008 private transient int iTransient_ = 99;
009
010 public SerTestBase() {
011 System.out.println("Called SerTestBase no-arg constructor");
012 }
013
014 public void print() {
015 System.out.println("Base: iTransient_ = " + iTransient_);
016 }
017 }
018
019 // Serializable subclass
020 class SerTest extends SerTestBase implements java.io.Serializable {
021
022 protected int a_ = 25;
023 protected String sTitle_ = new String("");
024 private transient int iNotSerialized_ = 12;
025
026 public SerTest() {
027 System.out.println("Default constructor");
028 print();
029 }
030
031 public SerTest(int a, String s) {
032 System.out.println("2-arg constructor");
033 a_ = a;
034 sTitle_ = s;
035 print();
036 }
037
038 // How to load myself from a stream
039 private void readObject(java.io.ObjectInputStream in)
040 throws IOException, ClassNotFoundException {
041 System.out.println("BEFORE READ");
042 print();
043 in.defaultReadObject();
044 System.out.println("AFTER READ");
045 print();
046 }
047
048 public void print() {
049 super.print();
050 System.out.println("a=" + a_ + ", s=" + sTitle_ +
051 ", NotSerialized = " + iNotSerialized_);
052 }
053
054 // How to write myself to a stream
055 private void writeObject(java.io.ObjectOutputStream out)
056 throws IOException {
057 out.defaultWriteObject();
058 }
059
060 // Properties
061 public void setTitle(String sTitle) { sTitle_ = sTitle; }
062 public String getTitle() { return sTitle_; }
063
064 public void setA(int aa) { a_ = aa; }
065 public int getA() { return a_; }
066 };
067
068 //
069 // main()
070 //
071 class Demo1 {
072
073 private static void Usage() throws java.io.IOException {
074 System.out.println("Usage:\n\tDemo1 w file a string\n\tDemo1 r file");
075 IOException ex = new IOException("ERROR");
076 throw ex;
077 }
078
079 public static void main(String[] args)
080 {
081 String cmd = args[0];
082
083 try {
084 if (cmd.compareTo("w") == 0) {
085 if (args.length != 4) { Usage(); }
086
087 int aa = Integer.parseInt(args[2]);
088 String ss = args[3];
089
090 SerTest bar = new SerTest(aa, ss);
091
092 FileOutputStream f = new FileOutputStream(args[1]);
093 ObjectOutputStream s = new ObjectOutputStream(f);
094
095 System.out.println("Write: a=" + aa + ", s='" + ss + "'");
096
097 s.writeObject(bar);
098 s.flush();
099 } else if (cmd.compareTo("r") == 0) {
100 if (args.length != 2) { Usage(); }
101
102 FileInputStream f = new FileInputStream(args[1]);
103 ObjectInputStream s = new ObjectInputStream(f);
104
105 System.out.println("Read SerTest:");
106
107 SerTest bar = (SerTest) s.readObject();
108
109 System.out.println("RECEIVED OBJECT:");
110 bar.print();
111 } else {
112 System.err.println("Unknown command " + cmd);
113 Usage();
114 }
115 }
116
117 catch (Exception ex) {
118 System.out.println("Exception: " + ex.getMessage());
119 ex.printStackTrace();
120 }
121 }
122 };
The Demo1 class writes or reads a SerTest object to or from a disk file, and tells the object to print itself after construction. So, let's run it and see what happens.
First, we tell the object to serialize; here's the result: