此文章很大部分转载于Java的架构师技术栈微信公众号,博主均测试通过加上自己理解写出

最近阅读java集合的源码,发现transient关键字,就了解了一下他的用法,transient关键字一般在实现Serializable接口的类中出现.如下:

一、初识transient关键字

其实这个关键字的作用很好理解,一句话:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化

下面使用代码去验证一下:User类实现序列化接口,在age属性前加上该关键字

public class User implements Serializable {
private static final long serialVersionUID = 123456L;
private transient int age;
private String name; //getter和setter方法
//toString方法
}

然后我们在Test中去验证一下:

public static void main(String[] args) throws Exception, IOException {
SerializeUser();
DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User();
user.setName("Java的架构师技术栈");
user.setAge(24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://Test/template.txt"));
oos.writeObject(user);
oos.close();
System.out.println("添加transient关键字序列化age= "+user.getAge());
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://Test/template.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User)ois.readObject();
System.out.println("添加了transient关键字反序列化:age= "+newUser);
}

看一下结果:

从上面的这张图可以看出,age属性变为了0,说明被transient关键字修饰之后没有被序列化。

二、深入分析transient关键字

为了更加深入的去分析transient关键字,我们需要带着几个问题去解读:

(1)transient底层实现的原理是什么?

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

(3)静态变量能被序列化吗?被transient关键字修饰之后呢?

带着这些问题一个一个来解决:

1、transient底层实现原理是什么?

java的serialization提供了一个非常棒的存储对象状态的机制,说白了serialization就是把对象的状态存储到硬盘上 去,等需要的时候就可以再把它读出来使用。有些时候像银行卡号这些字段是不希望在网络上传输的,transient的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化,意思是transient修饰的age字段,他的生命周期仅仅在内存中,不会被写到磁盘中。

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

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

Java序列化提供两种方式。

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

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

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

public class User1 implements Externalizable{
private transient String name;
//getter、setter、toString方法
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException {
name = (String) in.readObject();
}
}

然后我们就可以测试了

public static void main(String[] args) throws Exception, IOException {
SerializeUser();
DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException {
User1 user = new User1();
user.setName("Java的架构师技术栈");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://template"));
oos.writeObject(user);
oos.close();
System.out.println("使用Externalizable接口,"+ "添加了transient关键字序列化之前:"+user);
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://template");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User1 newUser = (User1)ois.readObject();
System.out.println("使用Externalizable接口,"+ " 添加了transient关键字序列化之后:"+newUser);
}

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

然后看一下结果:

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

3、静态变量能被序列化吗?没被transient关键字修饰之后呢?

这个我可以提前先告诉结果,静态变量是不会被序列化的,即使没有transient关键字修饰。下面去验证一下,然后再解释原因。

首先,在User类中对age属性添加transient关键字和static关键字修饰。

public class User2 implements Serializable {
private static final long serialVersionUID = 12345L;
private static transient int age;
private String name; public static long getSerialVersionUID() {
return serialVersionUID;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

然后,在Test类中去测试

public class Test2 {
public static void main(String[] args) throws Exception, IOException {
SerializeUser();
DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException, ClassNotFoundException {
User2 user = new User2();
user.setName("Java的架构师技术栈");
//序列化之前静态变量age年龄是24.
user.setAge(24);
//将数据24写入磁盘
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://template"));
oos.writeObject(user);
oos.close();
//再读取,通过getAge()打印新的值
System.out.println("static、transient关键字修饰age之前:"+user.getAge());
//现在把年龄改成18,此时18只存在内存
user.setAge(18);
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://template");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User2 newUser = (User2)ois.readObject();
System.out.println("使用Externalizable接口,"+ " 添加了transient关键字序列化之后:"+newUser);
}
}

最后,看看结果

结果已经很明显了。现在解释一下,为什么会是这样,其实在前面已经提到过了。因为静态变量在全局区,本来流里面就没有写入静态变量,我打印静态变量当然会去全局区查找,而我们的序列化是写到磁盘上的,所以JVM查找这个静态变量的值,是从全局区查找的,而不是磁盘上。user.setAge(18);年龄改成18之后,被写到了全局区,其实就是方法区,只不过被所有的线程共享的一块空间。因此可以总结一句话:

静态变量不管是不是transient关键字修饰,都不会被序列化

三、transient关键字总结

java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。像银行卡、密码等等这些数据。这个需要根据业务情况了。

四.serialVersionUID

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException

具体的序列化过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

下面我们测试一下 serialVersionUID = 123456L;

public class User implements Serializable {
private static final long serialVersionUID = 123456L;
private transient int age;
private String name; public static long getSerialVersionUID() {
return serialVersionUID;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

序列化到磁盘

public static void main(String[] args) throws Exception, IOException {
SerializeUser();
//DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User();
user.setName("Java的架构师技术栈");
user.setAge(24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://Test/template.txt"));
oos.writeObject(user);
oos.close();
System.out.println("添加transient关键字序列化age= "+user.getAge());
}

看下结果

可以看到,成功了,现在我们修改一下serialVersionUID = 12345L;

public class User implements Serializable {
private static final long serialVersionUID = 12345L;
private transient int age;
private String name; public static long getSerialVersionUID() {
return serialVersionUID;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

再反序列试一下

public static void main(String[] args) throws Exception, IOException {
//SerializeUser();
DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User();
user.setName("Java的架构师技术栈");
user.setAge(24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://Test/template.txt"));
oos.writeObject(user);
oos.close();
System.out.println("添加transient关键字序列化age= "+user.getAge());
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://Test/template.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User)ois.readObject();
System.out.println("添加了transient关键字反序列化:age= "+newUser);
}

运行,看结果

出现了序列化版本不一致的异常,即是InvalidCastException

当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,

serialVersionUID注释掉,去除transient关键字

测试一下:

public class User implements Serializable {
//private static final long serialVersionUID = 12345L;
private int age;
private String name; /*public static long getSerialVersionUID() {
return serialVersionUID;
}*/ public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

测试代码如下:

public class Test {
public static void main(String[] args) throws Exception, IOException {
SerializeUser();
DeSerializeUser();
}
//序列化
private static void SerializeUser() throws FileNotFoundException, IOException, ClassNotFoundException {
User user = new User();
user.setName("Java的架构师技术栈");
user.setAge(24);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://Test/template.txt"));
oos.writeObject(user);
oos.close();
System.out.println("添加transient关键字序列化age= "+user.getAge());
}
//反序列化
private static void DeSerializeUser() throws IOException, ClassNotFoundException {
File file = new File("D://Test/template.txt");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User)ois.readObject();
System.out.println("添加了transient关键字反序列化:age= "+newUser);
}
}

运行,看一下结果

可以看到,序列化与反序列化都很正常

这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。

transient关键字和serialVersionUID的更多相关文章

  1. transient关键字的用法

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

  2. transient关键字的作用

    代码如下: import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutpu ...

  3. 序列化,反序列化和transient关键字

    一.序列化和反序列化的概念 序列化:指把java对象转换为字节序列的过程. 反序列化:指把字节序列恢复为java对象的过程. 对象的序列化主要有两种用途: 1) 把对象的字节序列保存到硬盘上,通常存放 ...

  4. Java transient关键字使用小记

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

  5. Java中的Serializable接口transient关键字,及字节、字符、对象IO

    1.什么是序列化和反序列化Serialization是一种将对象转为为字节流的过程:deserialization是将字节流恢复为对象的过程. 2.什么情况下需要序列化a)当你想把的内存中的对象保存到 ...

  6. Java对象表示方式1:序列化、反序列化和transient关键字的作用

    平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...

  7. transient关键字

    transient关键字的英文意思是:瞬态,由此可见是瞬间的,不可固定的. 会不会与对象的状态等等有关系呢? 网上找了一下资料是跟对象的序列化有关. transient的作用 一个对象只要实现了Ser ...

  8. Java transient关键字序列化时使用小记

    1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过 ...

  9. 序列化、反序列化和transient关键字的作用

    引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口, ...

随机推荐

  1. 深入理解Java虚拟机-类加载连接和初始化解析

    不管学习什么,我一直追求的是知其然,还要知其所以然,对真理的追求可以体现在方方面面.人生短短数十载,匆匆一世似烟云,我认为,既然来了,就应该留下一些有意义的东西.本系列文章是结合张龙老师的<深入 ...

  2. restframework 序列化补充(自定义ModelSerializerl)

    一.知识点 1.source title = serializers.CharField(source='courses.title') source用于one2one.foreginkey.choi ...

  3. 前端开发利器 Web Replay

    前端开发人员收到测试发来的 bug 后,通常比较头疼复现的问题. 即使测试人员录了视频,照着一步步操作也不一定能复现,例如bug是与当时的数据相关的. 为了解决这个问题,Firefox 推出了一个重磅 ...

  4. [bzoj4815] [洛谷P3700] [Cqoi2017] 小Q的表格

    Description 小Q是个程序员. 作为一个年轻的程序员,小Q总是被老C欺负,老C经常把一些麻烦的任务交给小Q来处理. 每当小Q不知道如何解决时,就只好向你求助.为了完成任务,小Q需要列一个表格 ...

  5. 学习  解决用户验证、单点登录、api访问控制的开源框架 的 十月 第一弹:

    step one 去饭 源码 ps :https://identityserver.io/ 这个官网. ps: https://github.com/IdentityServer 这是 源码网

  6. 西柚考勤系统——alpha1

    这个作业属于哪个课程 http://edu.cnblogs.com/campus/xnsy/GeographicInformationScience 这个作业的要求在哪里 https://www.cn ...

  7. win10系统下自由切换桌面

    说明: win10系统下自由切换桌面,确认在win10系统下操作进行. 方法: 1.快捷键:ctrl+win键(开始键)+方向键(左/右) 2.桌面最下面的状态栏,点击红框

  8. pku-3321 Apple Tree(dfs序+树状数组)

    Description There is an apple tree outside of kaka's house. Every autumn, a lot of apples will grow ...

  9. python下的selenium和chrome driver的安装

    selenium是一款支持多种语言.多种浏览器.多个平台的开源web自动化测试软件,测试人员可用python.java等语言编写自动化脚本,使得浏览器可以完全按照你的指令运行,大大节省了测试人员用鼠标 ...

  10. Mybatisplus代码生成器主类CodeGenerator配置

    //代码自动生成public class CodeGenerator { /** * <p> * 读取控制台内容 * </p> */ public static String ...