Kotlin 序列化與反序列化,readResolve() 和 serialVersionUID 是用來做什么的?

今天閱讀協(xié)程源碼的時候,看到 EmptyCoroutineContext 類實現(xiàn)了 readResolve() 函數(shù),還定義了一個 serialVersionUID 常量。于是學(xué)習(xí)了一下這兩者的作用,特此記錄。

Kotlin 中的單例模式

在 Kotlin 中使用單例很方便,只需要使用 object 關(guān)鍵字即可:

object Singleton

這時就可以全局使用這個單例類了。但如果這個單例類需要序列化,在這個對象序列化之后,再反序列化就會產(chǎn)生一個全新的對象,導(dǎo)致單例模式失效,畢竟單例模式的特點就是全局只能有一個單例。

實驗驗證

做個實驗驗證一下,首先,修改 Singleton 類,使其實現(xiàn) Serializable 接口

object Singleton : Serializable

再對這個單例類進行序列化和反序列化,并打印其原始內(nèi)存地址和反序列化后生成的對象地址,以作比較:

Log.d("~~~", "Singleton: $Singleton")
val fileSavePath = "${cacheDir}${File.separator}test.txt"
val fileOutputStream = FileOutputStream(fileSavePath)
ObjectOutputStream(fileOutputStream).use {
    it.writeObject(Singleton)
}
val fileInputStream = FileInputStream(fileSavePath)
ObjectInputStream(fileInputStream).use {
    val obj = it.readObject()
    if (obj is Singleton) {
        Log.d("~~~", "obj is Singleton, $obj")
    } else {
        Log.d("~~~", "obj isn't Singleton, $obj")
    }
}

運行程序,輸出如下:

~~~: Singleton: com.example.myapplication.Singleton@a3df01b
~~~: obj is Singleton, com.example.myapplication.Singleton@3ef6564

可以看出,反序列化后生成的對象地址和原始對象地址是不一致的,證實了反序列化后生成了一個新對象。

解決方式 —— readResolve() 函數(shù)

想要解決這個問題,就要用到 readResolve() 函數(shù)。這個函數(shù)有一個返回值,指的是對象進行反序列化時,讀出來的對象。

所以我們需要對單例進行如下修改,使 Singleton 的 readResolve() 函數(shù)返回 Singleton 對象本身:

object Singleton : Serializable {
    private fun readResolve(): Any = Singleton
}

修改后,我們再運行之前的測試代碼,輸出如下:

~~~: Singleton: com.example.myapplication.Singleton@a3df01b
~~~: obj is Singleton, com.example.myapplication.Singleton@a3df01b

可以看出反序列后的對象和原始對象已經(jīng)一致了。

serialVersionUID 的作用

在一個對象被序列化后,如果這個對象的結(jié)構(gòu)被修改了,就可能導(dǎo)致反序列化時不兼容。為了解決這個問題,每個 class 可以定義一個 serialVersionUID 靜態(tài)變量,用于標(biāo)識這個類的序列化版本,當(dāng)這個對象的結(jié)構(gòu)被修改后,就修改一下這個變量,這樣就能阻止不匹配的 class 版本。

我測試了一下,如果把 serialVersionUID 設(shè)成 0,序列化存到本地后,再把 serialVersionUID 改成 1。進行反序列化時,程序拋出了 InvalidClassException 異常:

com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 8278
    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.io.InvalidClassException: com.example.myapplication.Singleton; local class incompatible: stream classdesc serialVersionUID = 0, local class serialVersionUID = 1
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:624)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1713)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1594)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1872)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1412)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
        at com.example.myapplication.MainActivity.onCreate$lambda-1(MainActivity.kt:23)
        at com.example.myapplication.MainActivity.$r8$lambda$U17Gk-Q12NTUVdhVQSbB0lbdtEQ(Unknown Source:0)
        at com.example.myapplication.MainActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
        at android.view.View.performClick(View.java:7448)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
        at android.view.View.performClickInternal(View.java:7425)
        at android.view.View.access$3600(View.java:810)
        at android.view.View$PerformClick.run(View.java:28305)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 

所以在 serialVersionUID 改變后,需要開發(fā)者手動處理 InvalidClassException 異常,當(dāng)這個異常出現(xiàn)時,很可能是反序列化的對象結(jié)構(gòu)已經(jīng)被更改了。

如果這個類的結(jié)構(gòu)雖然改變了,但和以前的結(jié)構(gòu)是兼容的,也可以不修改 serialVersionUID 的值,這樣反序列化就不會出錯了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 官方文檔理解 要使類的成員變量可以序列化和反序列化,必須實現(xiàn)Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,546評論 1 3
  • 在Java中,我們可以通過多種方式來創(chuàng)建對象,并且只要對象沒有被回收我們都可以復(fù)用該對象。但是,我們創(chuàng)建出來的這些...
    懶癌正患者閱讀 1,669評論 0 12
  • 什么是序列化與反序列化 序列化是指把對象轉(zhuǎn)換為字節(jié)序列的過程(Encoding an object as a by...
    小X感悟閱讀 960評論 0 4
  • java序列化與反序列化 對象序列化是一種持久化技術(shù),廣泛運用于網(wǎng)絡(luò)傳輸、RMI等場景中。java對象存在于JVM...
    龜龜鴨閱讀 684評論 0 0
  • 序列化的意義 1.永久存儲某個jvm中運行時的對象。2.對象可以網(wǎng)絡(luò)傳輸3.rmi調(diào)用都是以序列化的方式傳輸參數(shù) ...
    炫邁哥閱讀 717評論 0 0

友情鏈接更多精彩內(nèi)容