踩坑记录:Kotlin data class 在 Map 和 Set 中无法读取的问题
Kotlin 的 data class 自动覆写了 equals()
和 hashCode()
两个方法。虽然有些时候可以给用户带来便利,但是如果使用不慎就会出现一些匪夷所思的问题,比如这里遇到的一个问题是,将一个对象作为键放到 Map (或者 Set)之后,修改了对象的一些属性之后再从 Map (或者 Set)用它作为 key 读取的时候返回的数据是 null.
下面一段示例代码,取出来的结果是 null:
1 | fun main(vararg args: String) { |
这个程序的输出结果是,
1 | 1 |
从 Kotlin 反编译的结果来看,
1 | public final class Sample { |
也就是说 data class 覆写了 hashCode()
和 equals()
方法,并且内部使用所有字段参与两个方法的计算,所以如果任意一个字段发生变化,前后两个 hashCode()
将会发生变化,而 HashMap 的 get()
方法先通过哈希码进行散列,只有出现哈希冲突的时候才使用 equals()
进行计算。虽然存储在 Map 中的对象是同一个,但是因为前后的哈希值发生变化,所以前后散列的位置是不同的。因此修改了字段属性之后,再使用 get()
方法获取数据的时候,找到的坑位(position)不是之前的那个坑,所以就不会走到 eqauls()
的方法进行校验。当然,如果当数据修改了之后整个 Map 做了一次 rehash,可能会解决这个问题(当然我们不会这么做)。
当我们在做实际开发的时候,本身覆写 hashCode()
和 equals()
方法的时候就需要考虑具体的应用场景。比如说,使用数据对象的数据库索引或者某些业务 ID 作为对象的唯一标识,而不是所有的字段进行计算,而 data class 默认采用使用所有字段参与计算,并不一定满足我们业务场景的需求。
所以,结论就是,data class 看上去简洁但是有欺骗性,它帮你覆写了一些方法,大部分应用场景下可能不存在问题,但是一旦出现问题就是匪夷所思的。既然我们无法保证 data class 是否会应用到诸如作为 Map 和 Set 的 key 的场景,所以,最好的避免方法就是,不要在项目中使用 data class!