The function of transient keyword and the solution of several questions

[TOC]

1. From Serilizable to transient

We know that if an object needs to be serialized, it needs to implement the serializable interface, and all non static attributes of this class will be serialized.

Note: the above mentioned non static attributes, because static attributes belong to classes, not class objects, and serialization is an operation for class objects, so this will not be serialized at all. Now we can experiment:
Entity class teacher class:

import java.io.Serializable;

class Teacher implements Serializable {
    public int age;
    public static String SchoolName;

    public Teacher(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                '}';
    }
}

Test code serialtest Java, the basic idea is that when initializing, the static attribute SchoolName is "Dongfang primary school". After serializing the object, modify the static attribute, and then deserialize it. It is found that the static variable is still modified, indicating that the static variable has not been serialized.

import java.io.*;

public class SerialTest {
    public static void main(String[] args) {
        Teacher.SchoolName = "Oriental primary school";
        serial();
        Teacher.SchoolName = "Western primary school";
        deserial();
        System.out.println(Teacher.SchoolName);
    }
    // serialize
    private static void serial(){
        try {
            Teacher teacher = new Teacher(9);
            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(teacher);
            objectOutputStream.flush();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
    // Deserialization
    private static void deserial() {
        try {
            FileInputStream fis = new FileInputStream("Teacher.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Teacher teacher = (Teacher) ois.readObject();
            ois.close();
            System.out.println(teacher.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The output result proves that the static variable has not been serialized!!!

Teacher{age=9}
Western primary school

2. The class that serializes the attribute object needs to implement the serializable interface?

I suddenly thought of a question. If some attributes are objects rather than basic types, do you need to change the type of attributes to realize Serilizable?

The answer to the question is: Yes!!!

The following is the experimental process:

First, there is a teacher Java, which implements Serializable. There is an attribute of School type:

import java.io.Serializable;

class Teacher implements Serializable {
    public int age;

    public School school;
    public Teacher(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                '}';
    }
}

School type, not Serializable:

public class School {
    public String name;

    public School(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                '}';
    }
}

Test code, we only test serialization:

import java.io.*;

public class SerialTest {
    public static void main(String[] args) {
        serial();
    }

    private static void serial(){
        try {
            Teacher teacher = new Teacher(9);
            teacher.school = new School("Oriental primary school");
            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(teacher);
            objectOutputStream.flush();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

}

The reason why we want to serialize the object is that the serialized object can not be serialized. If we want to serialize the object, we will find that there is no error message. Therefore, the reason why we want to serialize the object is that there is no error message.

java.io.NotSerializableException: com.aphysia.transienttest.School
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.aphysia.transienttest.SerialTest.serial(SerialTest.java:18)
    at com.aphysia.transienttest.SerialTest.main(SerialTest.java:9)

When we implemented the serialization interface of School, we found that everything was normal Perfect solution to the problem

3. What about fields that do not want to be serialized?

However, if a variable is not a static variable, but we do not want to serialize it, because it may be some sensitive fields such as passwords, or it is a less important field. We do not want to increase the message size, so we want to exclude this field from the serialized message. Or change the field to store the reference address, not the really important data, such as elementData in ArrayList.

At this time, you need to use the transient keyword to mask the changed field.

When we decorate School with transient:

import java.io.Serializable;

class Teacher implements Serializable {
    public int age;

    public transient School school;
    public Teacher(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                ", school=" + school +
                '}';
    }
}
import java.io.Serializable;

public class School implements Serializable {
    public String name;

    public School(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                '}';
    }
}

Execute the following code for serialization and deserialization:

import java.io.*;

public class SerialTest {
    public static void main(String[] args) {
        serial();
        deserial();
    }

    private static void serial(){
        try {
            Teacher teacher = new Teacher(9);
            teacher.school = new School("Oriental primary school");
            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(teacher);
            objectOutputStream.flush();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    private static void deserial() {
        try {
            FileInputStream fis = new FileInputStream("Teacher.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Teacher teacher = (Teacher) ois.readObject();
            ois.close();
            System.out.println(teacher.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The execution results are as follows. You can see that the deserialization of the teacher field is actually null, which is also the function of transient.
Note, however, that transient can only modify variables, but not classes and methods,

4. The elementData in ArrayList is modified by the transient keyword. Why can ArrayList be serialized?

Here, since transient modifies the data node of ArrayList, why can we still see the data node of ArrayList when serializing?
This is because when serializing:

If you only implement the Serializable interface, you must call Java io. ObjectOutputStream. Defaultwriteobject() method to serialize the object. Then, if the attribute is modified by transient, it must not be serialized.
However, if we have implemented the Serializable interface and modified the attribute by transient, the attribute will not be in the default Java io. ObjectOutputStream. The defaultwriteobject () method is serialized, but we can rewrite a writeObject() method. In this way, writeObject() instead of Java is called during serialization io. ObjectOutputStream. defaultWriteObject().

The following source code is objectinputstream Writeobject (object obj). In fact, the underlying layer will call the writeObject() method of the rewritten object in a reflective way. There is no expansion here.

    public final void writeObject(Object obj) throws IOException {
        // If it can be overridden, the overridden method is called
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

The writeobject() method rewritten by ArrayList is as follows:

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        // Default method for serializing objects
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            // Value of serialized object
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

We can see that the default method defaultWriteObject() is called in writeobject(), and the bottom layer of defaultWriteObject() is changed to writeObject0(). The idea of writeobject () rewritten by ArrayList is to serialize the default, then the array size, and then the real elements in the array elementData. This achieves the purpose of serializing the real content of the element.

5. Besides transient, is there any other way to mask deserialization?

Wait a minute, ask this question, the answer must be there!!! That is the Externalizable interface.

Specific situation: Externalizable means that there are many attributes in the class, but I only want some and shield most. Then I don't want to add the keyword transient in front of most attributes. I just want to identify my serialized fields. At this time, I need to use the Externalizable interface.

show me the code!

First define a person Java, which has three properties

  • Age: age
  • Name: name (modified by transient)
  • score: score

If the Externalizable interface is implemented, the writeExternal() and readExternal() methods must be implemented.

  • writeExternal: Custom serialize the attributes to be serialized
  • readExternal: Custom deserialization of attributes that need to be deserialized
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable {
    public int age;
    public transient String name;
    public int score;

    // A parameterless constructor must be implemented
    public Person() {
    }

    public Person(int age, String name, int score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        /*
         * Specifies the attribute to be written during serialization. score is not written here
         */
        out.writeObject(age);
        out.writeObject(name);
        out.writeObject(score);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        /*
         * Specifies the attribute to be written during serialization. Age is still not written here
         */
        this.age = (int)in.readObject();
        this.name = (String)in.readObject();
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score='" + score + '\'' +
                '}';
    }
}

From the above code, we can see that all three attributes are written in during serialization, but only two attributes are restored during deserialization. Let's take a look at the test code:

import java.io.*;

public class ExternalizableTest {
    public static void main(String[] args) {
        serial();
        deserial();
    }

    private static void serial(){
        try {
            Person person = new Person(9,"Sam",98);
            FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(person);
            objectOutputStream.flush();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    private static void deserial() {
        try {
            FileInputStream fis = new FileInputStream("person.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person person = (Person) ois.readObject();
            ois.close();
            System.out.println(person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The test results are as follows. In fact, the first two are deserialized successfully. The latter one is because we didn't customize the deserialization of this attribute when rewriting, so it's normal

Person{age=9, name='Sam', score='0'}

If you are more careful, you can find that a field is modified by transient. It doesn't mean that if it is modified, it will not be serialized. How can it be serialized.

Yes, as long as the Externalizable interface is implemented, it will not be controlled by transient. It will only be serialized and deserialized according to our custom fields. The transient here is invalid

For the serialized transient, please stay going~

This article only represents your own learning accumulation records or learning notes. If there is infringement, please contact the author to delete it. No one is perfect, so is the article. Your writing is immature. Don't spray if you don't have talent. If there are mistakes, please point them out. Thank you very much~

The road of technology is not for a while. The mountains are high and the rivers are long. Even if it is slow, it will not stop.

Official account: Qinhuai grocery store

Tags: Java source code

Posted by creativeimpact on Thu, 05 May 2022 07:48:11 +0300