javaSE

序列化(静态变量与transient)

2022-11-19  本文已影响0人  virtual灬zzZ

序列化

我们的对象并不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术。

Java序列化技术正是将对象转变成一串由二进制字节组成的数组,可以通过将二进制数据保存到磁盘或者传输网络,磁盘或者网络接收者可以在对象的属类的模板上来反序列化的对象,达到对象持久化的目的。

序列化注意事项

  1. 序列化对象必须实现序列化接口。

  2. 序列化对象里面的属性是对象的话也要实现序列化接口。

  3. 类的对象序列化后,类的序列化ID不能轻易修改,不然反序列化会失败。

  4. 类的对象序列化后,类的属性有增加或者删除不会影响序列化,只是值会丢失。

  5. 如果父类序列化了,子类会继承父类的序列化,子类无需添加序列化接口。

  6. 如果父类没有序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化。

  7. 用Java序列化的二进制字节数据只能由Java反序列化,不能被其他语言反序列化。如果要进行前后端或者不同语言之间的交互一般需要将对象转变成Json/Xml通用格式的数据,再恢复原来的对象。

  8. 如果某个字段不想序列化,在该字段前加上transient关键字即可。

transient?

简单来说就是,被 transient 修饰的变量不能被序列化。

具体来看下面的示例1

import java.io.*;


public class TransientTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setUsername("Java");
        user.setId("javastack");

        System.out.println("\n序列化之前");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/usertxt"));
        os.writeObject(user);
        os.flush();
        os.close();

        ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/usertxt"));
        user = (User) is.readObject();
        is.close();

        System.out.println("\n序列化之后");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());
    }

}

class User implements Serializable {

    private static final long serialVersionUID = -1L;
    private String username;
    private transient String id;
    public String getUsername() {

        return username;

    }
    public void setUsername(String username) {

        this.username = username;

    }
    public String getId() {

        return id;

    }
    public void setId(String id) {

        this.id = id;

    }

}

输出结果:

序列化之前
username: Java
id: javastack
序列化之后`
username: Java
`id: null`
示例1在 id 字段上加了 transient 关键字修饰,反序列化出来之后值为 null,说明了被 transient 修饰的变量不能被序列化。

静态变量序列化

那么,到底静态变量能被序列化吗

示例2:

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;


public class TransientStaticTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        User.username = "Java技术1";
        user.setId("javastack");
        System.out.println("\n序列化之前");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/user.txt"));
        os.writeObject(user);
        os.flush();
        os.close();

        // 在反序列化出来之前,改变静态变量的值
        User.username = "Java技术2";
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/user.txt"));
        user = (User) is.readObject();
        is.close();

        System.out.println("\n序列化之后");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());
    }

}


class User implements Serializable {
    private static final long serialVersionUID = -1L;
    public static String username;
    private transient String id;

    public String getUsername() {
        return username;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

}

输出结果:

序列化之前
username: Java技术1
id: javastack
序列化之后
username: Java技术2
id: null

示例2把 username 改为了 public static, 并在反序列化出来之前改变了静态变量的值,结果可以看出序列化之后的值并非序列化进去时的值。

由以上结果分析可知,静态变量不能被序列化,示例2读取出来的是 username 在 JVM 内存中存储的值。

transient 真不能被序列化吗?

继续来看示例3:

import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;


public class ExternalizableTest {

    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setUsername("Java技术栈");
        user.setId("javastack");

        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File("javastack")));
        objectOutput.writeObject(user);
        ObjectInput objectInput = new ObjectInputStream(new FileInputStream(new File("javastack")));

        user = (User) objectInput.readObject();
        System.out.println(user.getUsername());
        System.out.println(user.getId());
        objectOutput.close();
        objectInput.close();
    }

}


class User implements Externalizable {
    private static final long serialVersionUID = -1L;

    public User() {
    }

    private String username;

    private transient String id;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeObject(id);
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        id = (String) objectInput.readObject();
    }
}

输出结果:

null
javastack

示例3的 id 被 transient 修改了,为什么还能序列化出来?那是因为 User3 实现了接口 Externalizable,而不是 Serializable。

在 Java 中有两种实现序列化的方式,Serializable 和 Externalizable,可能大部分人只知道 Serializable 而不知道 Externalizable。

这两种序列化方式的区别是:实现了 Serializable 接口是自动序列化的,实现 Externalizable 则需要手动序列化,通过 writeExternal 和 readExternal 方法手动进行,这也是为什么上面的 username 为 null 的原因了。

transient 关键字总结

1)transient修饰的变量不能被序列化;
2)transient只作用于实现 Serializable 接口;
3)transient只能用来修饰普通成员变量字段;
4)不管有没有 transient 修饰,静态变量都不能被序列化;

上一篇 下一篇

猜你喜欢

热点阅读