在Kotlin中,密封类的一种常见用法是将具有某些数据(使用data class
)和其他单例(使用object
)的情况组合在一起,例如:>
sealed class Location {
object Unknown : Location()
data class Known(val lat: Float, val lon: Float) : Location()
}
我们正在使用的系统需要模型来实现Serializable
。令我惊讶的是,这无法与object
单例一起使用,如您在此演示中所见:
输出以下内容:
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使用“人工”手段对它进行反序列化。是反射还是其他合成方式。
我该如何做?
答案 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
旧答案:
我发现的最佳解决方案是使单例对象覆盖默认的equals
,hashCode
和toString
以使其在功能上相同:
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
与此同时,我们对此保持执着。
注意:只要对象不可变,此方法就起作用。如果我们允许更改状态,那么当我们在每个反序列化的对象上开始具有不同的状态时,所有的地狱都将打开。