Kotlin集合异常行为:可变数据类存入Set后的问题解析
Kotlin数据类在MutableSet中的行为解析
核心原因:数据类的equals与hashCode实现
Kotlin数据类会自动基于主构造器中的所有属性生成equals()和hashCode()方法:
- equals():先判断是否为同一引用,是则直接返回true;否则逐一比较所有属性的值,全部相等才返回true。
- hashCode():基于所有属性的值计算哈希值,只要属性变化,哈希值就会跟着改变。
Kotlin的mutableSetOf()默认实现是LinkedHashSet,底层逻辑和HashSet一致:依靠hashCode()确定元素存储的哈希桶,依靠equals()判断元素是否相等。
疑问1:修改属性后,set.contains(pos)为何有时返回true有时返回false?
当你把pos实例添加到set后,修改它的x/y属性:
- 实例的引用没有改变,但
hashCode()会因为属性更新而变化。 - 调用
set.contains(pos)时,流程是:- 先计算当前
pos的新哈希值,去对应的哈希桶查找元素。 - 如果新哈希值和添加时的旧哈希值碰巧相同(哈希碰撞):会找到存储该实例的桶,然后用equals()比较——因为是同一引用,equals()直接返回true,所以contains返回true。
- 如果新哈希值和旧哈希值不同:会去新的哈希桶查找,而该实例实际存在旧哈希桶里,新桶找不到,所以contains返回false。
- 先计算当前
你代码中修改y为-37时刚好触发了哈希碰撞,所以返回true;改成-35时哈希值不同,就返回false。
疑问2:为何setByCopy.contains(pos)返回false?
setByCopy里存的是pos.copy()生成的全新实例:
- 这个新实例的属性是添加时的
x=-9,y=-6,但当前pos的属性已经变成x=-8,y=-37。 - 调用contains()时,哪怕当前
pos的哈希值和copy实例的哈希值碰巧一致,equals()会比较两者的属性值,结果不相等,所以返回false。
总结:可变属性数据类存入Set的风险
因为数据类的hashCode和equals依赖可变属性,一旦属性修改:
- 实例在Set中的存储位置(哈希桶)和当前哈希值不匹配,会导致contains()、remove()等方法行为异常。
- 如果要维护唯一坐标的Set,建议把数据类的属性改成不可变的val,每次更新坐标时创建新实例,这样Set的行为会完全符合预期。
内容的提问来源于stack exchange,提问作者CaptainDaVinci
相关产品推荐
相关产品推荐

