一、写在开头

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

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

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

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

  1. static 和 final 关键字 对实例变量赋初始值的影响

    static 和 final 关键字 对实例变量赋初始值的影响 最近一直在看<深入理解Java虚拟机>,在看完了对象内存分配.Class文件格式之后,想深扒一下实例变量是如何被赋上初始值的 ...

  2. 静态static关键字概述-静态static关键字修饰成员变量

    静态static关键字概述 概述 关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的.也就是说,既然属于类,就可以不靠创建对象来 ...

  3. BAT面试官告诉你如何回答你的职业规划

    前言(Why) 在面试中不论是在一面二面三面这种技术面,还是在最后的hr面,经常会被人问及,"谈谈你的职业规划"这种问题,我们回答的很可能会给我们的面试表现加分,如果回答地不好,对 ...

  4. 谷歌PM面试官告诉你,如何成功拿到理想offer

    快来看看大咖Nick如何甄选人才~文章转自墨刀公众号. 本文作者: Nick Baum,目前在谷歌做PM 作为谷歌的产品经理和面试官,我已经面过几百个PM的候选人了吧.这些经历能让我分享一下在PM求职 ...

  5. 阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第6节 static静态_12_静态static关键字修饰成员变量

    创建一个学生类 定义成员变量,无参构造,全参构造.成员变量的getter和setter 所在教室必须是一样的,定义所在教室 下面来创建两个学生 只给one的room赋值了.two的教室并没有赋值.都输 ...

  6. 静态static关键字概述和静态static关键字修饰成员变量

    static关键字 概述 关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的.也就是说,既然属于类,就可以不靠创建对象来调用了 ...

  7. K:java序列化与反序列化—transient关键字的使用

      首先,应该明白的是transient是java中的一个关键字,音标为 英: [ˈtrænziənt].   在了解transient关键字之前,应该先弄明白序列化和反序列化.所谓的序列化,通俗点的 ...

  8. transient关键字和serialVersionUID

    此文章很大部分转载于Java的架构师技术栈微信公众号,博主均测试通过加上自己理解写出 最近阅读java集合的源码,发现transient关键字,就了解了一下他的用法,transient关键字一般在实现 ...

  9. transient关键字的用法

    本篇博客转自 一直在路上 Java transient关键字使用小记 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,Java ...

  10. Java transient关键字使用小记

    哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...

随机推荐

  1. 《Effective C++》第三版-0. 导读(Introduction)

    目录 术语(Terminology) 命名习惯(Naming Conventions) 关于线程(Threading Consideration) TR1和Boost 术语(Terminology) ...

  2. 在鼠标右键菜单中新增新建Markdown文件选项(VSCode)

    引言 正常情况下,我们新建md文件有两种方式:一是通过Markdown编辑器新建,二是新建txt文件再修改后缀. 但是在Windows系统中,我们可以通过修改注册表来新增右键菜单选项.这里我们可以通过 ...

  3. Mac安装mysql5.7

    1.下载文件(访问就直接下载了) http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.10-osx10.10-x86_64.dmg 2.打开下 ...

  4. 「IT运维迷宫」那些让人头疼的常见问题与破局之道

    在数字化浪潮汹涌的今天,IT运维如同一座错综复杂的迷宫,稍有不慎便可能迷失方向.作为企业运营的幕后英雄,运维团队常常面临着各种突如其来的挑战.本文将带你深入探索IT运维中的那些常见"坑&qu ...

  5. RocketMQ 事件驱动:云时代的事件驱动有啥不同?

    前言: 从初代开源消息队列崛起,到 PC 互联网.移动互联网爆发式发展,再到如今 IoT.云计算.云原生引领了新的技术趋势,消息中间件的发展已经走过了 30 多个年头. 目前,消息中间件在国内许多行业 ...

  6. ansible(8)--ansible的hostname模块

    1. hostname模块 功能:管理远程主机的主机名. 示例一:更改192.168.20.22的主机名为nginx01: [root@xuzhichao ~]# ansible 192.168.20 ...

  7. C语言:汉诺塔问题(Hanoi Tower)------递归算法

    汉诺塔问题是一个经典的问题.汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆 ...

  8. C#应用的欢迎界面窗体方案 - 开源研究系列文章

    这次整理以前的代码,然后想到了应用的欢迎界面窗体的问题.这个例子是在应用中启动一个线程来进行显示欢迎窗体的,对于应用的启动无影响,与其他人的源码不相同,欢迎读者进行复用此类库. 以前有编写过欢迎界面窗 ...

  9. AIRIOT物联网低代码平台如何配置Modbus TCP协议?

    AIRIOT物联网低代码平台稳定性超高,支持上百种驱动,各种主流驱动已在大型项目中通过验证,持续稳定运行. AIRIOT物联网低代码平台如何配置Modbus TCP协议?操作如下: AIRIOT与西门 ...

  10. 一文读懂Apollo客户端配置加载流程

    SpringBoot集成Apollo源码分析 本文基于 apollo-client 2.1.0 版本源码进行分析 Apollo 是携程开源的配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修 ...