首页 > 编程语言 >Java 关键字 transient

Java 关键字 transient

时间:2023-03-15 18:48:42浏览次数:37  
标签:Java 对象 关键字 transient 修饰 序列化 属性

前言

最近在看 HashMap 源代码的时候,发现链表 table 数组采用了transient 关键字,笔者当时感觉对 transient 关键字即陌生但又有似曾相识,所以花了一些时间简要的总结了下使用transient 关键字的一些基本常识,希望对你们也有些帮助,让我们一起进步,一起牛逼吧。

transient 关键字的定义

说起 transient 关键字,不得不提对象的 序列化 的,因为我们常常需要在网络上以对象(数据)的二进制方式传输数据,这里涉及到发送方序列化对象,接收方反序列化对象的过程。

那什么是序列化/反序列化?

Java 中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输。一般地,当我们使用缓存 cache(内存空间不够有可能会本地存储到硬盘)或远程调用 rpc(网络传输)的时候,经常需要让实体类实现 Serializable 接口,目的就是为了让其可序列化。当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象实例。所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。

在对象的序列化/反序列化过程中,我们经常有这种需求,就是非必要字段不必进行序列化。

例如有一个对象有三个字段 field1field2field3,发送方不想让字段 field3 被序列化,因为这里面可能涉及到一些敏感信息不想被接收方知道,那有没有办法解决这个问题呢?

其实聪明的 Java 作者早就为我们量身定做了 transient 关键字!

简单来说,被 transient 关键字修饰过的成员属性不能被序列化,transient 关键字只能修饰变量,而不能修饰方法和类。

transient 关键字的约定

  • 约定一、只能修饰变量而不能修饰方法和类。注意本地变量是不能被 transient 关键字修饰的。
  • 约定二、被 transient 关键字修饰过的属性不能被序列化,也就是说被 transient 修饰过的属性,在对对象序列化后,是无法访问到该属性的。
  • 约定三、静态变量不管有无被 transient 修饰过,不能被序列化。

transient 关键字的使用场景

首先,我们看个例子,有个产品对象 Product,包括价格数量总价三个字段,那么总价可以通过 价格 乘以 数量 推导出来。

我们以查询某个产品 API 接口为例,通过产品 ID,查询返回一个产品对象。

public class Product {
 private int amounts;
 private int price;
 private int sum;
}

通过 Gson 序列化后把 json 数据返回给前端,这时的 sum 字段是没有经过 transient 修饰过的,所以能够正常序列化。

{"amounts":3,"price":2,"sum":6}

 

假设我们的产品对象 Productsum 属性加上 transient 关键字修饰:
public class  产品对象 Product 的 sum {
 private int amounts;
 private int price;
 private transient int sum;
}

然后我们试着初始化 Product ,并用 GsontoJson() 方法序列化输出 json 格式的结果。

public static void main(String[] args) {
 Product p = new Product();
 p.setAmounts(3);
 p.setPrice(2);
 p.setSum(p.getAmounts() * p.getPrice());
 String json = new Gson().toJson(p);
 System.out.println(json);
}

这时控制台是没有打印出 sum 字段的。

{"amounts":3,"price":2}

我们看到,sum 属性被 transient 修饰后,是不会被 Gson 序列化输出的,这里就引出了使用 transient 关键字一个很重要的概念:对象属性推导

对象属性推导

“如果一个对象的属性值可以通过其他属性或者方法推理出来的,那么该属性就没必要被序列化了。

借此我们以 Gson 来分析被 transient 修饰过的属性不能被序列化过程。

首先,调用 GsontoJson() 方法,传入 Product 对象。

new Gson().toJson(product)

根据传入的产品对象,获取 Product 对象的 class 类型:typeOfSrc,最后 找到对应的对象解析适配器工厂。

toJson(Object src, Type typeOfSrc, JsonWriter writer)
TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
for (TypeAdapterFactory factory : factories) {
 // 得到ReflectiveTypeAdapterFactory
 TypeAdapter<T> candidate = factory.create(this, type);
}

通过适配器 ReflectiveTypeAdapterFactory 工厂的 create() 方法,我们找到 getBoundFields 方法。

new Adapter<T>(constructor, getBoundFields(gson, type, raw));
for (Field field : fields) {
 boolean serialize = excludeField(field, true);
 boolean deserialize = excludeField(field, false);
 ...
}

这个方法做了两件事情:

  1. 剔除被 transient 关键字修饰过的属性
  2. 筛选出可序列化的属性

通过 excludeField() 方法,剔除被 transient 修饰过的属性,其规则是通过位运算 "&" 判断 modifiers 属性与对象属性的 field.getModifiers() 的值是否一致,来证明该属性是否被 transient 修饰过,如果是为真,表示剔除该属性,不进行序列化。

public boolean excludeField(Field field, boolean serialize) {
 // 通过 if 判断 modifiers 属性
 // private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
 if ((modifiers & field.getModifiers()) != 0) {
   return true;
 } 
}

另外根据 modifiers 属性定义 Modifier.TRANSIENT | Modifier.STATIC 两种类型,一种是 tranient,另一种是 static 静态类型。

Modifier.STATIC:静态类型

由约定三、我们知道,静态变量不会被序列化。

代码 debug 到此,我们已经知道 Gson 是如何证明对象是否存在被 transient 修饰过属性以及如何过滤掉的完整过程。

被 transient 关键字修饰过得变量真的不能被序列化嘛?

想要解决这个问题,首先还要再重提一下对象的序列化方式,Java 序列化提供两种方式。

一种是实现 Serializable 接口,另一种是实现 Exteranlizable 接口。

实现 Exteranlizable 接口需要重写 writeExternalreadExternal 方法,它的效率比 Serializable 高一些,并且可以决定哪些属性需要序列化(即使是 transient 修饰的),但是对大量对象,或者重复对象,则效率低。

从上面的这两种序列化方式,我想你已经看到了,使用 Exteranlizable 接口实现序列化时,我们自己指定那些属性是需要序列化的,即使是 transient 修饰的。下面就验证一下

首先我们定义 User1 类:这个类是被 Externalizable 接口修饰的

然后我们就可以测试了

上面,代码分了两个方法,一个是序列化,一个是反序列化。里面的代码和一开始给出的差不多,只不过,User1 里面少了 age 这个属性。

然后看一下结果:

结果基本上验证了我们的猜想,也就是说,实现了 Externalizable 接口,哪一个属性被序列化是我们手动去指定的,即使是 transient 关键字修饰也不起作用。

transient 关键字总结

  • 通过常用的 Gson 方式来验证 tranient 关键字不能序列化的使用场景。
  • 通过实现了 Externalizable 接口,如果手动去指定属性序列化的,即使是 transient 关键字修饰也不起作用。
  • 另外,还可以通过 javaio 包下的 ObjectInputStreamObjectOutputStream 两个对象输入输出流也可以验证,这里就不再做赘述,感兴趣的朋友可以在网上找找例子。

参考

  • https://www.cnblogs.com/chenpt/p/9415249.html
  • https://blog.csdn.net/u012723673/article/details/80699029
  • https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc

 

 

转载自:https://cloud.tencent.com/developer/article/1808869

标签:Java,对象,关键字,transient,修饰,序列化,属性
From: https://www.cnblogs.com/sunny226/p/17219576.html

相关文章

  • 【学习日志】Java基本数据类型的自动装箱和拆箱
    //测试代码publicstaticvoidmain(String[]args){Integera=1;Integerb=2;Integerc=3;Integerd=3;Integ......
  • Java字符串基础练习题2(较难)
    ​生成验证码内容:可以是小写字母,也可以是大写字母,还可以是数字​规则: 长度为5 内容中是四位字母,1位数字。 其中数字只有1位,但是可以出现在任意的位置。publicstati......
  • java进阶 -JFrame36
      packagecom.cyjt97.Frame;importjavax.swing.*;publicclassJFrameDEME{publicstaticvoidmain(String[]args){//创建窗体JFr......
  • Java:SpringBoot获取所有接口的路由映射关系
    重要:版本很重要,先说一下版本,版本不一样,可能获取结果也不一样spring-boot2.7.7java1.8定义一个查看路由的数据结构packagecom.example.demo.entity;importlombok.......
  • JAVA开发 电信支付系统短信模块精准发送短信
    电信支付测试环境检测支付系统设备磁盘运行情况,监控磁盘到一定阀值,从client连到支付系统的统一短信模块,统一短信模块连接到短信发送系统,发送短信。短信模块也可应用于业务预......
  • SPARK 使用Java 在IDE中实战RDD和DataFrame动态转换操作
    /***王家林老师授课​​​http://weibo.com/ilovepains​​*/ 源文件1,Spark,72,Hadoop,113,Flink,5 运行结果 ......
  • Java float 和 double 精度问题
    先看现象涉及诸如float或者double这两种浮点型数据的处理时,偶尔总会有一些怪怪的现象,不知道大家注意过没,举几个常见的栗子:典型现象(一):条件判断超预期System.out.println(......
  • java接口学习笔记
    目录java接口学习笔记1.抽象类和抽象方法2.接口3.抽象类和接口对比参考java接口学习笔记1.抽象类和抽象方法抽象方法:abstractvoidf();抽象类:包含抽象方法的类称......
  • Java+Redis 通过Lua 完成库存扣减,创建消息队列,异步处理消息--实战
    需要完成功能借助redisStream数据结构实现消息队列,异步完成订单创建,其中涉及到了缓存(击穿,穿透,雪崩),锁(Redisson),并发处理,异步处理,Lua脚本IDE:IDEA2022 1、读取库存数......
  • java-返回值封装
    BaseResponse.Class@ApiIgnore@Data@JsonInclude(value=JsonInclude.Include.NON_NULL)@JsonIgnoreProperties(ignoreUnknown=true)publicclassBaseResponse<T>......