🔍 Android 开发中的 设计一个可以监听List集合数据变化的类(设计原理与最佳实践)引言在 Android 开发中,我们经常需要监听集合数据的变化来驱动 UI 更新。比如:用户选中了多少个模型、购物车里有几件商品、已勾选了哪些标签……这类"集合变更 → UI 响应"的需求随处可见。你可能会想:「这不是用LiveDataListT或StateFlow…等等就能搞定的吗?」是的,但在某些场景下(特别是与 WebView/JS 交互、需要精确感知单个元素增减的场景),一个轻量的可观察集合反而更直观、更高效。今天我们就来聊一个实际项目中的案例——ObservableHashSet,一个看似只有 30 行代码的小类,却暗藏了几个容易被忽视的致命 Bug。一、业务场景:3D 模型的"隐藏"功能先说背景。我们的项目是一个建筑行业的 BIM 三维可视化应用,其中有一个"隐藏模型"功能:用户选择若干 3D 模型 → 点击"隐藏" → 模型从场景中消失 → UI 实时显示"已选择 N 个模型实例"核心需求很简单:当集合元素变化时,自动更新 UI 提示文案。为此,我们设计了一个ObservableHashSet,继承LinkedHashSet(保持插入顺序),在add/remove时通知监听器:classObservableHashSetT:LinkedHashSetT(){privatevarlistener:SetChangeListenerT?=nullfunsetChangeListener(listener:SetChangeListenerT?){this.listener=listener}//重写add并添加回调overridefunadd(element:T):Boolean{valadded=super.add(element)if(added){listener?.onElementAdded(element,this)}returnadded}//重写remove并添加回调overridefunremove(element:T):Boolean{valremoved=super.remove(element)if(removed){listener?.onElementRemoved(element,this)}returnremoved}interfaceSetChangeListenerT{funonElementAdded(element:T,set:ObservableHashSetT)funonElementRemoved(element:T,set:ObservableHashSetT)}}看起来很简洁对吧?在实际使用中,我们这样注册监听://初始化valhiddenAddNewIdSet:ObservableHashSetString=ObservableHashSet()hiddenAddNewIdSet.setChangeListener(object:SetChangeListenerString{overridefunonElementAdded(element:String,set:ObservableHashSetString){showHidingModelCount()// 更新 UI:"已选择 N 个模型实例"}overridefunonElementRemoved(element:String,set:ObservableHashSetString){if(set.isEmpty())showTips()elseshowHidingModelCount()}})一切看起来都挺好。但是,魔鬼藏在细节里。二、问题分析:三个隐藏的致命 Bug🔴 Bug 1:clear()是"静默清空"在业务代码中,我们多处调用了hiddenAddNewIdSet.clear():// 点击"隐藏模型"按钮后hiddenAddNewIdSet.clear()showHiddenModelCount()问题:HashSet.clear()的内部实现是直接调用底层HashMap.clear(),它不会逐个调用remove()!// java.util.HashSet 源码publicvoidclear(){map