對於transient
這個關鍵字或許很陌生基本上沒怎麼用過,但是transient
關鍵字在java中卻起到了不可或缺的地位!
在學習java的過程中transient
關鍵字少見的原因其實離不開它的作用:transient
關鍵字的主要作用就是讓某些被transient關鍵字修飾的成員屬性變數不被序列化。實際上也正是因此,在學習過程中很少用得上序列化操作,一般都是在實際開發中!至於序列化,相信有很多小白童鞋一直迷迷糊糊或沒有具體的概念,以下這篇文章就介紹一下。
1、何謂序列化?
說起序列化,隨之而來的另一個概念就是反序列化,小白童鞋不要慌,記住了序列化就相當於記住了反序列化,因為反序列化就是序列化反過來,所以部落客建議只記住序列化概念即可,省的搞暈自己。
專業術語定義的序列化:
Java提供了一個物件序列化的機制。用一個位元組序列可以表示一個對象,該位元組序列包含該對象的資料、對象的類型和對像中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久保存了一個物件的資訊。反之,該位元組序列還可以從檔案中讀取回來,重構對象,對它進行反序列化。物件的資料、物件的類型和物件中儲存的資料訊息,都可以用來在記憶體中建立物件。
宜春的術語定義序列化:
序列化:位元組—> 物件
##其實,我總結的就是上面的結論,如果不理解,直接參照專業術語的定義,理解之後就記住我的話就行了,記不住,請打死我(我踢m簡直就是天才)
圖理解序列化:啥?你不懂啥是位元組?其實,我在一篇IO流的文章裡就已經介紹了序列化,放心,絕對特別詳細~光看文章名字就知道了~
史上最騷最全最詳細的IO流教程,小白都能看懂! 2、為何要序列化?從上一節提到序列化的概念,知道概念之後,我們就必須知道 為何要序列化了。
講為何要序列化原因之前,博主我舉個栗子:#就像你去街上買菜,一般操作都是用塑膠袋給包裝起來,直到回家要做飯的時候就把菜拿出來。而這一系列操作就像是極了序列化和反序列化! Java中物件的序列化指的是將物件轉換成以位元組序列的形式來表示,這些位元組序列包含了物件的資料和訊息,一個序列化後的物件 可以寫到資料庫或檔案,也可用來 網路傳輸,一般當我們使用 快取cache
遠端呼叫rpc
(網路傳輸)的時候,經常需要讓我們的實體類別實作Serializable接口,目的就是為了讓其可序列化。 ● 在開發過程中要使用transient關鍵字修飾的栗子:
如果一個使用者有一些密碼等訊息,為了安全起見,不希望在網路操作中被傳輸,這些資訊對應的變數就可以加上transient關鍵字。
換句話說,這個欄位的生命週期只存於呼叫者的記憶體而不會寫到磁碟裡持久化。不需要transient● 在開發過程中
關鍵字修飾的栗子:
######1、類別中的字段值可以根據其它字段推導出來。 ###2、看具體業務需求,哪些字段不想被序列化;#########不知道各位有木有想過為什麼要不被序列化呢?其實主要是為了節省儲存空間。優化程序! ###PS:記得之前看
HashMap
原始碼的時候,發現有個欄位是用transient
修飾的,我覺得還是有道理的,確實沒必要對這個modCount字段進行序列化,因為沒有意義,modCount主要用於判斷HashMap是否被修改(像put、remove操作的時候,modCount
都會自增),對於這種變量,一開始可以為任何值,0當然也是可以(new出來、反序列化出來、或克隆clone出來的時候都是為0的),沒必要持久化其值。
當然,序列化後的最終目的是為了反序列化,恢復成原先的Java對象,要不然序列化後幹嘛呢,就像買菜一樣,用塑膠袋包裹最後還是為了方便安全到家再去掉塑膠袋,所以序列化後的位元組序列都是可以恢復成Java物件的,這個過程就是反序列化。
3、序列化與transient的使用
#1、#需要做序列化的物件的類,必須實作序列化接口:Java.lang.Serializable 接口(一個標誌接口,沒有任何抽象方法),Java 中大多數類別都實作了該接口,例如:String
,Integer
類別等,不實作此介面的類別將不會使任何狀態序列化或反序列化,會拋NotSerializableException
例外。
2、底層會判斷,如果目前物件是 Serializable
的實例,才允許做序列化,Java物件 instanceof Serializable
來判斷。
3、在Java 中使用物件流ObjectOutputStream
來完成序列化以及ObjectInputStream
流反序列化
==ObjectOutputStream:透過writeObject()方法做序列化操作==
==ObjectInputStream:透過readObject() 方法做反序列化操作==
#4、該類別的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient
關鍵字修飾。
由於位元組嘛所以一定要涉及流的操作,也就是物件流也叫序列化流ObjectOutputstream,下面進行多種狀況分析序列化的操作程式碼!
在這裡,我真的強烈建議看宜春博客的讀者朋友,請試著去敲,切記一眼帶過或複製過去運行就完事了,特別是小白童鞋,相信我!你一定會有不一樣的收穫。千萬不要覺得浪費時間,有時候慢就是快,宜春親身體會!
3.1、沒有實作Serializable介面進行序列化情況
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(); } } }
運行結果
3.2、實作Serializable介面序列化情況
當我們加上實作Serializable介面再運作會發現,專案中出現的userinfo.txt
檔案內容是這樣的:
其實這都不是重點,重點是序列化操作成功了!
3.3、普通序列化情況
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'}
3.4、transient序列化情況
#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
的屬性在物件被序列化的時候不會被保存(或者說變數不會持久化)
3.5、static序列化情況
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关键字
3.6、final序列化情况
对于final关键字来讲,final变量将直接通过值参与序列化,至于代码程序我就不再贴出来了,大家可以试着用final修饰验证一下!
主要注意的是final 和transient可以同时修饰同一个变量,结果也是一样的,对transient没有影响,这里主要提一下,希望各位以后在开发中遇到这些情况不会满头雾水!
4、java类中serialVersionUID作用
既然提到了transient关键字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID
了,它是啥呢?基本上有序列化就会存在这个serialVersionUID。
serialVersionUID
适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException
,在开发中有时候可写可不写,建议最好还是写上比较好。
5、transient关键字小结
1、变量被transient修饰,变量将不会被序列化
2、transient关键字只能修饰变量,而不能修饰方法和类。
3、被static关键字修饰的变量不参与序列化,一个静态static变量不管是否被transient修饰,均不能被序列化。
4、final变量值参与序列化,final transient同时修饰变量,final不会影响transient,一样不会参与序列化
第二点需要注意的是:本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口
第三点需要注意的是:反序列化后类中static型变量的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。
结语:被transient关键字修饰导致不被序列化,其优点是可以节省存储空间。优化程序!随之而来的是会导致被transient修饰的字段会重新计算,初始化!
本文来自 java入门 栏目,欢迎学习!
以上是深入學習java之transient關鍵字的詳細內容。更多資訊請關注PHP中文網其他相關文章!