前言
说实话学了一段时间java的朋友对于transient
这个关键字依旧很陌生基本没怎么用过,可是transient
关键字在java中却起到了不可或缺的地位!若是要说讲到,我以为最可能出现的地方是IO流中对象流(也叫序列化流)的时候会讲到!java
相信不少人都是直到本身碰到才会关心这个关键字,记得博主第一次碰到transient
关键字是在阅读JDK源码的时候。在学习java的过程当中transient
关键字少见的缘由其实离不开它的做用:transient
关键字的主要做用就是让某些被transient关键字修饰的成员属性变量不被序列化。实际上也正是所以,在学习过程当中不多用得上序列化操做,通常都是在实际开发中!至于序列化,相信有不少小白童鞋一直迷迷糊糊或者没有具体的概念,这都不是事,下面博主会很清楚的让你记住啥是序列化,保证你这辈子忘不了(貌似有点夸张,有点装b,感受要被打)程序员
@数据库
提及序列化,随之而来的另外一个概念就是反序列化,小白童鞋不要慌,记住了序列化就至关于记住了反序列化,由于反序列化就是序列化反过来,因此博主建议只记住序列化概念便可,省的搞晕本身。缓存
专业术语定义的序列化:安全
Java提供了一种对象序列化的机制。用一个字节序列能够表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件以后,至关于文件中持久保存了一个对象的信息。反之,该字节序列还能够从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,均可以用来在内存中建立对象。网络
宜春的术语定义序列化:ide
序列化: 字节 ——> 对象学习
其实,我总结的就是上面的结论,若是不理解,直接参照专业术语的定义,理解以后就记住个人话就好了,记不住,请打死我(我踢m简直就是个天才)优化
图理解序列化:
啥?你不懂啥是字节?其实,我在一篇IO流的文章里就已经介绍了序列化,放心,绝对特别详细~光看文章名字就知道了~
从上一节提到序列化的概念,知道概念以后,咱们就必需要知道 为什么要序列化了。
讲为什么要序列化缘由以前,博主我举个栗子:
就像你去街上买菜,通常操做都是用塑料袋给包装起来,直到回家要作菜的时候就把菜给拿出来。而这一系列操做就像极了序列化和反序列化!
Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象 能够被写到数据库或文件中,也可用于 网络传输,通常当咱们使用 缓存cache(内存空间不够有可能会本地存储到硬盘)或 远程调用rpc(网络传输)的时候,常常须要让咱们的实体类实现Serializable
接口,目的就是为了让其可序列化。
若是一个用户有一些密码等信息,为了安全起见,不但愿在网络操做中被传输,这些信息对应的变量就能够加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
一、类中的字段值能够根据其它字段推导出来。
二、看具体业务需求,哪些字段不想被序列化;
不知道各位有木有想过为何要不被序列化呢?其实主要是为了节省存储空间。优化程序!
PS:记得以前看
HashMap
源码的时候,发现有个字段是用transient
修饰的,我以为仍是有道理的,确实不必对这个modCount字段进行序列化,由于没有意义,modCount主要用于判断HashMap是否被修改(像put、remove操做的时候,modCount
都会自增),对于这种变量,一开始能够为任何值,0固然也是能够(new出来、反序列化出来、或者克隆clone出来的时候都是为0的),不必持久化其值。
固然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象,要否则序列化后干吗呢,就像买菜同样,用塑料袋包裹最后仍是为了方便安全到家再去掉塑料袋,因此序列化后的字节序列都是能够恢复成Java对象的,这个过程就是反序列化。
一、须要作序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,好比:String
,Integer
类等,不实现此接口的类将不会使任何状态序列化或反序列化,会抛NotSerializableException
异常 。
二、底层会判断,若是当前对象是 Serializable
的实例,才容许作序列化,Java对象 instanceof Serializable
来判断。
三、在 Java 中使用对象流ObjectOutputStream
来完成序列化以及ObjectInputStream
流反序列化
==ObjectOutputStream:经过 writeObject()方法作序列化操做==
==ObjectInputStream:经过 readObject() 方法作反序列化操做==
四、该类的全部属性必须是可序列化的。若是有一个属性不须要可序列化的,则该属性必须注明是瞬态的,使用transient
关键字修饰。
因为字节嘛因此确定要涉及流的操做,也就是对象流也叫序列化流ObjectOutputstream,下面进行多种状况分析序列化的操做代码!
在这里,我真的强烈建议看宜春博客的读者朋友,请试着去敲,切记一眼带过或者复制过去运行就完事了,特别是小白童鞋,相信我!你必定会有不同的收获。千万不要以为浪费时间,有时候慢就是快,宜春亲身体会!
package TransientTest; import java.io.*; class UserInfo { //================================注意这里没有实现Serializable接口 private String name; private transient String password; public UserInfo(String name,String psw) { this.name = name; this.password=psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) { UserInfo userInfo=new UserInfo("老王","123"); System.out.println("序列化以前信息:"+userInfo); try { ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); output.writeObject(new UserInfo("老王","123")); output.close(); } catch (IOException e) { e.printStackTrace(); } } }
运行结果
当咱们加上实现Serializable接口再运行会发现,项目中出现的userinfo.txt
文件内容是这样的:
其实这都不是重点,重点是序列化操做成功了!
package TransientTest; import java.io.*; class UserInfo implements Serializable{ //第一步实现Serializable接口 private String name; private String password;//都是普通属性============================== public UserInfo(String name,String psw) { this.name = name; this.password=psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo=new UserInfo("程序员老王","123"); System.out.println("序列化以前信息:"+userInfo); try { ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操做 output.writeObject(new UserInfo("程序员老王","123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步开始反序列化操做 Object o = input.readObject();//ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化以后信息:"+o); } catch (IOException e) { e.printStackTrace(); } } }
运行结果:
序列化以前信息:UserInfo{name='程序员老王', password='123'} 序列化以后信息:UserInfo{name='程序员老王', password='123'}
package TransientTest; import java.io.*; class UserInfo implements Serializable{ //第一步实现Serializable接口 private String name; private transient String password; //特别注意:属性由transient关键字修饰=========== public UserInfo(String name,String psw) { this.name = name; this.password=psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo=new UserInfo("程序员老王","123"); System.out.println("序列化以前信息:"+userInfo); try { ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操做 output.writeObject(new UserInfo("程序员老王","123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步开始反序列化操做 Object o = input.readObject();//ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化以后信息:"+o); } catch (IOException e) { e.printStackTrace(); } } }
运行结果:
序列化以前信息:UserInfo{name='程序员老王', password='123'} 序列化以后信息:UserInfo{name='程序员老王', password='null'}
特别注意结果,添加transient修饰的属性值为默认值null
!若是被transient修饰的属性为int类型,那它被序列化以后值必定是0,固然各位能够去试试,这能说明什么呢?说明被标记为transient
的属性在对象被序列化的时候不会被保存(或者说变量不会持久化)
package TransientTest; import java.io.*; class UserInfo implements Serializable{ //第一步实现Serializable接口 private String name; private static String password; //特别注意:属性由static关键字修饰============== public UserInfo(String name, String psw) { this.name = name; this.password=psw; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", password='" + password + '\'' + '}'; } } public class TransientDemo { public static void main(String[] args) throws ClassNotFoundException { UserInfo userInfo=new UserInfo("程序员老王","123"); System.out.println("序列化以前信息:"+userInfo); try { ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操做 output.writeObject(new UserInfo("程序员老王","123")); output.close(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步开始反序列化操做 Object o = input.readObject();//ObjectInputStream的readObject方法会抛出ClassNotFoundException System.out.println("序列化以后信息:"+o); } catch (IOException e) { e.printStackTrace(); } } }
运行结果:
序列化以前信息:UserInfo{name='程序员老王', password='123'} 序列化以后信息:UserInfo{name='程序员老王', password='123'}
这个时候,你就会错误的认为static修饰的也被序列化了,其实否则,实际上这里很容易被搞晕!明明取出null
(默认值)就能够说明不会被序列化,这里明明没有变成默认值,为什么还要说static
不会被序列化呢?
实际上,反序列化后类中static型变量name的值其实是当前JVM中对应static变量的值,这个值是JVM中的并非反序列化得出的。也就是说被static修饰的变量并无参与序列化!可是咱也不能口说无凭啊,是的,那咱们就来看两个程序对比一下就明白了!
第一个程序:这是一个没有被static修饰的name属性程序:
package Thread; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class UserInfo implements Serializable { private String name; private transient String psw; public UserInfo(String name, String psw) { this.name = name; this.psw = psw; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPsw() { return psw; } public void setPsw(String psw) { this.psw = psw; } public String toString() { return "name=" + name + ", psw=" + psw; } } public class TestTransient { public static void main(String[] args) { UserInfo userInfo = new UserInfo("程序员老过", "456"); System.out.println(userInfo); try { // 序列化,被设置为transient的属性没有被序列化 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt")); o.writeObject(userInfo); o.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { //在反序列化以前改变name的值 =================================注意这里的代码 userInfo.setName("程序员老改"); // 从新读取内容 ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt")); UserInfo readUserInfo = (UserInfo) in.readObject(); //读取后psw的内容为null System.out.println(readUserInfo.toString()); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
运行结果:
name=程序员老过, psw=456 name=程序员老过, psw=null
从程序运行结果中能够看出,在反序列化以前试着改变name的值为程序员老改,结果是没有成功的!
第二个程序:这是一个被static修饰的name属性程序:
package Thread; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class UserInfo implements Serializable { private static final long serialVersionUID = 996890129747019948L; private static String name; private transient String psw; public UserInfo(String name, String psw) { this.name = name; this.psw = psw; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPsw() { return psw; } public void setPsw(String psw) { this.psw = psw; } public String toString() { return "name=" + name + ", psw=" + psw; } } public class TestTransient { public static void main(String[] args) { UserInfo userInfo = new UserInfo("程序员老过", "456"); System.out.println(userInfo); try { // 序列化,被设置为transient的属性没有被序列化 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt")); o.writeObject(userInfo); o.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { //在反序列化以前改变name的值 userInfo.setName("程序员老改"); // 从新读取内容 ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt")); UserInfo readUserInfo = (UserInfo) in.readObject(); //读取后psw的内容为null System.out.println(readUserInfo.toString()); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
运行结果:
name=程序员老过, psw=456 name=程序员老改, psw=null
从程序运行结果中能够看出,在反序列化以前试着改变name的值为程序员老改,结果是成功的!如今对比一下两个程序是否是就很清晰了?
static关键字修饰的成员属性优于非静态成员属性加载到内存中,同时静态也优于对象进入到内存中,被static修饰的成员变量不能被序列化,序列化的都是对象,静态变量不是对象状态的一部分,所以它不参与序列化。因此将静态变量声明为transient变量是没有用处的。所以,反序列化后类中static型变量name的值其实是当前JVM中对应static变量的值,这个值是JVM中的并非反序列化得出的。
若是对static关键字仍是不太清楚理解的童鞋能够参考这篇文章,应该算是不错的:深刻理解static关键字
对于final关键字来说,final变量将直接经过值参与序列化,至于代码程序我就再也不贴出来了,你们能够试着用final修饰验证一下!
主要注意的是final 和transient能够同时修饰同一个变量,结果也是同样的,对transient没有影响,这里主要提一下,但愿各位之后在开发中遇到这些状况不会满头雾水!
既然提到了transient关键字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID
了,它是啥呢?基本上有序列化就会存在这个serialVersionUID。
serialVersionUID
适用于Java的序列化机制。简单来讲,Java的序列化机制是经过判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,若是相同就认为是一致的,能够进行反序列化,不然就会出现序列化版本不一致的异常,便是InvalidCastException
,在开发中有时候可写可不写,建议最好仍是写上比较好。
一、变量被transient修饰,变量将不会被序列化
二、transient关键字只能修饰变量,而不能修饰方法和类。
三、被static关键字修饰的变量不参与序列化,一个静态static变量无论是否被transient修饰,均不能被序列化。
四、final变量值参与序列化,final transient同时修饰变量,final不会影响transient,同样不会参与序列化
第二点须要注意的是:本地变量是不能被transient关键字修饰的。变量若是是用户自定义类变量,则该类须要实现Serializable接口
第三点须要注意的是:反序列化后类中static型变量的值其实是当前JVM中对应static变量的值,这个值是JVM中的并非反序列化得出的。
结语:被transient关键字修饰致使不被序列化,其优势是能够节省存储空间。优化程序!随之而来的是会致使被transient修饰的字段会从新计算,初始化!
若是本文对你有一点点帮助,那么请点个赞呗,谢谢~
如有不足或者不正之处,欢迎指正批评,感激涕零!若是有疑问欢迎留言,绝对第一时间回复!
最后,欢迎各位关注宜春的公众号,一块儿探讨技术,向往技术,追求技术,说好了来了就是盆友喔...