摘要

今天看hazelcast的demo,发现有transient关键字,只是大概了解这个关键字用来修饰不需要序列化的字段,所以想再进一步测试一下。

正文

抄袭来源

  1. java关键字之transient - 知乎
  2. Java中关键字transient解析 - 知乎
  3. 静态成员变量能不能被序列化????-CSDN社区
  4. Java中创建对象的5种方式 - _1900 - 博客园

一、transient概念

transient只能用来修饰成员变量(field),被transient修饰的成员变量不参与序列化过程。

Java中的对象如果想要在网络上传输或者存储在磁盘时,就必须要序列化。Java中序列化的本质是**Java对象转换为字节序列**。但是在序列化的过程中,可以允许被序列对象中的某个成员变量不参与序列化,即该对象完成序列化之后,被transient修饰的成员变量会在字节序列中消失。

通过阅读序列化接口Serializable,可知,我们可以通过读写流来模拟序列化与反序列化

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class Person implements Serializable {

    private transient String name;

    private static String sex;

    private String nickName;

    public Person(String name, String sex, String nickName) {
        this.name = name;
        this.sex = sex;
        this.nickName = nickName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return String.format("Halo, My name is %s, my sex is %s, " +
                "my nickName is %s",
                name,
                sex,
                nickName);
    }


    public static void main(String[] args) throws Exception {
        Person person = new Person("向晚", "girl", "Ava");
        System.out.println(String.format("序列化前:%s",person));

        //序列化后写入到磁盘里,再读取出来反序列化。添加transient的字段,在序列化时,就已经忽略掉了
        File file = new File("C:\\Users\\meethigher\\Desktop\\aaa\\src\\main\\resources\\index.txt");
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
        os.writeObject(person);

        ObjectInputStream is = new ObjectInputStream(new FileInputStream(file));
        person = (Person) is.readObject();
        System.out.println(String.format("序列化后:%s",person));

        //通过fastjson模拟序列化,添加transient的字段不参与序列化
        String s = JSON.toJSONString(person);
        System.out.println(String.format("使用fastjson序列化后的内容:%s",s));


    }
}

运行结果

text
1
2
3
序列化前:Halo, My name is 向晚, my sex is girl, my nickName is Ava
序列化后:Halo, My name is null, my sex is girl, my nickName is Ava
使用fastjson序列化后的内容:{"nickName":"Ava","sex":"girl"}

可以看出,使用transient关键字修饰的成员变量没有被序列化。

通过Serializable的注释中,可知,其实static和transient的变量都不能序列化。

但是上面的例子中,static看上去也被序列化了。但并不是。

原因是因为静态变量是属于类的,只要这个类的静态变量值没被变过,创建多少个对象,都是该值。

我们可以给Person,再加一个构造函数,不去改变静态变量的值。

java
1
2
3
4
5
6
7
8
public static void main(String[] args) {
    Person person = new Person("a", "init", "a");
    System.out.println(person);
    Person person1 = new Person("b", "b");
    System.out.println(person1);
    Person person2 = new Person("c", "c");
    System.out.println(person2);
}

输出结果

text
1
2
3
Halo, My name is a, my sex is init, my nickName is a
Halo, My name is b, my sex is init, my nickName is b
Halo, My name is c, my sex is init, my nickName is c

序列化写入文件时,其实静态变量的值没写进去,那么读取出来时,就相当于创建了一个对象(创建对象,需要了解创建对象的几种方式,像反序列化,是不需要通过构造函数的)。

反序列出来时,由于静态变量的值没有发生改变,所以看样子像是又反序列化出来了一样。

我们新建另一个Test类,读取Person类写入的index.txt文件。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Test {

    public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\meethigher\\Desktop\\aaa\\src\\main\\resources\\index.txt");

        ObjectInputStream is = new ObjectInputStream(new FileInputStream(file));
        Person person = (Person) is.readObject();
        System.out.println(String.format("序列化后:%s",person));
    }
}

就能看出,其实static修饰的变量也没有被序列化

text
1
序列化后:Halo, My name is null, my sex is null, my nickName is Ava

二、transient本质

为什么要有transient?

毫无疑问,这是一个平常的编程语言设计思路,即实现两种编码转化的时候,我们希望用户在转化过程中可以控制一些内容。

理解transient的关键在于理解序列化,序列化是Java对象转换为字节序列。

详细的说,就是Java对象在电脑中是存于内存之中的,内存之中的存储方式毫无疑问和磁盘中的存储方式不同(一个显而易见的区别就是对象在内存中的存储分为堆和栈两部分,两部分之间还有指针;但是存到磁盘中肯定不可能带指针,一定是某种文本形式)。序列化和反序列化就是在这两种不同的数据结构之间做转化。

序列化:JVM中的Java对象转化为字节序列。

反序列化:字节序列转化为JVM中的Java对象。

理解到这里,实现原理也是显而易见的,只要在处理两个数据结构转化的过程中,把标为transient的成员变量特殊处理一下就好了。

三、自定义序列化

如果想要实现自定义的序列化,我们可以实现Externalizable接口。

通过实现接口,在重写方法时,指定我们想要序列化的字段。在这种情况下,static与transient都不再生效。

测试发现fastjson不支持Externalizable

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class Person implements Externalizable {

    private transient String name;

    private static String sex;

    private String nickName;

    public Person() {
    }

    public Person(String name, String sex, String nickName) {
        this.name = name;
        this.sex = sex;
        this.nickName = nickName;
    }

    public Person(String name, String nickName) {
        this.name = name;
        this.nickName = nickName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return String.format("Halo, My name is %s, my sex is %s, " +
                        "my nickName is %s",
                name,
                sex,
                nickName);
    }


    public static void main(String[] args) throws Exception {
        Person person = new Person("向晚", "girl", "Ava");
        System.out.println(String.format("序列化前:%s", person));

        //序列化后写入到磁盘里,再读取出来反序列化。添加transient的字段,在序列化时,就已经忽略掉了
        File file = new File("C:\\Users\\meethigher\\Desktop\\aaa\\src\\main\\resources\\index.txt");
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
        os.writeObject(person);

        ObjectInputStream is = new ObjectInputStream(new FileInputStream(file));
        person = (Person) is.readObject();
        System.out.println(String.format("序列化后:%s", person));

        //通过fastjson模拟序列化,添加transient的字段不参与序列化
        String s = JSON.toJSONString(person);
        System.out.println(String.format("使用fastjson序列化后的内容:%s", s));


    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(nickName);
        out.writeUTF(name);
        out.writeUTF(sex);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        nickName=in.readUTF();
        name=in.readUTF();
        sex=in.readUTF();
    }
}

write与read要保持顺序一致

输出结果

text
1
2
3
序列化前:Halo, My name is 向晚, my sex is girl, my nickName is Ava
序列化后:Halo, My name is 向晚, my sex is girl, my nickName is Ava
使用fastjson序列化后的内容:{"nickName":"Ava","sex":"girl"}