objective

This series is intended to give you a solid grasp of the basics of Java, using the most superficial text and code examples.

One, foreword

We learn JDK source code, if you pay attention, should often see the keyword: TRANSIENT! But it is not known how many people actually understand the use of this keyword.

Two, the first transient

Transient, Chinese meaning: transient! So, what does it do? This is where the Java persistence mechanism comes in.

2.1. Java Persistence

Java provides us with a very convenient persistent instance mechanism: Serializable!

One might say: isn’t Serializable for serialization and deserialization? Well, yeah! Right, so what does an instance serialize for?

Serialization of an instance can be used for data transfer, however, its greatest use is for writing to disk to prevent data loss.

2.2. Java serialization

When we write classes that serialize/deserialize, we implement Serializable, but sometimes we don’t want to serialize some fields, so we can use the keyword TRANSIENT to modify the field.

// BankCard.java
import java.io.Serializable;

public class BankCard implements Serializable {
    private String cardNo;
    private transient String password;

    public String getCardNo(a) {
        return cardNo;
    }

    public void setCardNo(String cardNo) {
        this.cardNo = cardNo;
    }

    public String getPassword(a) {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString(a) {
        return "BankCard{cardNo='" + cardNo + '\' ' + ", password='" + password + '\' ' + '} '; }}Copy the code

Writing test classes

public class Main {
    public static void main(String[] args) {
        try {
            serializeBankCard();
            Thread.sleep(2000);
            deSerializeBankCard();
        } catch(Exception e) { e.printStackTrace(); }}private static void serializeBankCard(a) throws Exception {
        BankCard bankCard = new BankCard();
        bankCard.setCardNo("CardNo: 1234567890");
        bankCard.setPassword("* * * * * * * *");

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bank.txt"));
        os.writeObject(bankCard);
        os.close();
        System.out.println("Serialization" + bankCard.toString());
    }

    private static void deSerializeBankCard(a) throws Exception {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bank.txt"));
        BankCard bankCard = (BankCard) is.readObject();
        is.close();
        System.out.println("Deserialize"+ bankCard.toString()); }}// Print the output
CardNo: / / serialization BankCard {cardNo = '1234567890' and password = '* * * * * * * *}
BankCard{cardNo=' cardNo: 1234567890', password='null'}
Copy the code

As you can see, we first created the “bankcard” class, and then serialized the data to disk. After 2 seconds, we read the data from disk again and printed the “password” field as “NULL”.

If we open the file on disk and look at the data in hexadecimal, we can also see that in the file, only “cardNo”, no “password” :

Third, in-depth analysis of transient

Before I dive in, LET me ask you a few questions, and then I’ll take you through them one by one:

  • Transient implementation principle;
  • Transient field really cannot be serialized?
  • Can static variables be serialized?
  • Can transient + static fields be serialized?

OK, with the above problems, we go to see the source!

3.1, the Serializable class

/ * * *... * The writeObject method is responsible for writing the state of the * object for its particular class so that the corresponding * readObject method can restore it. The default mechanism for saving * the Object's fields can be invoked by calling * out.defaultWriteObject. The method does not need to concern * itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * The readObject method  is responsible for reading from the stream and * restoring the classes fields. It may call in.defaultReadObject to invoke * the default mechanism for restoring the object's non-static and * non-transient fields. * * ......... * *@see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @sinceJDK1.1 * /
public interface Serializable {}Copy the code

The Serializable comment says:

  • WriteObject writes data and readObject reads data.
  • Read and write data (state), regardless of its parent or subclass;
  • ObjectOutputSteam is a write based on DataOutput implementation;
  • ReadObject reads streams and is stored in class member variables. Its default value is stored in non-static and non-transient members.

3.2, the ObjectOutputStream class

The default Serializable mechanism writes only class objects, signatures, and non-transient and non-static fields

/* * The default serialization mechanism for an object writes the class of the * object, the class signature, and the values of all non-transient and * non-static fields. */
 public class ObjectOutputStream extends OutputStream implements ObjectOutput.ObjectStreamConstants {
    private final BlockDataOutputStream bout;
    private static class BlockDataOutputStream extends OutputStream implements DataOutput {... }public final void writeObject(Object obj) throws IOException {...try {
            writeObject0(obj, false); }... }private void writeObject0(Object obj, boolean unshared) throws IOException {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try{... ObjectStreamClass desc;for(;;) {...// Key: Get desc of Serializable object
                // fields in desc filter static and transient
                // See 3.3 for details on the code flow
                desc = ObjectStreamClass.lookup(cl, true); . }}else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared); // obj serializes data from desc.fields}... }finally{ depth--; bout.setBlockDataMode(oldMode); }}private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared) throws IOException {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\"," + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();
            // Finally call bout to write data
            bout.writeByte(TC_OBJECT);
            
            // All writeXXX will call bout to write data
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);
            if(desc.isExternalizable() && ! desc.isProxy()) { writeExternalData((Externalizable) obj); }else{ writeSerialData(obj, desc); }}finally {
            if(extendedDebugInfo) { debugInfoStack.pop(); }}}}Copy the code

3.3, ObjectStreamClass class

3.3.1, ObjectStreamClass. Lookup

public class ObjectStreamClass implements Serializable {
    static ObjectStreamClass lookup(Class<? > cl,boolean all) {...if (entry == null) {
            try {
                entry = new ObjectStreamClass(cl);
            } catch(Throwable th) { entry = th; }... }... }}Copy the code

ObjectStreamClass constructor

public class ObjectStreamClass implements Serializable {
    private ObjectStreamClass(finalClass<? > cl) {...if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {...try {
                        fields = getSerialFields(cl);  // Get serializable fields
                        computeFieldOffsets();
                    } catch(InvalidClassException e) { ...... fields = NO_FIELDS; }... } } } } }Copy the code

3.3.3, ObjectStreamClass getSerialFields

public class ObjectStreamClass implements Serializable {
    private staticObjectStreamField[] getSerialFields(Class<? > cl)throws InvalidClassException {
        ObjectStreamField[] fields;
        if(Serializable.class.isAssignableFrom(cl) && ! Externalizable.class.isAssignableFrom(cl) && ! Proxy.isProxyClass(cl) && ! cl.isInterface()) {if ((fields = getDeclaredSerialFields(cl)) == null) {
                fields = getDefaultSerialFields(cl);
            }
            Arrays.sort(fields);
        } else {
            fields = NO_FIELDS;
        }
        returnfields; }}Copy the code

3.3.4, ObjectStreamClass getDefaultSerialFields

public class ObjectStreamClass implements Serializable {
    private staticObjectStreamField[] getDefaultSerialFields(Class<? > cl) { Field[] clFields = cl.getDeclaredFields(); ArrayList<ObjectStreamField> list =new ArrayList<>();
        int mask = Modifier.STATIC | Modifier.TRANSIENT; // Focus 1: mask
    
        for (int i = 0; i < clFields.length; i++) {
            // Key 2: filter static or transient
            if ((clFields[i].getModifiers() & mask) == 0) {
                list.add(new ObjectStreamField(clFields[i], false.true)); }}int size = list.size();
        return (size == 0)? NO_FIELDS : list.toArray(newObjectStreamField[size]); }}Copy the code

3.4 breakpoint View runtime JDK source code

A simple look at the above source, at least answered us so a few questions:

  • Static variables cannot be serialized, whether transient modifiers are added or not;
  • Transient variables cannot be serialized;

So, one more question: can transient variables really not be serialized?

Before I answer that question, do you know of any other ways Java provides Serializable serialization?

The answer is: Java provides two ways to serialize

  1. Serializable, serialization and deserialization are implemented by Java (default mechanism of ObjectOutputStream/ObjectInputStream);
  2. Externalizable, serialization and deserialization are realized independently by developers;

Let’s start with an example

// BankCardExt.java
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class BankCardExt implements Externalizable {
    private String cardNo;
    private transient String password;

    public String getCardNo(a) {
        return cardNo;
    }

    public void setCardNo(String cardNo) {
        this.cardNo = cardNo;
    }

    public String getPassword(a) {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(cardNo);
        out.writeObject(password);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        cardNo = (String) in.readObject();
        password = (String) in.readObject();
    }

    @Override
    public String toString(a) {
        return "BankCardExt{cardNo='" + cardNo + "\', password='" + password + "\ '}"; }}Copy the code

The reading and writing of this class is done by us.

Let’s do another test class

public class Main {

    public static void main(String[] args) {
        try {
            serializeBankCard();
            Thread.sleep(2000);
            deSerializeBankCard();
        } catch(Exception e) { e.printStackTrace(); }}private static void serializeBankCard(a) throws Exception {
        BankCardExt bankCard = new BankCardExt();
        bankCard.setCardNo("CardNo: 1234567890");
        bankCard.setPassword("* * * * * * * *");

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bankExt.txt"));
        os.writeObject(bankCard);
        os.close();
        System.out.println("Serialization" + bankCard.toString());
    }

    private static void deSerializeBankCard(a) throws Exception {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bankExt.txt"));
        BankCardExt bankCard = (BankCardExt) is.readObject();
        is.close();
        System.out.println("Deserialize"+ bankCard.toString()); }}// Print the output
// serialize BankCardExt{cardNo=' cardNo: 1234567890', password='********'}
// deserialize BankCardExt{cardNo=' cardNo: 1234567890', password='********'}
Copy the code

And we see that when we read it, the data comes back.

Looking at the file, we also see “cardNo” and “password”.

One more note: Write in the same order as you read! If we are inconsistent, the result will be as follows:

{
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(cardNo);
        out.writeObject(password);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { password = (String) in.readObject(); cardNo = (String) in.readObject(); }}// Print the output
// serialize BankCardExt{cardNo=' cardNo: 1234567890', password='********'}
BankCardExt{cardNo='********', password=' cardNo: 1234567890'}
Copy the code

When we implement Externalizable, we need to choose which fields to serialize and deserialize, so the TRANSIENT modifier will have no effect.

Four,

This paper analyzes transient serialization underlying mechanism:

  • When our class implements Serializable, transient variables can only exist in memory, and ObjectOutputStream ignores transient and static variables.
  • When our class implements Externalizable, the transient keyword will be invalid, and the serialization and deserialization of the class’s member variables will be controlled by us.