The article mainly involves the following issues:
1. Serialization of Java objects
The Java platform allows us to create reusable Java objects in memory, but in general, these objects can only exist when the JVM is running, i.e., the life cycle of these objects will not be longer than that of the JVM. However, in real applications, it may be necessary to save (persist) the specified object after the JVM is stopped and re-read the saved object in the future. Java object serialization can help us implement this function.
Using Java object serialization, when saving an object, its state will be saved as a set of bytes, and in the future, these bytes will be assembled into an object. It must be noted that the object serialization saves the "state" of the object, that is, its member variables. From this we can see that object serialization does not focus on static variables in the class.
In addition to using object serialization when persisting objects, object serialization is used when using RMI (remote method call) or passing objects on the network. The Java serialization API provides a standard mechanism for handling object serialization. This API is simple and easy to use and will be discussed in subsequent chapters of this article.
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; transient Object[] elementData; // non-private to simplify nested class access private int size;}2. How to serialize and deserialize Java objects
In Java, as long as a class implements the java.io.Serializable interface, it can be serialized. Here is a piece of code:
code 1 Create a User class for serialization and deserialization
package com.hollis;import java.io.Serializable;import java.util.Date;/** * Created by hollis on 16/2/2. */public class User implements Serializable{ private String name; private int age; private Date birthday; private transient String gender; private static final long serialVersionUID = -6849794470754667710L; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "name='" + name + '/'' + ", age=" + age + ", gender=" + gender + ", birthday=" + birthday + '}'; }}code 2 Demo for serializing and deserializing the User
package com.hollis;import org.apache.commons.io.FileUtils;import org.apache.commons.io.IOUtils;import java.io.*;import java.util.Date;/** * Created by hollis on 16/2/2. */public class SerializableDemo { public static void main(String[] args) { //Initializes The Object User user = new User(); user.setName("hollis"); user.setGender("male"); user.setAge(23); user.setBirthday(new Date()); System.out.println(user); //Write Obj to File ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(user); } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(oos); } //Read Obj from File File file = new File("tempFile"); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(file)); User newUser = (User) ois.readObject(); System.out.println(newUser); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(ois); try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); } } } }}//output //User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}3. Knowledge related to serialization and deserialization
1. In Java, as long as a class implements the java.io.Serializable interface, it can be serialized.
2. Serialize and deserialize objects through ObjectOutputStream and ObjectInputStream
3. Whether the virtual machine allows deserialization not only depends on whether the classpath and function code are consistent. A very important point is whether the serialization IDs of the two classes are consistent (that is, private static final long serialVersionUID)
4. Serialization does not save static variables.
5. To serialize the parent class object, you need to make the parent class implement the Serializable interface.
6. The function of the Transient keyword is to control the serialization of variables. Adding this keyword before the variable declaration can prevent the variable from being serialized into the file. After being deserialized, the value of the transient variable is set to the initial value, such as the int type is 0 and the object type is null.
7. The server sends serialized object data to the client. Some data in the object is sensitive, such as password strings, etc. It is hoped that the password field will be encrypted when serialized. If the client has a decrypted key, it can only read the password when deserializing the client, which can ensure the data security of the serialized object to a certain extent.
4. Serialization of ArrayList
Before introducing ArrayList serialization, let’s consider a question:
How to customize serialization and deserialization strategies
With this question, let's look at the source code of java.util.ArrayList
code 3
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; transient Object[] elementData; // non-private to simplify nested class access private int size;}The author omits other member variables. From the above code, we can know that ArrayList implements the java.io.Serializable interface, so we can serialize and deserialize it. Because elementData is transient, we believe that this member variable will not be serialized and retained. Let's write a demo to verify our ideas:
code 4
public static void main(String[] args) throws IOException, ClassNotFoundException { List<String> stringList = new ArrayList<String>(); stringList.add("hello"); stringList.add("world"); stringList.add("hollis"); stringList.add("chuang"); System.out.println("init StringList" + stringList); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist")); objectOutputStream.writeObject(stringList); IOUtils.close(objectOutputStream); File file = new File("stringlist"); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); List<String> newStringList = (List<String>)objectInputStream.readObject(); IOUtils.close(objectInputStream); if(file.exists()){ file.delete(); } System.out.println("new StringList" + newStringList); }//init StringList[hello, world, hollis, chang]//new StringList[hello, world, hollis, chang]Anyone who knows ArrayList knows that the underlying layer of ArrayList is implemented through arrays. Then the array elementData is actually used to save elements in the list. Through the declaration method of this attribute, we know that it cannot be persisted through serialization. So why does the result of code 4 retain the elements in List through serialization and deserialization?
5. WriteObject and readObject methods
In ArrayList, a method is defined: writeObject and readObject.
Here is a conclusion:
During the serialization process, if the writeObject and readObject methods are defined in the serialized class, the virtual machine tries to call the writeObject and readObject methods in the object class to perform user-defined serialization and deserialization.
If there is no such method, the default calls are the defaultWriteObject method of ObjectOutputStream and the defaultReadObject method of ObjectInputStream.
User-defined writeObject and readObject methods can allow users to control the serialization process, such as dynamically changing the serialized value during the serialization process.
Let’s take a look at the specific implementation of these two methods:
code 5
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }code 6
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioral compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }So why does ArrayList need to use this method to achieve serialization?
Why transient
ArrayList is actually a dynamic array. Each time it is filled, it will automatically increase the set length value. If the automatic growth length of the array is set to 100, and only one element is actually placed, then 99 null elements will be serialized. In order to ensure that so many nulls will not be serialized at the same time during serialization, ArrayList sets the element array to transient.
Why writeObject and readObject
As mentioned earlier, in order to prevent an array containing a large number of empty objects from being serialized and to optimize storage, ArrayList uses transient to declare elementData.
However, as a collection, it is also necessary to ensure that the elements in it can be persisted during the serialization process. Therefore, the elements in it are preserved by rewriting the writeObject and readObject methods.
The writeObject method saves the elements in the elementData array traversal to the output stream (ObjectOutputStream).
The readObject method reads the object from the input stream (ObjectInputStream) and saves the assignment to the elementData array.
At this point, let's try to answer the question we just asked:
1. How to customize serialization and deserialization strategies
Answer: You can add writeObject and readObject methods to the serialized class. 2. Then the question comes again:
Although the writeObject and readObject methods are written in ArrayList, these two methods are not displayed and are called.
So if a class contains writeObject and readObject methods, then how are these two methods called?
6. ObjectOutputStream
From code 4, we can see that the serialization process of objects is implemented through ObjectOutputStream and ObjectInputStream. So with the question just now, let’s analyze how the writeObject and readObject methods in ArrayList are called?
To save space, here is the call stack of writeObject of ObjectOutputStream:
writeObject ---> writeObject0 --->writeOrdinaryObject --->writeSerialData --->invokeWriteObject
Here is a look at invokeWriteObject:
void invokeWriteObject(Object obj, ObjectOutputStream out) throws IOException, UnsupportedOperationException { if (writeObjectMethod != null) { try { writeObjectMethod.invoke(obj, new Object[]{ out }); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof IOException) { throw (IOException) th; } else { throwMiscException(th); } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }where writeObjectMethod.invoke(obj, new Object[]{ out }); is the key, and the writeObjectMethod method is called through reflection. This is how the official explains the writeObjectMethod:
class-defined writeObject method, or null if none
In our example, this method is the writeObject method we define in ArrayList. It was called through reflection.
At this point, let's try to answer the question we just asked:
If a class contains writeObject and readObject methods, how are these two methods called?
Answer: When using the writeObject method of ObjectOutputStream and the readObject method of ObjectInputStream, it will be called in reflection.
So far, we have introduced the serialization method of ArrayList. So, I wonder if anyone has raised such a question:
Serializable is obviously an empty interface. How does it ensure that only methods that implement this interface can be serialized and deserialized?
Definition of Serializable interface:
public interface Serializable {}Readers can try to remove the code inherited from Serializable in code 1, and then execute code 2, which will throw a java.io.NotSerializableException.
In fact, this question is also easy to answer. Let's go back to the call stack of writeObject in ObjectOutputStream just now:
writeObject ---> writeObject0 --->writeOrdinaryObject --->writeSerialData --->invokeWriteObject
There is a piece of code in the writeObject0 method:
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()); } }When performing serialization operations, it will determine whether the class to be serialized is of Enum, Array, and Serializable types. If not, a NotSerializableException will be directly thrown.
Summarize
1. If a class wants to be serialized, it needs to implement the Serializable interface. Otherwise, a NotSerializableException will be thrown, because the type will be checked during the serialization operation, requiring that the serialized class must belong to any of the Enum, Array and Serializable types.
2. Adding this keyword before the variable declaration can prevent the variable from being serialized into the file.
3. Adding writeObject and readObject methods to the class can implement custom serialization strategies
The above is all about this article, I hope it will be helpful to everyone's learning.