无法在 Kotlin 中序列化对象单例



在Kotlin 中,密封类的一个常见用途是将具有一些数据(使用data class)和其他单例(使用object)的情况组合在一起,如下所示:

sealed class Location {
object Unknown : Location()
data class Known(val lat: Float, val lon: Float) : Location()
}

我们使用的系统需要模型来实现Serializable.令我惊讶的是,这不适用于object单例,如您在此演示中看到的那样:

https://pl.kotl.in/vd_TgUR6a

输出以下内容:

Success: Known(lat=37.563934, lon=-116.85123) and Known(lat=37.563934, lon=-116.85123) are equal
Failure: sample.Location$Unknown@7c3df479 and sample.Location$Unknown@452b3a41 are not equal

实例 ID 不同。我的猜测是JVM使用"人工"手段对其进行反序列化。无论是反射还是其他合成方式。

我怎样才能做到这一点?

我发现了一个"最佳"解决方案。使用readResolve()的隐藏 JVM API

sealed class Location: Serializable {
object Unknown : Location() {
private fun readResolve() : Any? = Location.Unknown
}
data class Known(val lat: Float, val lon: Float) : Location()
}

此处的代码:https://pl.kotl.in/tMGf-AIfq

生成以下输出:

Success: Known(lat=37.563934, lon=-116.85123) and Known(lat=37.563934, lon=-116.85123) are equal
Success: sample.Location$Unknown@452b3a41 and sample.Location$Unknown@452b3a41 are equal

此函数在从流加载对象后调用,并允许返回不同的对象而不是从内存加载的对象。

这意味着即使我们的object有状态,也可以安全使用(但不是应该。并且应该更加节省内存。

这里有一张票:https://youtrack.jetbrains.com/issue/KT-9499

看起来它可能作为另一张票的一部分来添加@JVMSerializable注释:https://youtrack.jetbrains.com/issue/KT-14528

旧答案:

我发现的最佳解决方案是使单例对象覆盖默认equalshashCodetoString使其在功能上相同:

sealed class Location: Serializable {
object Unknown : Location() {
override fun equals(other: Any?) = other is Unknown
override fun hashCode() = toString().hashCode()
override fun toString(): String = "Location.Unknown"
}
data class Known(val lat: Float, val lon: Float) : Location()
}

这是演示: https://pl.kotl.in/oNd-mnWlQ

输出为:

Success: Known(lat=37.563934, lon=-116.85123) and Known(lat=37.563934, lon=-116.85123) are equal
Success: Location.Unknown and Location.Unknown are equal

如果内存不是一个极端问题,这是一个可能的解决方案,因为它将为每个反序列化的对象创建一个对象。尽管该对象的占用空间非常小,但在大多数情况下,这不应该是一个问题,但需要注意。

如果 Kotlin 可以使用枚举类实现object,这将不是问题,因为它们在 JVM 中的序列化方式不同。但是,它们不能扩展类(仅扩展接口),因此它不适用于此实例。

总而言之,甲骨文计划最终放弃它: https://www.infoworld.com/article/3275924/oracle-plans-to-dump-risky-java-serialization.html

与此同时,我们被困住了。

注意:只要对象不可变,这就可以工作。如果我们允许更改状态,那么当我们开始在每个反序列化对象上具有不同的状态时,所有地狱都会打开。

最新更新