最近在开发的过程中,遇到了对象集合List的操作问题,主要是涉及到引用对象集合list的操作修改问题,这个问题困扰了好几个小时,特此记录一下,以防以后可以迅速回忆,有类似情况的也可以给大家一个排查问题的思路。
一,问题背景:
1,需要2个集合list列表:一个用于保存已选择过的list数据(列表1),每次进入操作界面,需要标识已选择list item;一个用于记录当前操作选择的list数据(列表2),进入操作界面,需要用已选择过的list数据(列表1)进行先赋值,后面根据当前界面的操作修改 列表2 的数据;在退出操作界面的时候,将列表2的数据重置赋值给列表1;
2,2个列表的数据都是引用对象的list ,非基本类型(Int、Double等)的的list
3,使用的是kotlin的LiveData 记录 列表1 的数据,列表使用的mutableListOf()
二,问题描述:
这里引发了2个问题:
1,修改列表2的对象数据的某个成员变量时,会修改到列表1的数据;
2,在操作选择列表item项的时候选择或不选1个的时候,如果有引用到 列表2的数据 对方法内的局部变量list进行赋值,那么会触发到整个数据列表list LiveData的Observer监听数据变化失效。诡异的是,选择2个或者不选是正常的,能触发LiveData的Observer监听数据变化;
三,原因排查:
1,问题一这个原因很明显,是引用了同一个地址的列表数据,想起Java时候list.addAll()进行副本生成可以解决这个问题,然后就addAll()试了一下(注:kotlin代码),并没有生效,然后就查找如何生成list的副本,试了一通,并没有奏效,网上博主很多提到的操作都是基于基本数据类型操作有效的,对于对象列表list根本无效,这个有点坑,自己就写个小demo进行验证,毕竟实践是检查真理的唯一标准呢。
data class Person(
var name: String,
var age: Int,
var isStudent: Boolean,
)
fun test() {
//测试 .toCollection(mutableListOf()) 【基础类型有效】
val original: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5);
val copy: MutableList<Int> = original.toCollection(mutableListOf())
copy.add(8)
LogUtils.error("test## [toCollection] original: " + original)
LogUtils.error("test## [toCollection] copy: " + copy)
//测试 toMutableList() 【基础类型有效】
val original2: MutableList<Int> = mutableListOf(11, 12, 13, 14, 15);
val copy2: MutableList<Int> = original2.toMutableList()
copy2.add(18)
copy2.add(20)
LogUtils.error("test## [toMutableList] original2: " + original2)
LogUtils.error("test## [toMutableList] copy2: " + copy2)
//测试 addAll() 【基础类型有效】
val original3: MutableList<Int> = mutableListOf(21, 22, 23, 24, 25);
val copy3: MutableList<Int> = mutableListOf<Int>()
copy3.addAll(original3)
copy3.add(2000)
LogUtils.error("test## [addAll] original3: " + original3)
LogUtils.error("test## [addAll] copy3: " + copy3)
//测试对象 toMutableList 【无效】
val original4 = mutableListOf<Person>(
Person("dong", 20, true),
Person("nan", 35, false),
Person("xi", 50, false)
)
val copy4 = original4.toMutableList()
copy4.get(2).isStudent = true
LogUtils.error("test## [object-toMutableList] original4: " + original4)
LogUtils.error("test## [object-toMutableList] copy4: " + copy4)
//测试对象 addAll() 【无效】
val original5 = mutableListOf<Person>(
Person("hei", 10, true),
Person("bai", 25, false),
Person("hui", 40, false)
)
val copy5 = mutableListOf<Person>()
copy5.addAll(original5)
copy5.get(2).isStudent = true
LogUtils.error("test## [object-addAll] original5: " + original5)
LogUtils.error("test## [object-addAll] copy5: " + copy5)
//测试对象 toCollection 【无效】
val original6 = mutableListOf<Person>(
Person("lan", 40, true),
Person("lv", 55, false),
Person("hong", 60, false)
)
val copy6 = original6.toCollection(mutableListOf())
copy6.get(2).isStudent = true
LogUtils.error("test## [object-toCollection] original6: " + original6)
LogUtils.error("test## [object-toCollection] copy6: " + copy6)
//测试对象 copy() 【注意:引用对象的时候有效】
val original7 = mutableListOf<Person>(
Person("one", 7, true),
Person("two", 8, false),
Person("free", 9, false)
)
val copy7 = mutableListOf(
original7.get(0).copy(),
original7.get(1).copy(),
original7.get(2).copy()
)
copy7.get(2).isStudent = true
LogUtils.error("test## [object-copy()] original7: " + original7)
LogUtils.error("test## [object-copy()] copy7: " + copy7)
}
输出结果如下:
验证完,基本就是使用copy()的方式去生成对象副本
2,问题2的虽然也解决了,但是原因至今没有想明白,因为livedata的value是有赋值修改的,但是并没有回调到监听数据的Oberver监听回调里面,是完全没有回调进来,尝试过debug进入livedata的setvalue() 方法,一步步debug下来,并没有发现任何端倪。猜测可能的原因是,是LiveData的setvalue()方法对于判断数据变更监听做了暂时未知的判断(例如是否监听对象的地址的一致等);
而问题2的解决原因是操作了列表2引起的,那么在选择操作时,不直接操作列表2的当前选中数据list不就可以了,那么怎么拿到当前已选择的列表数据,因为列表都是使用adaper适配器进行实现的,那么直接将adapter.data的数据进行读取出来进行数据赋值不就好了。
//####################### 修改前 #######################
val selectList = curSelectList //将当前的数据list赋值给局部变量
//省略中间进行操作的代码 ....
//...... 期间 selectList 对象列表的数据会因为操作而变更
//.......
curSelectList = selectList //最后将修改的数据保存回成员变量 curSelectList
//####################### 修改后 ########################
val selectList = adapter.data.filter {
it.isStudent == true //这里以Person对象为例子
}.toMutableList() //不直接操作成员变量 curSelectList
//省略中间进行操作的代码 ....
//...... 期间 selectList 对象列表的数据会因为操作而变更
//.......
//curSelectList = selectList //最后也不直接操作成员变量 curSelectList (放弃操作该成员变量)
//退出界面也是通过 adapter.data 进行读取选中的数据
四、解决方案:
1,基础数据类型可以通过toMutableList()等方法进行副本赋值;但是对象数据需要通过copy()进行副本赋值,否则会操作的是同一个地址的对象;
2,通过adapter.data的方式获取选中数据,而不通过成员变量进行记录和获取;
ps:关于问题2中LiveData中的监听回调失效问题,各位有思路可以说说,我看到会进行实践验证。
五、总结:
1,有遇到列表数据操作修改,修改到了源数据的类似问题,可以借鉴参考一下
2,有时候使用别人封装的框架,例如jetpack的livedata,遇到一些诡异的异常时,往往会让你痛苦万分,这时候就明白,多好的框架都不如最基本的代码,或者自己实现的代码。有问题还能修改处理,不熟悉的框架只能食之无味弃之可惜的两难境地。
3,不要迷信任何代码权威,自己亲手去实践,自己写的东西才是最有力量的