Kotlin中如何让contains方法忽略指定属性?
在Android RecyclerView实现的对象追踪功能中,点击收藏图标后产品的isFavourite属性会改为true。RecyclerView的onScrollStateChanged触发时会调用trackVisibleItems方法,通过shownTrackablePositions.contains(it)判断对象是否已被追踪。但isFavourite属性更新后,contains会将其判定为不同对象,导致重复追踪。尝试将isFavourite放在Kotlin data类主构造函数外仍未解决,需要让contains忽略该属性的方案。
方案1:重写equals()和hashCode()方法
Kotlin data类默认会基于主构造函数属性生成equals和hashCode,如果你的业务类继承自TrackableObject,可以手动重写这两个方法,明确排除isFavourite属性的比较:
data class Product( val productId: String, val productName: String // 核心业务属性(不包含isFavourite) ) : TrackableObject() { var isFavourite: Boolean = false // 收藏状态放在主构造外 override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Product // 仅比较核心唯一标识属性 if (productId != other.productId) return false if (productName != other.productName) return false // 不参与isFavourite的比较 return true } override fun hashCode(): Int { var result = productId.hashCode() result = 31 * result + productName.hashCode() // 不加入isFavourite的哈希值 return result } // 实现TrackableObject的抽象方法 override fun getClickEventName(): String = "product_click" override fun getViewEventName(): String = "product_view" override fun getTrackParams(): MutableSet<Pair<String, Any>> { return mutableSetOf( "product_id" to productId, "is_favourite" to isFavourite ) } }
修改后,shownTrackablePositions.contains(it)会基于核心属性判断,完全忽略isFavourite的状态变化。
方案2:用唯一标识代替对象存入集合
放弃存储TrackableObject实例,改为存储对象的唯一标识(比如产品ID),从根源上避免属性变化带来的判断错误:
步骤1:调整集合类型
// 将原有的MutableSet<TrackableObject>改为存储唯一ID private val shownTrackablePositions = mutableSetOf<String>()
步骤2:新增抽象方法并修改追踪逻辑
给TrackableObject新增获取唯一ID的抽象方法:
abstract class TrackableObject { abstract fun getUniqueId(): String // 返回对象唯一标识 // 原有抽象方法... } // 产品类实现该方法 data class Product(val productId: String, ...) : TrackableObject() { override fun getUniqueId(): String = productId // ... }
修改trackVisibleItems方法:
private fun trackVisibleItems(visiblePositions: IntRange) { for (position in visiblePositions) { if (position != -1) { trackableCollectionCallback?.getViewedItem(position)?.let { item -> val itemUniqueId = item.getUniqueId() if(isPresent() && !shownTrackablePositions.contains(itemUniqueId)){ item.trackView() trackableCollectionCallback?.onTrackedItem(item, position) shownTrackablePositions.add(itemUniqueId) } } } } }
这种方式不受对象属性变化影响,是稳定性最高的方案之一。
方案3:基于对象引用判断(适合实例不重建的场景)
如果RecyclerView不会重新创建TrackableObject实例(仅更新属性而非生成新对象),可以使用基于内存引用的集合,忽略equals方法的影响:
import java.util.Collections // 创建基于对象引用判断的Set private val shownTrackablePositions = Collections.newSetFromMap(IdentityHashMap<TrackableObject, Boolean>())
该集合判断对象是否存在时,仅比较内存引用,不管属性如何变化,只要是同一个实例就会被判定为已存在。但如果Adapter刷新时重新生成对象实例,此方案会失效,需根据业务场景选择。
内容的提问来源于stack exchange,提问作者Taimoor Khan

