transient關鍵字的做用以及幾個疑問的解決

[TOC]java

1.從Serilizable說到transient

咱們知道,若是一個對象須要序列化,那麼須要實現Serilizable接口,那麼這個類的全部非靜態屬性,都會被序列化。數組

注意:上面說的是非靜態屬性,由於靜態屬性是屬於類的,而不是屬於類對象的,而序列化是針對類對象的操做,因此這個根本不會序列化。下面咱們能夠實驗一下:
實體類Teacher.class:ide

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 +
                '}';
    }
}

測試代碼SerialTest.java,基本思路就是初始化的時候,靜態屬性SchoolName爲"東方小學",序列化對象以後,將靜態屬性修改,而後,反序列化,發現其實靜態變量仍是修改以後的,說明靜態變量並無被序列化。學習

import java.io.*;

public class SerialTest {
    public static void main(String[] args) {
        Teacher.SchoolName = "東方小學";
        serial();
        Teacher.SchoolName = "西方小學";
        deserial();
        System.out.println(Teacher.SchoolName);
    }
    // 序列化
    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();
        }
    }
    // 反序列化
    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();
        }
    }
}

輸出的結果,證實靜態變量沒有被序列化!!!測試

Teacher{age=9}
西方小學

2.序列化屬性對象的類須要實現Serilizable接口?

忽然想到一個問題,若是有些屬性是對象,而不是基本類型,需不須要改屬性的類型也實現Serilizable呢?this

問題的答案是:須要!!!code

下面是實驗過程:對象

首先,有一個Teacher.java,實現了Serializable,裏面有一個屬性是School類型:接口

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類型,不實現Serializable:ci

public class School {
    public String name;

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

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

測試代碼,咱們只測試序列化:

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("東方小學");
            FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(teacher);
            objectOutputStream.flush();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

}

會發現報錯了,報錯的緣由是:School不能被序列化,也就是沒有實現序列化接口,因此若是咱們想序列化一個對象,那麼這個對象的屬性也必須是可序列化的,或者它是transient修飾的。

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)

當咱們將School實現序列化接口的時候,發現一切就正常了...問題完美解決

3.不想被序列化的字段怎麼辦?

可是若是有一個變量不是靜態變量,可是咱們也不想序列化它,由於它多是一些密碼等敏感的字段,或者它是不那麼重要的字段,咱們不但願增長報文大小,因此想在序列化報文中排除該字段。或者改字段存的是引用地址,不是真正重要的數據,好比ArrayList裏面的elementData

這個時候就須要使用transient 關鍵字,將改字段屏蔽。

當咱們用transient修飾School的時候:

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 + '\'' +
                '}';
    }
}

執行下面序列化和反序列化的代碼:

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("東方小學");
            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();
        }
    }
}

執行結果以下,能夠看到teacher字段反序列化出來,實際上是null,這也是transient起做用了。
可是注意,transient只能修飾變量,可是不能修飾類和方法,

4.ArrayList裏面的elementData都被transient 關鍵字修飾了,爲何ArrayList還能夠序列化呢?

這裏提一下,既然transient修飾了ArrayList的數據節點,那麼爲何序列化的時候咱們仍是能夠看到ArrayList的數據節點呢?
這是由於序列化的時候:

若是僅僅實現了 Serializable接口,那麼序列化的時候,確定是調用 java.io.ObjectOutputStream.defaultWriteObject()方法,將對象序列化。而後若是是 transient修飾了該屬性,確定該屬性就不能序列化。
可是,若是咱們雖然實現了 Serializable接口,也 transient修飾了該屬性,該屬性確實不會在默認的 java.io.ObjectOutputStream.defaultWriteObject()方法裏面被序列化了,可是咱們能夠重寫一個 writeObject()方法,這樣一來,序列化的時候調用的就是 writeObject(),而不是 java.io.ObjectOutputStream.defaultWriteObject()

下面的源碼是ObjectInputStream.writeObject(Object obj),裏面底層其實會有反射的方式調用到重寫的對象的writeObject()方法,這裏不作展開。

public final void writeObject(Object obj) throws IOException {
        // 若是能夠被重寫,那麼就會調用重寫的方法
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

ArrayList重寫的writeOject()方法以下:

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

咱們能夠看到,writeOject()裏面其實在裏面調用了默認的方法defaultWriteObject()defaultWriteObject()底層實際上是調用改了writeObject0()ArrayList重寫的writeOject()的思路主要是先序列化默認的,而後序列化數組大小,再序列化數組elementData裏面真實的元素。這就達到了序列化元素真實內容的目的。

5.除了transient,有沒有其餘的方式,能夠屏蔽反序列化?

且慢,問出這個問題,答案確定是有的!!!那就是Externalizable接口。

具體狀況:Externalizable意思就是,類裏面有不少不少屬性,可是我只想要一部分,要屏蔽大部分,那麼我不想在大部分的屬性前面加關鍵字transient,我只想標識一下本身序列化的字段,這個時候就須要使用Externalizable接口。

show me the code!

首先定義一個Person.java,裏面有三個屬性

  • age:年齡
  • name:名字(被transient修飾)
  • score:分數

實現了Externalizable接口,就必須實現writeExternal()readExternal()方法。

  • writeExternal:將須要序列化的屬性進行自定義序列化
  • readExternal:將須要反序列化的屬性進行自定義反序列化
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;

    // 必須實現無參構造器
    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 {
        /*
         * 指定序列化時候寫入的屬性。這裏不寫入score
         */
        out.writeObject(age);
        out.writeObject(name);
        out.writeObject(score);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        /*
         * 指定序列化時候寫入的屬性。這裏仍然不寫入年齡
         */
        this.age = (int)in.readObject();
        this.name = (String)in.readObject();
    }

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

上面的代碼,咱們能夠看出,序列化的時候,將三個屬性都寫進去了,可是反序列化的時候,咱們僅僅還原了兩個,那麼咱們來看看測試的代碼:

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

測試結果以下,就能夠發現其實前面兩個都反序列化成功了,後面那個是由於咱們重寫的時候,沒有自定義該屬性的反序列化,因此沒有是正常的啦...

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

若是細心點,能夠發現,有一個字段是transient修飾的,不是說修飾了,就不會被序列化麼,怎麼序列化出來了。

沒錯,只要實現了Externalizable接口,其實就不會被transient左右了,只會按照咱們自定義的字段進行序列化和反序列化,這裏的transient是無效的...

關於序列化的transient暫時到這,keep going~

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

技術之路不在一時,山高水長,縱使緩慢,馳而不息。

公衆號:秦懷雜貨店

相關文章
相關標籤/搜索