修改Kotlin数据类属性后MutableMap.contains返回false问题
我有一个MutableMap,其键为User数据类的对象,值为Dog数据类的列表。将User对象存入Map后,通过持有该对象的变量修改其属性,此时map的contains方法返回false——尽管Map的key已同步更新属性,且对象引用未发生变化。反向操作(从Map取出key修改后测试contains)也会出现同样问题。
示例代码:
data class User( var name: String, var project: String, ) data class Dog( var kind: String ) fun main(args: Array<String>) { var mapUserDogs: MutableMap<User, MutableList<Dog>> = mutableMapOf() var userSelected = User("name2", "P2") mapUserDogs.put( User("name1", "P1"), mutableListOf(Dog("R1"), Dog("R2")) ) mapUserDogs.put( userSelected, mutableListOf(Dog("R21"), Dog("R31")) ) println(userSelected) println(mapUserDogs.keys.toString()) println(mapUserDogs.contains(userSelected)) println(mapUserDogs.values.toString()) println("\n") userSelected.name = "Name3" println(userSelected) println(mapUserDogs.keys.toString()) println(mapUserDogs.contains(userSelected)) println(mapUserDogs.values.toString()) }
运行打印结果:
User(name=name2, project=P2) [User(name=name1, project=P1), User(name=name2, project=P2)] true [[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]] User(name=Name3, project=P2) [User(name=name1, project=P1), User(name=Name3, project=P2)] false [[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]] Process finished with exit code 0
请问如何实现修改User对象属性后,Map的contains方法仍能返回true?
出现这个问题的核心原因是:Kotlin的data class会自动基于所有属性生成equals()和hashCode()方法,而MutableMap(默认是HashMap)依赖这两个方法来定位键。当修改User的可变属性时,对象的hashCode会发生变化,导致Map无法在原来的哈希桶位置找到这个键,即使对象引用完全相同。
下面提供几种可行的解决办法:
1. 将User的属性设为不可变(推荐)
如果业务允许,直接把User的属性改为val,禁止修改:
data class User( val name: String, val project: String, )
这样属性无法被修改,hashCode和equals的结果永远稳定,Map的contains方法就能正常工作。
2. 修改属性后重新更新Map
如果必须要修改属性,修改完成后需要将该键从Map中移除,再重新存入。这样Map会根据新的hashCode将键放到正确的哈希桶位置:
// 修改属性后执行以下代码 val dogs = mapUserDogs.remove(userSelected) ?: mutableListOf() mapUserDogs[userSelected] = dogs // 此时再调用contains就会返回true println(mapUserDogs.contains(userSelected)) // 输出true
3. 自定义equals和hashCode,基于不变的唯一标识
给User添加一个不变的唯一标识(比如id),然后手动实现equals()和hashCode(),只依赖这个不变标识。这样修改其他属性不会影响键的匹配:
data class User( val id: String, // 唯一不变标识 var name: String, var project: String, ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as User return id == other.id } override fun hashCode(): Int { return id.hashCode() } }
使用这种方式,无论修改name还是project,equals和hashCode的结果都不会变,Map的contains方法始终能正确识别该键。
内容的提问来源于stack exchange,提问作者Daniel Florez

