Kotlin中RecyclerView适配器使用DiffUtil的正确实现疑问
代码问题先修正
你目前的代码存在两处核心逻辑错误,先优先修复:
- MainAdapter的DiffUtil回调职责混淆
areItemsTheSame和areContentsTheSame的作用完全不同,你现在的写法把两个逻辑合并,会导致DiffUtil优化完全失效 - NestedRecyclerView的内容比较有笔误
areContentsTheSame的判断里你误把newItem写成了oldItem,oldItem.isRead == oldItem.isRead这类判断永远返回真,内容变更时不会触发刷新,需要改成和newItem对应属性比较。
问题逐个解答
1. 此处的DiffUtil.ItemCallback中,我是否需要额外比较key的type、sender等其他属性?
不需要。你用到的GroupKey、Sender、RoleType都是Kotlin数据类(data class),数据类会自动重写equals()方法,用==比较时会自动遍历比较所有主构造函数的属性,你不需要手动逐个判断内部属性。
另外需要修正你MainAdapter的DiffUtil写法:
areItemsTheSame的作用是判断两个条目是不是同一个逻辑条目(比如同一个分组的唯一标识匹配),仅需要比较唯一标识即可,比如如果GroupKey是分组的唯一标识,这里写return oldItem.key == newItem.key即可。areContentsTheSame的作用是判断同一个逻辑条目的内容有没有发生变化,是否需要刷新UI,这里才需要比较所有影响UI展示的属性,你现在的写法oldItem.value == newItem.value && oldItem.key == newItem.key是对的,放在这个方法里即可。
2. Kotlin中==、===以及equals()分别应该在什么场景使用?
==:结构相等比较,Kotlin中默认调用equals(),且自带空安全,是绝大多数场景下比较内容的首选。数据类的==会自动比较所有主构造属性,普通类默认比较内存地址,重写equals()后会按重写逻辑比较。===:引用相等比较,仅用于判断两个变量是否指向内存中的同一个对象实例,普通业务开发很少用到。equals():和==逻辑完全等价,但不支持空安全,调用方为null时会直接崩溃,Kotlin开发中不需要主动使用这个方法,直接用==即可。
3. 比较int、boolean、String类型时应该用==还是其他比较方式?
Kotlin中这些基础类型、字符串类型直接用==即可,会自动做值比较,不需要额外用其他方法,且自带空安全,比如两个字符串比较内容直接写str1 == str2就可以正常工作。
嵌套列表疑问:是否需要额外比较sender的其他属性?
不需要,Sender是数据类,==会自动比较它的company、id属性,而RoleType也是数据类,也会自动递归比较内部的name、id属性,不需要你手动逐个写内部属性的判断。
4. areItemsTheSame方法中应该比较id还是直接写oldItem == newItem?
必须比较条目的唯一标识(比如Item的id),绝对不能直接写oldItem == newItem。areItemsTheSame的作用是判断新旧列表里的两个条目是不是同一个逻辑条目,比如同一个id的消息,就算已读状态变了,也属于同一个条目,DiffUtil就会继续走内容比较,触发局部刷新。如果直接写oldItem == newItem,同一个id的条目只要内容变了,就会被判定为是两个不同的条目,DiffUtil会执行删除旧条目+插入新条目的逻辑,不仅失去局部刷新的优化,还会导致列表闪烁、动画异常。
5. 使用DiffUtil时更新适配器列表的正确方式是什么?是否只需要调用submitList即可?
是的,只要你的DiffUtil回调逻辑正确,直接调用submitList传入新列表即可,DiffUtil会自动计算差异,自动执行对应的条目增删改移动的刷新,完全不需要手动调用notifyDataSetChanged。
需要注意一个常见坑:不能直接修改原列表再submit同一个列表对象,DiffUtil会判断新旧列表是同一个对象,跳过差异计算。每次更新时要传入新的列表对象,比如修改完数据后调用submitList(旧列表.toMutableList())生成新对象传入即可。
内容的提问来源于stack exchange,提问作者Compose Learner

