• 面试官:告诉我为什么static和transient关键字修饰的变量不能被序列化?


    一、写在开头

    在上一篇学习序列化的文章中我们提出了这样的一个问题:

    “如果在我的对象中,有些变量并不想被序列化应该怎么办呢?”

    当时给的回答是:不想被序列化的变量我们可以使用transientstatic关键字修饰;transient 关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复;而static关键字修饰的变量并不属于对象本身,所以也同样不会被序列化!

    当时没有解释具体为什么static和transient 关键字修饰的变量就不能被序列化了,这个问题实际上在很多大厂的面试中都可能会被问及。我们今天在这篇中进行解释吧。

    二、案例演示

    我们先通过一个实战案例,去看一看用static和transient 关键字修饰后的变量,序列化与反序列化后的现象。

    public class TestService {
    public static void main(String[] args) throws IOException {
    //初始化对象信息
    Person person = new Person();
    person.setName("JavaBuild");
    person.setAge(30);
    System.out.println(person.getName()+" "+person.getAge());
    //序列化过程
    try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:\\person.txt"));) {
    objectOutputStream.writeObject(person);
    } catch (IOException e) {
    e.printStackTrace();
    }
    person.par1 = "序列化后静态字段";
    //反序列化过程
    try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E:\\person.txt"));) {
    Person p = (Person) objectInputStream.readObject();
    System.out.println(p);
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }
    class Person implements Serializable{
    private static final long serialVersionUID = 8711922740433840551L;
    private String name;
    private int age;
    public static String par1 = "静态字段";
    transient String par2 = "临时字段";
    transient int high = 175;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public void setAge(int age) {
    this.age = age;
    }
    @Override
    public String toString() {
    return "Person{" +
    "name='" + name + '\'' +
    ", age=" + age +
    ", par1=" + par1 +
    ", high=" + high +
    ", par2='" + par2 + '\'' +
    '}';
    }
    }

    在Person类中,我们定义了两个正常的属性,姓名与年龄,同时呢,我们也分别定义了一个静态字段和两个临时字段,输出结果为:

    JavaBuild 30
    Person{name='JavaBuild', age=30, par1=序列化后静态字段, high=0, par2='null'}

    对于使用static关键字修饰的par1来说,在整个序列化过程中,它并未参与,原因是:我们在序列化与反序列化之间插入了属性的重新赋值操作,最后输出中打印出的是最新赋值,说明仅是调用了实例对象的属性值,而不是反序列化的结果。

    而对于transient 关键字修饰high和par2,在序列化时直接被忽略了。从输出结果看就更加的明了了,int类型直接还原为默认值0,而String类型直接为null。

    什么原因呢?咱们继续往下看。

    三、源码分析

    在之前的文章中,我们已经解释过了,在序列化时Serializable只是作为一种标识接口,告诉程序我这个对象需要序列化,那么真正的实现还要以来序列化流,比如写出到文件时,我们需要用到的ObjectOutputStream,它在序列化的时候会依次调用 writeObject()→writeObject0()→writeOrdinaryObject()→writeSerialData()→invokeWriteObject()→defaultWriteFields()。

    然后最后一步的defaultWriteFields()方法中,会去调用ObjectStreamClass对象,里面有个方法为getDefaultSerialFields(),提供了可以被序列化的属性值。

    private static ObjectStreamField[] getDefaultSerialFields(Class cl) {
    // 获取该类中声明的所有字段
    Field[] clFields = cl.getDeclaredFields();
    ArrayList list = new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;
    // 遍历所有字段,将非 static 和 transient 的字段添加到 list 中
    for (int i = 0; i < clFields.length; i++) {
    Field field = clFields[i];
    int mods = field.getModifiers();
    if ((mods & mask) == 0) {
    // 根据字段名、字段类型和字段是否可序列化创建一个 ObjectStreamField 对象
    ObjectStreamField osf = new ObjectStreamField(field.getName(), field.getType(), !Serializable.class.isAssignableFrom(cl));
    list.add(osf);
    }
    }
    int size = list.size();
    // 如果 list 为空,则返回一个空的 ObjectStreamField 数组,否则将 list 转换为 ObjectStreamField 数组并返回
    return (size == 0) ? NO_FIELDS :
    list.toArray(new ObjectStreamField[size]);
    }

    这段源码中,定义一个mask标记变量,用于接收访问修饰符中包含STATIC与TRANSIENT的属性,并在后面的if判断中,将这种mask的过滤掉,从而实现遍历所有字段,将非 static 和 transient 的字段添加到 list 中。

    而这段源码就证明了,为什么在对象序列化过程中,static和transient不会被序列化!

    四、总结

    好啦,今天针对为什么static和transient关键字修饰的变量不能被序列化进行了一个解释,下次大家在面试的时候再被问道就可以这样回答啦,不过,还有的BT面试官会问transient关键字修饰的变量真的不能被序列化吗?这个问题咱们后面继续讨论哈。

  • 相关阅读:
    vue3 封装公共弹窗函数
    【CSS颜色指南】:看完这一篇就明白CSS常见颜色表示方法
    Python文件及目录操作(基本文件操作篇)
    【 web网页设计期末课程大作业】基于HTML+CSS+JavaScript智慧路灯综合管理系统
    JSD-2204-(续)SpringGateway网关-Elasticsearch-Day06
    【k8s资源调度-StatefulSet】
    第三百六十五回
    如何提高爬虫效率
    网易企业邮箱斩获双料奖项!产品实力与客户口碑双丰收!【公司邮箱怎么申请】
    Qt进程和线程QProcess和QThread
  • 原文地址:https://www.cnblogs.com/JavaBuild/p/18263716