Kotlin中Java HashMap无法通过存在的Key对象删除条目问题排查
HashMap中使用Data Class作为Key删除失败的排查与解决
问题场景
用Waypoint(继承自抽象类Markup的data class)作为HashMap<Markup, Feature>的Key,预期传入的entry是Map中存在的有效Key,但偶尔会出现remove(entry)返回null的情况。即使通过uuid从Map中找到对应的Key,断言该Key与传入的entryequals和引用都相等,调用containsKey(markup)依然会失败,改用mutableScatterMapOf()也存在同样问题。
相关代码
集合操作代码
val markupFeatureCollection = HashMap<Markup, Feature>() fun remove(entry: Markup) { // 预期entry是Map中存在的有效Key val feature = markupFeatureCollection.remove(entry) // 出现异常情况:删除失败,但需要保留Feature以便后续复用 if (feature == null) { // 通过uuid查找Map中的对应条目 val member = markupFeatureCollection.filter { it.key.uuid == entry.uuid } // 断言存在对应Feature member.values.first() // 获取找到的Key val markup = member.keys.first() // 验证找到的Key与传入entry的相等性 assert(markup == entry) { "entry key was found in markupFeatureCollection but could not be removed" } assert(markup === entry) { "entry key was found in markupFeatureCollection but could not be removed" } // 打印对比两个对象 println("xxx: $markup") println("xxx: $entry") // 此处断言始终失败:明明是从Map中取出的Key,containsKey却返回false assert(markupFeatureCollection.containsKey(markup)) } }
类定义
abstract class Markup( open val uuid: String, open val name: String, open val geoJson: JsonObject ) data class Waypoint( override val uuid: String = UUID.randomUUID().toString(), override val name: String = createDefaultName(), override val geoJson: JsonObject ):Markup( uuid = uuid, name = name, geoJson = geoJson, )
根本原因
虽然Waypoint是data class,且所有成员都声明为val,但其中的geoJson是可变的JsonObject。Data class的hashCode()默认由所有成员变量计算生成,当geoJson的内容被修改后,Waypoint的hashCode会发生变化。
HashMap的工作依赖于Key的hashCode定位桶位置,当Key存入Map后如果hashCode改变,后续的查找、删除操作会根据新的hashCode去错误的桶中查找,即使Key的引用完全相同,也无法匹配到原来的条目,导致containsKey和remove操作失败。
解决方案
改用不可变的uuid作为HashMap的Key,替换原来的Markup对象。因为uuid是字符串类型,内容不可变,hashCode始终稳定,不会出现因内部状态变化导致的匹配失败问题。
内容的提问来源于stack exchange,提问作者jchristof
相关产品推荐
相关产品推荐

