transient关键字和serialVersionUID
此文章很大部分转载于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的更多相关文章
- transient关键字的用法
本篇博客转自 一直在路上 Java transient关键字使用小记 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,Java ...
- transient关键字的作用
代码如下: import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutpu ...
- 序列化,反序列化和transient关键字
一.序列化和反序列化的概念 序列化:指把java对象转换为字节序列的过程. 反序列化:指把字节序列恢复为java对象的过程. 对象的序列化主要有两种用途: 1) 把对象的字节序列保存到硬盘上,通常存放 ...
- Java transient关键字使用小记
哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...
- Java中的Serializable接口transient关键字,及字节、字符、对象IO
1.什么是序列化和反序列化Serialization是一种将对象转为为字节流的过程:deserialization是将字节流恢复为对象的过程. 2.什么情况下需要序列化a)当你想把的内存中的对象保存到 ...
- Java对象表示方式1:序列化、反序列化和transient关键字的作用
平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...
- transient关键字
transient关键字的英文意思是:瞬态,由此可见是瞬间的,不可固定的. 会不会与对象的状态等等有关系呢? 网上找了一下资料是跟对象的序列化有关. transient的作用 一个对象只要实现了Ser ...
- Java transient关键字序列化时使用小记
1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过 ...
- 序列化、反序列化和transient关键字的作用
引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口, ...
随机推荐
- python requests 库 首次使用
安装requests库 执行pip3 install requests 使用resquests库获取百度网站首页 打开python idle终端.以python3为例,在终端执行python3并回车. ...
- 插画版Kubernetes指南
原文地址:https://www.cnblogs.com/kouryoushine/articles/8007648.html 是根据一个视频翻译过来的,比较形象 编者按:Matt Butcher 是 ...
- Qt下Armadillo矩阵函数库的添加
其实本文严格说只能算VS2013添加Armadillo教程,因为为了省事,用的是VS2013编译器版本的Qt,Armadillo也直接用了自带例子中的blas_win64_MT.dll.blas_wi ...
- Mac系统 python2.7中安装MySQLdb
由于要在python2.7上使用到MySQLdb连接数据库,所以要安装MySQLdb,也就是MySQL-Python.安装之前已经有人告诉我,这个东西比较难装,果然我也遇到好多问题,在百度找了半天,发 ...
- Docker获取镜像报错docker: Error response from daemon
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled ...
- Prometheus学习笔记之教程推荐
最近学习K8S和基于容器的监控,发现了如下的教程质量不错,记录下来以备参考 K8S最佳实战(包括了K8S的Prometheus监控和EFK日志搜集) https://jimmysong.io/kube ...
- 源码分析系列 | 从零开始写MVC框架
1. 前言 2. 为什么要自己手写框架 3. 简单MVC框架设计思路 4. 课程目标 5. 编码实战 5.1 配置阶段 web.xml配置 config.properties 自定义注解 5.2 初始 ...
- Ecplise中指定tomcat里Web项目发布文件
有时候发布项目时,我们会看到Ecplise会自动把一些并不是我们想需要的文件也发布到服务器上,可以通过以下方式解决: Properties->Deployment Assembly
- js 字符串方法 和 数组方法总览
字符串方法 search() 方法搜索特定值的字符串,并返回匹配的位置. 相比于indexOf(),search()可以设置更强大的搜索值(正则表 ...
- k3s首季在线培训来袭!本周四晚,线上见!
筹备已久的k3s在线培训终于要和大家见面啦! k3s是一款适用于边缘计算场景以及IoT场景的轻量级Kubernetes发行版,经过CNCF的一致性认证.由业界应用最广泛的Kubernetes管理平台R ...