Reprinted fromHttp://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html
The article on Java serialization has long been full of resources. This article is a summary of my past studies, understanding and application of Java serialization. This article covers the basic principles of Java serialization and various methods to customize the serialization form. At the time of writing this article, I have referenced thinking in Java, objective Java, javaworld, related articles in developerworks and other network materials, and also added my own practical experience and understanding, I hope to help you. (Last updated on February 14)
1. What is Java object serialization?
The Java platform allows us to create reusable Java objects in the memory. However, these objects can exist only when the JVM is running. That is, the lifecycle of these objects is no longer than that of JVM. However, in real applications, it may be required to save (persistently) specified objects after the JVM stops running, and re-read the saved objects in the future. Java object serialization can help us implement this function.
When Java object is used for serialization, the state of an object is saved as a group of bytes. In the future, these bytes will be assembled into objects. It must be noted that the object is serialized to save the "State" of the object, that is, its member variables. We can see that object serialization does not involve static variables in the serialize class.
In addition to object serialization, Object serialization is used when RMI (remote method call) or an object is transmitted on the network. Java serialization API provides a standard mechanism for processing object serialization. This API is easy to use and will be discussed later in this article.
2. Simple Example
In Java, as long as a class implements the java. Io. serializable interface, it can be serialized. A serializable class person will be created here. All examples in this article will be centered around this class or its modified version.
Gender class, which is an enumeration type, indicating gender
public enum Gender { MALE, FEMALE}
If you are familiar with Java enumeration types, you should know that each Enumeration type inherits the java. Lang. Enum class by default, and this class implements the serializable interface, so Enumeration type objects can be serialized by default.
The person class implements the serializable interface. It contains three fields: name, String, age, integer, gender, and gender. In addition, the tostring () method of the class is rewritten to print the content in the person instance.
public class Person implements Serializable { private String name = null; private Integer age = null; private Gender gender = null; public Person() { System.out.println("none-arg constructor"); } public Person(String name, Integer age, Gender gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } @Override public String toString() { return "[" + name + ", " + age + ", " + gender + "]"; }}
Simpleserial is a simple serialization program, which first saves a person object to the file person. and then read the stored person object from the file, and print the object.
Public class simpleserial {public static void main (string [] ARGs) throws exception {file = new file ("person. out "); objectoutputstream oout = new objectoutputstream (New fileoutputstream (File); person = new person (" John ", 101, gender. male); oout. writeobject (person); oout. close (); objectinputstream oin = new objectinputstream (New fileinputstream (File); object newperson = oin. readobject (); // It is not forcibly converted to the person type oin. close (); system. out. println (newperson );}}
The output result of the above program is:
arg constructor[John, 31, MALE]
Note that when you re-read the person object to be saved, the system does not call any constructor of the person object, which looks like restoring the person object directly using bytes.
When the person object is saved to person. after the out file, we can read the file elsewhere to restore the object, but we must ensure that the classpath of the read program contains person. class (even if the person class is not displayed when reading the person object, as shown in the above example), otherwise classnotfoundexception will be thrown.
3. Functions of serializable
Why can a class be serialized when the serializable interface is implemented? In the preceding example, objectoutputstream is used to persist objects. The following code is available in this class:
private void writeObject0(Object obj, boolean unshared) throws IOException { if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } }
The code above shows that if the type of the written object is string, array, Enum, or serializable, the object can be serialized; otherwise, notserializableexception will be thrown.
4. Default serialization Mechanism
If you only want a class to implement the serializable interface without any other processing, the default serialization mechanism is used. Using the default mechanism, when serializing an object, not only the current object itself will be serialized, but other objects referenced by this object will also be serialized. Similarly, other Objects referenced by these other objects will also be serialized, and so on. Therefore, if an object contains a member variable that is a container-class object, and the elements contained in these containers are also a container-class object, the serialization process will be complicated and costly.
5. Impact on serialization
In practical applications, the default serialization mechanism is sometimes unavailable. For example, you want to ignore sensitive data or simplify the serialization process. The following describes several methods that affect serialization.
5.1 transient keyword
When a field is declared as transient, the default serialization mechanism ignores this field. Here, the age field in the person class is declared as transient, as shown below,
public class Person implements Serializable { transient private Integer age = null; }
Run the simpleserial application and the following output is displayed:
arg constructor[John, null, MALE]
It can be seen that the age field is not serialized.
5.2 writeobject () and readobject () Methods
In addition to removing the transitive keyword, is there any other way to serialize the field age declared as transitive? One of the methods is to add two methods to the person class: writeobject () and readobject (), as shown below:
public class Person implements Serializable { transient private Integer age = null; private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); }}
In the writeobject () method, the defaultwriteobject () method in objectoutputstream is called first. This method executes the default serialization mechanism. As described in section 5.1, the age field is ignored. Then, call the writeint () method to display and write the age field to objectoutputstream. Readobject () is used to read objects. Its principle is the same as that of writeobject.
If you run the simpleserial application again, the following output is displayed:
arg constructor[John, 31, MALE]
Note that both writeobject () and readobject () are private methods. How are they called? There is no doubt that reflection is used. For details, you can see the writeserialdata method in objectoutputstream and the readserialdata method in objectinputstream.
5.3 externalizable Interface
Both the transient keyword and the writeobject () and readobject () methods are serialized Based on the serializable interface. JDK provides another serialization interface, externalizable. After this interface is used, the serialization mechanism based on the serializable interface will become invalid. Modify the person class to the following,
public class Person implements Externalizable { private String name = null; transient private Integer age = null; private Gender gender = null; public Person() { System.out.println("none-arg constructor"); } public Person(String name, Integer age, Gender gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } @Override public void writeExternal(ObjectOutput out) throws IOException { } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { } }
After executing the simpleserial program, the following result is displayed:
arg constructornone-arg constructor[null, null, null]
From this result, we can see that no field in the person object is serialized. On the other hand, if you are careful, you can also find that this serialization process calls the no-argument constructor of the person class.
Externalizable inherits from serializable. When this interface is used, the serialization details need to be completed by the programmer. As shown in the code above, since the writeexternal () and readexternal () methods are not processed, this serialization will not save/read any field. This is why the values of all fields in the output result are empty.
In addition, if externalizable is used for serialization, when an object is read, a non-parameter constructor of the serialized class is called to create a new object, then, the Field Values of the saved object are filled in the new object. This is why the non-parameter constructor of the person class is called during the serialization process. For this reason, the class implementing the externalizable interface must provide a constructor without parameters and its access permission is public.
Further modify the person class to serialize the name and age fields, but ignore the gender field, as shown in the following code:
public class Person implements Externalizable { private String name = null; transient private Integer age = null; private Gender gender = null; public Person() { System.out.println("none-arg constructor"); } public Person(String name, Integer age, Gender gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(age); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = in.readInt(); } }
After simpleserial is executed, the following results are displayed:
arg constructornone-arg constructor[John, 31, null]
5.4 readresolve () method
When we use the singleton mode, it is expected that the instance of a class should be unique, but if the class is serializable, the situation may be slightly different. In this case, modify the person class used in section 2nd to implement the singleton mode, as shown below:
public class Person implements Serializable { private static class InstanceHolder { private static final Person instatnce = new Person("John", 31, Gender.MALE); } public static Person getInstance() { return InstanceHolder.instatnce; } private String name = null; private Integer age = null; private Gender gender = null; private Person() { System.out.println("none-arg constructor"); } private Person(String name, Integer age, Gender gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; } }
At the same time, modify the simpleserial application to save/obtain the preceding singleton object and compare the object equality, as shown in the following code:
Public class simpleserial {public static void main (string [] ARGs) throws exception {file = new file ("person. out "); objectoutputstream oout = new objectoutputstream (New fileoutputstream (File); oout. writeobject (person. getinstance (); // Save the singleton object oout. close (); objectinputstream oin = new objectinputstream (New fileinputstream (File); object newperson = oin. readobject (); oin. close (); system. out. println (newperson); system. out. println (person. getinstance () = newperson); // compare the obtained object with the singleton object in the person class }}
After the preceding application is executed, the following result is displayed:
arg constructor[John, 31, MALE]false
It is worth noting that the person object obtained from the file person. out is not the same as the singleton object in the person class. To maintain the singleton feature during serialization, you can add a readresolve () method to the person class to directly return the singleton object of person in this method, as shown below:
public class Person implements Serializable { private static class InstanceHolder { private static final Person instatnce = new Person("John", 31, Gender.MALE); } public static Person getInstance() { return InstanceHolder.instatnce; } private String name = null; private Integer age = null; private Gender gender = null; private Person() { System.out.println("none-arg constructor"); } private Person(String name, Integer age, Gender gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.gender = gender; } private Object readResolve() throws ObjectStreamException { return InstanceHolder.instatnce; } }
After executing the simpleserial application in this section again, the following output is displayed:
arg constructor[John, 31, MALE]true
Whether it is the serializable interface or the externalizable interface, the readresolve () method is called when an object is read from an I/O Stream. In fact, the object returned in readresolve () is used to directly replace the object created in the deserialization process, and the object created will be reclaimed by garbage collection.
(Updating ...)