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

今天阅读协程源码的时候,看到 EmptyCoroutineContext 类实现了 readResolve() 函数,还定义了一个 serialVersionUID 常量。于是学习了一下这两者的作用,特此记录。

Kotlin 中的单例模式

在 Kotlin 中使用单例很方便,只需要使用 object 关键字即可:

object Singleton

这时就可以全局使用这个单例类了。但如果这个单例类需要序列化,在这个对象序列化之后,再反序列化就会产生一个全新的对象,导致单例模式失效,毕竟单例模式的特点就是全局只能有一个单例。

实验验证

做个实验验证一下,首先,修改 Singleton 类,使其实现 Serializable 接口

object Singleton : Serializable

再对这个单例类进行序列化和反序列化,并打印其原始内存地址和反序列化后生成的对象地址,以作比较:

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() 函数

想要解决这个问题,就要用到 readResolve() 函数。这个函数有一个返回值,指的是对象进行反序列化时,读出来的对象。

所以我们需要对单例进行如下修改,使 Singleton 的 readResolve() 函数返回 Singleton 对象本身:

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

修改后,我们再运行之前的测试代码,输出如下:

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

可以看出反序列后的对象和原始对象已经一致了。

serialVersionUID 的作用

在一个对象被序列化后,如果这个对象的结构被修改了,就可能导致反序列化时不兼容。为了解决这个问题,每个 class 可以定义一个 serialVersionUID 静态变量,用于标识这个类的序列化版本,当这个对象的结构被修改后,就修改一下这个变量,这样就能阻止不匹配的 class 版本。

我测试了一下,如果把 serialVersionUID 设成 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 改变后,需要开发者手动处理 InvalidClassException 异常,当这个异常出现时,很可能是反序列化的对象结构已经被更改了。

如果这个类的结构虽然改变了,但和以前的结构是兼容的,也可以不修改 serialVersionUID 的值,这样反序列化就不会出错了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容

  • 官方文档理解 要使类的成员变量可以序列化和反序列化,必须实现Serializable接口。任何可序列化类的子类都是...
    狮_子歌歌阅读 2,346评论 1 3
  • 在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些...
    懒癌正患者阅读 1,502评论 0 12
  • 什么是序列化与反序列化 序列化是指把对象转换为字节序列的过程(Encoding an object as a by...
    小X感悟阅读 865评论 0 4
  • java序列化与反序列化 对象序列化是一种持久化技术,广泛运用于网络传输、RMI等场景中。java对象存在于JVM...
    Crazy贵子阅读 624评论 0 0
  • 序列化的意义 1.永久存储某个jvm中运行时的对象。2.对象可以网络传输3.rmi调用都是以序列化的方式传输参数 ...
    炫迈哥阅读 627评论 0 0