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

时间:2019-06-06 22:16:55

标签: serialization 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使用“人工”手段对它进行反序列化。是反射还是其他合成方式。

我该如何做?

1 个答案:

答案 0 :(得分:2)

我找到了一个“酯”解决方案。使用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

与此同时,我们对此保持执着。

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