首页 > 其他分享 >面试官:告诉我为什么static和transient关键字修饰的变量不能被序列化?

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

时间:2024-06-23 17:53:37浏览次数:3  
标签:面试官 int 关键字 transient static 修饰 序列化

一、写在开头

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

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

当时给的回答是:不想被序列化的变量我们可以使用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<ObjectStreamField> 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关键字修饰的变量真的不能被序列化吗?这个问题咱们后面继续讨论哈。

标签:面试官,int,关键字,transient,static,修饰,序列化
From: https://www.cnblogs.com/JavaBuild/p/18263716

相关文章

  • 大厂面试官问我:布隆过滤器有不能扩容和删除的缺陷,目前有没有能够利用到的数据结构来做
    往期内容:面试官问我:Redis处理点赞,如果瞬时涌入大量用户点赞(千万级),应当如何进行处理?【后端八股文(1)】-CSDN博客本文为【布隆过滤器八股文合集】初版,后续还会进行优化更新,欢迎大家评论交流~大家第一眼看到这个标题,不知道心中是否有答案了?在面试当中,面试官经常对项目亮点进行......
  • Go自定义数据的序列化流程
    ......
  • 面试官:请你实现三栏布局并且优先加载中间内容 我:稳啦- ̗̀(๑ᵔ⌔ᵔ๑)
    前言三栏布局是网页设计中一种经典布局方式,它将页面分为三个垂直部分:左栏、中栏和右栏,三栏在同一行显示。这种布局模式在很多网站的首页或内容密集型页面中非常常见,因为它能够有效地组织信息,提供良好的用户体验。常常也是作为面试常考题出现,今天将为大家介绍常见的三栏布......
  • 树的序列化笔记
    \(dfs\)序以\(DFS\)(先根遍历)⾸次访问顺序将节点重新排列。特征:每个顶点在序列中出现恰好⼀次(废话)⽗节点排在⼦节点前⾯(废话)每棵⼦树都占据序列的⼀个区间欧拉序记录\(DFS\)递归/回溯时依次经过的所有点。特征:每个点出现次数=度数(根多1次)相邻点深度差1\(LCA(x,y)=\)......
  • 做完这些大模型项目,面试官直呼太牛了
    前言大语言模型正迅速成为互联网时代最热门的技术创新之一,虽然现在该技术尚处于起步阶段,但已经开始在一些企业应用中广泛部署。接下来,我们来看看大语言模型的10大应用场景。一、文本翻译二、恶意软件分析三、创造文本内容四、搜索五、代码开发六、检测和预防网络攻......
  • 序列化和反序列化pickle和json 模块
    importpicklehello='helloworld'data=pickle.dumps(hello)#pickle.dumps把任意对象序列化成一个bytes(字节数)print(data)data1=pickle.loads(data)#pickle.loads将字节数反序列化print(data1)importjsondata={'hello':123,'nihao':'word&......
  • 面试官问:Kafka 会不会丢消息?怎么处理的?
    作者:Java3y链接:https://www.zhihu.com/question/628325953/answer/3281764326来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。面试官:今天我想问下,你觉得Kafka会丢数据吗?候选者:嗯,使用Kafka时,有可能会有以下场景会丢消息候选者:比如说,我们用Produce......
  • 关于面试被面试官暴怼:“几年研究生白读” 的前因后果
      中午一个网友来信说自己和面试官干起来了,看完他的描述真是苦笑不得,这年头是怎么了,最近互联网CS消息满天飞,怎么连面试官都SB起来了呢?  大概是这样的:这位网友面试时被问及了Serializable接口的底层实现原理,因为这是一个标识性的空接口,大部分同学在学习时都秉持着会用就行......
  • 面试官:你讲下如何设计支持千万级别的短链?
    前言前几天面试遇到的,感觉比较有趣。第一次面试遇到考架构设计相关的题目,挺新奇的,开始向国外大厂靠拢了,比天天问八股文好太多了,工作5年左右的,问八股文,纯纯的不负责任偷懒行为。感觉此问题比较有趣,这几天简单的实现了一版本,和大家分享一下具体的细节,也欢迎大家交流讨论,代码gith......
  • JAVA面试中,面试官最爱问的问题。
    什么是静态变量和静态方法?在Java中,静态变量和静态方法是通过`static`关键字定义的,与类相关联而不是与类的实例相关联。它们在类加载时就被初始化,可以通过类名直接访问,而不需要创建类的实例。###静态变量(StaticVariable)静态变量是类级别的变量,属于类而不是任何单个实例。每......