一些收藏Android开发

Java小白系列(一):关键字transient

2021-01-27  本文已影响0人  青叶小小

宗旨

本系列意在用最浅显的文字和代码示例,让大家实实在在的掌握Java最基础的知识。

一、前言

我们在学习JDK源码时,如果大家有留心,应该会经常看到关键字:transient !但不知道有多少人去真正了解过该关键字的作用。

二、初识transient

transient,中文意思:短暂的!那么,它有什么用呢?这就涉及到 Java 持久化机制。

2.1、Java 持久化

Java为我们提供了一种非常方便的持久化实例机制:Serializable !

可能有人要说:Serializable 不是用于序列化与反序列化么?
嗯,对!没错,那我想问:一个实例序列化后干啥用?

一个实例的序列化,可用于数据传输,然而,其最大的作用还是用于写入磁盘,从而防止数据丢失。

2.2、Java 序列化

我们在编写可用于序列化/反序列化的类时,都会去实现 Serializable,但是有时候,我们又不想序列化某些字段,那么,我们就可以用关键字 transient 来修饰该字段。

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

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

    public String getCardNo() {
        return cardNo;
    }

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

    public String getPassword() {
        return password;
    }

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

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

编写测试类

public class Main {
    public static void main(String[] args) {
        try {
            serializeBankCard();
            Thread.sleep(2000);
            deSerializeBankCard();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void serializeBankCard() 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("序列化 " + bankCard.toString());
    }

    private static void deSerializeBankCard() throws Exception {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bank.txt"));
        BankCard bankCard = (BankCard) is.readObject();
        is.close();
        System.out.println("反序列化 " + bankCard.toString());
    }
}

// 打印输出
// 序列化 BankCard{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCard{cardNo='CardNo: 1234567890', password='null'}

我们可以看到,我们先创建『银行卡』类,然后将数据序列化写到磁盘上,过了2秒后,我们再从磁盘上读取数据,打印发现,『password』字段是『null』。

我们打开磁盘上的文件,用十六进制查看数据,也可以看到,文件中,只有『CardNo』,没有『password』:

serializable + transient.png

三、深入分析 transient

在深入分析之前,我先抛出几个问题,然后,再带大家一一去解惑:

OK,带着以上问题,我们去看源码!

3.1、Serializable 类

/**
 * ......
 * 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
* @since   JDK1.1
*/
public interface Serializable {
}

Serializable 的注释就说到了:

3.2、ObjectOutputStream 类

开头的注释也说了:默认的 Serializable 机制,只写类的对象、签名以及非 transient 和非 static 字段

/*
 * 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 (;;) {
                ......
                // 重点:获取 Serializable对象的 desc
                // desc 中的 fields 会过滤 static 和 transient
                // 具体代码流程继续看 3.3
                desc = ObjectStreamClass.lookup(cl, true);
                ......
            }
            

            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared); // obj 根据 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();
            // 最终调用 bout 来写数据
            bout.writeByte(TC_OBJECT);
            
            // 所有的 writeXXX 都将调用 bout 来写数据
            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();
            }
        }
    }
}

3.3、ObjectStreamClass类

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;
            }
           ......
        }
    
        ......
    }
}

3.3.2、ObjectStreamClass构造函数

public class ObjectStreamClass implements Serializable {
    private ObjectStreamClass(final Class<?> cl) {
        ......
        if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    ......
                    try {
                        fields = getSerialFields(cl);  // 获取可序列化的字段
                        computeFieldOffsets();
                    } catch (InvalidClassException e) {
                        ......
                        fields = NO_FIELDS;
                    }
                    ......
                }
            }
        }
    }
}

3.3.3、ObjectStreamClass.getSerialFields

public class ObjectStreamClass implements Serializable {
    private static ObjectStreamField[] 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;
        }
        return fields;
    }
}

3.3.4、ObjectStreamClass.getDefaultSerialFields

public class ObjectStreamClass implements Serializable {
    private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
        Field[] clFields = cl.getDeclaredFields();
        ArrayList<ObjectStreamField> list = new ArrayList<>();
        int mask = Modifier.STATIC | Modifier.TRANSIENT; // 重点1:掩码 mask
    
        for (int i = 0; i < clFields.length; i++) {
            // 重点2:过滤 static 或者 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(new ObjectStreamField[size]);
    }
}

3.4、断点查看运行时JDK源码

jdk_runtime.png

简单的看完以上源码,至少回答了我们这么几个问题:

那么,还有一个问题,transient 修饰的变量真的无法序列化么?

再回答该问题前,首先我想问下大家,大家知道 Java 除了提供 Serializable 序列化外,还有提供其它方式么?

答案是:Java 提供了两种序列化方式

  1. Serializable,序列化与反序列化由 Java 实现(ObjectOutputStream / ObjectInputStream 的默认机制);
  2. Externalizable,序列化与反序列化由开发者自主来实现;

先来看看示例

// 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() {
        return cardNo;
    }

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

    public String getPassword() {
        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() {
        return "BankCardExt{cardNo='" + cardNo + "\', password='" + password + "\'}";
    }
}

这个类的读和写,由我们来实现。

再来个测试类

public class Main {

    public static void main(String[] args) {
        try {
            serializeBankCard();
            Thread.sleep(2000);
            deSerializeBankCard();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void serializeBankCard() 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("序列化 " + bankCard.toString());
    }

    private static void deSerializeBankCard() throws Exception {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bankExt.txt"));
        BankCardExt bankCard = (BankCardExt) is.readObject();
        is.close();
        System.out.println("反序列化 " + bankCard.toString());
    }
}

// 打印输出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}

我们看到,读的时候,数据也回来了。

exteralizable.png

查看文件,我们也看到了『CardNo』和『password』。

这里还有一点需要注意:写的顺序与读的顺序要保持一致!如果我们顺充不一致,结果将如下:

{
    @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();
    }
}

// 打印输出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='********', password='CardNo: 1234567890'}

OK,最后一个问题我们也解答了,当我们实现 Externalizable 时,需要我们自主选择哪些字段需要序列化与反序列化,因此,此时的 transient 修饰符将没有作用。

四、总结

本文分析了 transient 的序列化底层机制:

上一篇下一篇

猜你喜欢

热点阅读