一次由 List.addAll 所引起的深入

2,598 阅读18分钟

前言

今天遇到一个问题,是关于 MutableList.addAll(specifiedCollection)。我发现执行addAll后,列表中的数据就会随着 specifiedCollection 一起变化,也就是说指向了同一内存引用地址。

上案例

我们ViewModel中有一个itemDataList,表示水果的价格,如:[ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]

class TestViewModel : ViewModel() {
    private val TAG = "TestViewModel"

    private val itemDataList: MutableList<ItemData> = mutableListOf(
        ItemData("西瓜", 3),
        ItemData("苹果", 2),
    )
    private val _itemDataListMld: MutableLiveData<List<ItemData>> = MutableLiveData<List<ItemData>>().apply {
        postValue(itemDataList)
    }
    val itemDataLd: LiveData<List<ItemData>> = _itemDataListMld

    fun getItemDataList() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                //重复10次修改动作。
                repeat(10) {
                    //随机修改列表中的数据
                    (0..1).random().let {
                        Log.e(TAG, "getItemDataList: random change index = $it")
                        itemDataList[it].price = (1..100).random()
                    }
                    _itemDataListMld.postValue(itemDataList)
                    delay(1000)
                }

            }
        }
    }

}

当调用ViewModel.getItemDataList()后,就会重复随机修改列表中的数据,并通过postValue将修改后的列表数据给到itemDataLd,通知给相对应的活跃观察者对象。

Activity中进行观察该itemDataLd对象,在收到数据更新通知后,打印数据,并对未初始化的oldItemDataList进行初始化。

class TestActivity : AppCompatActivity() {
    private val TAG = "TestActivity"
    private lateinit var binding: ActivityTestBinding
    private lateinit var viewModel: TestViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel = ViewModelProvider(this)[TestViewModel::class.java]

        binding.updateListBtn.setOnClickListener {
            viewModel.getItemDataList()
        }

        val oldItemDataList: MutableList<ItemData> = mutableListOf()
        var isInit = false

        viewModel.itemDataLd.observe(this) { newItemDataList ->
            Log.e(TAG, "onCreate: itemDataLd.observe oldItemDataList = $oldItemDataList")
            Log.e(TAG, "onCreate: itemDataLd.observe newItemDataList = $newItemDataList")

            //对于 oldItemDataList 只进行一次初始化
            if (!isInit) {
                oldItemDataList.clear()
                val isAddAllData = oldItemDataList.addAll(newItemDataList)
                Log.e(TAG, "onCreate: addALL ************************ isAddAllData = $isAddAllData")
                isInit = true
            }

        }

    }
}

通过上述代码,可以看出,我们只对 oldItemDataList 只进行一次初始化操作,接着后面收到新的newItemDataList数据通知也不会去修改 oldItemDataList

由于我们在实例化_itemDataListMld对象时,会立即进行postValue(itemDataList),所以在进入Activity后会立马收到关于itemDataLd最新数据的通知,打印Log看看。

17:25:47.518 26784-26784   onCreate: itemDataLd.observe oldItemDataList = []
17:25:47.518 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
17:25:47.518 26784-26784   onCreate: addALL ************************ isAddAllData = true

通过Log可以看到,oldItemDataList 一开始是空的,接着对于进行初始化,addAll 操作返回 true,说明addAll操作成功。

接着,我们再触发一下 viewModel.getItemDataList 来打印一下Log看看。

17:25:59.310 26784-26965   getItemDataList: random change index = 1
17:25:59.315 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=21)]
17:25:59.315 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=21)]
17:26:00.321 26784-26965   getItemDataList: random change index = 0
17:26:00.323 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=39), ItemData(name=苹果, price=21)]
17:26:00.323 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=39), ItemData(name=苹果, price=21)]
17:26:01.325 26784-26965   getItemDataList: random change index = 0
17:26:01.327 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=63), ItemData(name=苹果, price=21)]
17:26:01.328 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=63), ItemData(name=苹果, price=21)]
17:26:02.330 26784-26965   getItemDataList: random change index = 0
17:26:02.332 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=21)]
17:26:02.332 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=21)]
17:26:03.335 26784-26965   getItemDataList: random change index = 1
17:26:03.337 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=74)]
17:26:03.337 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=74)]
17:26:04.338 26784-26965   getItemDataList: random change index = 1
17:26:04.340 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=44)]
17:26:04.341 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=75), ItemData(name=苹果, price=44)]
17:26:05.344 26784-26965   getItemDataList: random change index = 0
17:26:05.347 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=22), ItemData(name=苹果, price=44)]
17:26:05.347 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=22), ItemData(name=苹果, price=44)]
17:26:06.350 26784-26965   getItemDataList: random change index = 1
17:26:06.353 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=22), ItemData(name=苹果, price=33)]
17:26:06.353 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=22), ItemData(name=苹果, price=33)]
17:26:07.355 26784-26965   getItemDataList: random change index = 0
17:26:07.359 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=91), ItemData(name=苹果, price=33)]
17:26:07.359 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=91), ItemData(name=苹果, price=33)]
17:26:08.360 26784-26965   getItemDataList: random change index = 0
17:26:08.362 26784-26784   onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=46), ItemData(name=苹果, price=33)]
17:26:08.363 26784-26784   onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=46), ItemData(name=苹果, price=33)]

通过Log可以看出,虽然我们没有对 oldItemDataList 进行新的操作,但是 oldItemDataList 的数据还是会随着LiveData推过来的新的List<ItemData>数据变化而变化。

这是为什么呢?🤔

有些同学可能对MutableList不熟悉,kotlin将集合类型分为了只读类型与可变类型。

  • 只读类型:实现只读接口Collection<out E>,提供访问集合元素的操作,。
  • 可变类型:实现可变接口MutableCollection<E>,通过写操作扩展相应的只读接口:添加、删除和更新其元素。

而看MutableList的初始化方法mutableListOf,可以发现本质上其实是ArrayList

public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
    if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

所以,我们往下看看ArrayListaddAll方法。

ArrayList.java

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

先将要添加的结合转换为数组a,然后通过System.arraycopy()方法将数组a的元素拷贝到elementData数组中。

System.arraycopy()是Java中的一个方法,用于数组之间的元素拷贝。但是该方法拷贝的是数组元素的内存引用地址,所以是一个浅拷贝方法。

这就涉及到了浅拷贝知识。

关于浅拷贝与深拷贝

  • 浅拷贝:创建一个新对象,指向被复制对象的内存引用地址。
  • 深拷贝:创建一个新对象,且创建一个新的内存地址,并将被复制对象的值复制到新对象中。

举个例子

我们为需要展示的商品创建一个bean文件,包含商品名称、价格以及来源这几个属性,其中价格与来源的城市是可变属性,其它都是不可变属性。

/**
 * 商品数据
 */
data class ItemData(
    val name: String,//不可变的基本数据类型属性
    var price: Int,//可变的基本数据类型属性
    val source: Source//不可变的引用类型属性
)

/**
 * 来源地址
 */
data class Source(
    var city: String//可变的基本数据类型属性
)

我们先通过ItemData来创建我们的第一个商品:苹果。

val originalUser = ItemData("苹果", 20, Source("烟台"))

接着,我们试着使用浅拷贝来创建另一个商品:香蕉。

val copiedUser = originalUser.copy(name = "香蕉")

打印这两个商品出来看看,分别是:

originalUser = ItemData(name=苹果, price=20, source=Source(city=烟台))
copiedUser = ItemData(name=香蕉, price=20, source=Source(city=烟台))

接着,我们修改一下香蕉的来源信息,将香蕉的来源城市修改为"海南"

println("********* 修改来源 *********")
copiedUser.source.city = "海南"

修改后,再次打印两个商品:

********* 修改来源 *********
originalUser = ItemData(name=苹果, price=20, source=Source(city=海南))
copiedUser = ItemData(name=香蕉, price=20, source=Source(city=海南))

可以看出,虽然我们只修改了香蕉的来源信息,但是,苹果的来源也随之更改了。

接着,我们再来修改一下香蕉的价格,将香蕉的价格修改为9999

println("********* 修改价格 *********")
copiedUser.price = 9999

思考一下🤔,这次,苹果的价格也会随着香蕉一起更改为9999吗?

上答案!再次打印两个商品:

********* 修改价格 *********
originalUser = ItemData(name=苹果, price=20, source=Source(city=海南))
copiedUser = ItemData(name=香蕉, price=9999, source=Source(city=海南))

通过Log可以看出,这次,苹果的价格并没有随之一起更改

这是为什么呢?🤔

我们先来看看 data class copy() 方法。

data class copy方法

首先 copy 方法是浅拷贝方法,而且是会对主构造函数中的所有属性进行浅拷贝。

但是属性又分基本数据类型与引用类型,这两者是有差异的。

  • 基本数据类型:新对象在浅拷贝时会直接复制被拷贝对象的基本数据类型值,但这两者之间是独立的值,也就是说,修改这两者之间的任何一个对象的基本数据类型属性都不会影响到另外一个对象。
  • 引用类型:新对象在浅拷贝时会复制被拷贝对象的引用。这两者对象持有同一内存引用地址,所以是关联的。也就是说,修改这两者之间的任何一个对象的属性,另外一个对象也会跟着一起发生更改。

所以在上方的例子中,price为可变的基本数据类型属性,拷贝对象的值是独立的,所以当我们更改 copiedUser.price = 9999 时,originalUser.price 并不会随之一起更改。而source为不可变的引用类型属性,浅拷贝对象与被拷贝对象是关联的,所以当我们更改 copiedUser.source.city = "海南" 时,originalUser.source 会随之一起更改。

关于originalUsercopiedUser 的内存地址关系图,如下所示👇

浅拷贝深拷贝案例内存地址关系图.png

解决浅拷贝问题

OK,回到初始List.addAll()方法,如果我们想解决其浅拷贝问题,我们可以这么做。

TestActivity.java

val oldItemDataList: MutableList<ItemData> = mutableListOf()
var isInit = false
viewModel.itemDataLd.observe(this) { newItemDataList ->
    Log.e(TAG, "onCreate: itemDataLd.observe oldItemDataList = $oldItemDataList")
    Log.e(TAG, "onCreate: itemDataLd.observe newItemDataList = $newItemDataList")

    if (!isInit) {
        oldItemDataList.clear()
        //使用循环遍历复制
        newItemDataList.forEach { itemData ->
            //使用copy()方法拷贝一个新对象
            val addResult = oldItemDataList.add(itemData.copy())
            Log.e(TAG, "onCreate: add ${itemData.name} ${itemData.price} result is -> $addResult")
        }
        isInit = true
    }
}

循环遍历新数据,并使用copy()方法拷贝一个新对象出来,然后addoldItemDataList

打印一下Log看看:

09:40:49.830 25724-25724  onCreate: itemDataLd.observe oldItemDataList = []
09:40:49.830 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:49.830 25724-25724  onCreate: add 西瓜 3 result is -> true
09:40:49.831 25724-25724  onCreate: add 苹果 2 result is -> true

09:40:51.435 25724-25933  getItemDataList: random change index = 1
09:40:51.448 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:51.448 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=100)]
09:40:52.445 25724-25935  getItemDataList: random change index = 1
09:40:52.447 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:52.447 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=54)]
09:40:53.450 25724-25935  getItemDataList: random change index = 0
09:40:53.454 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:53.454 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=51), ItemData(name=苹果, price=54)]
09:40:54.453 25724-25935  getItemDataList: random change index = 1
09:40:54.455 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:54.455 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=51), ItemData(name=苹果, price=91)]
09:40:55.458 25724-25935  getItemDataList: random change index = 0
09:40:55.460 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:55.461 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=74), ItemData(name=苹果, price=91)]
09:40:56.462 25724-25935  getItemDataList: random change index = 0
09:40:56.463 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:56.463 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=11), ItemData(name=苹果, price=91)]
09:40:57.467 25724-25934  getItemDataList: random change index = 0
09:40:57.470 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:57.471 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=54), ItemData(name=苹果, price=91)]
09:40:58.472 25724-25934  getItemDataList: random change index = 1
09:40:58.473 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:58.474 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=54), ItemData(name=苹果, price=96)]
09:40:59.475 25724-25934  getItemDataList: random change index = 0
09:40:59.477 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:40:59.478 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=50), ItemData(name=苹果, price=96)]
09:41:00.480 25724-25934  getItemDataList: random change index = 1
09:41:00.483 25724-25724  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]
09:41:00.484 25724-25724  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=50), ItemData(name=苹果, price=73)]

通过Log可以看出,oldItemDataList只会被初始化一次,并且不会跟随着新数据一起更新内容了,始终维持着初始数据[ItemData(name=西瓜, price=3), ItemData(name=苹果, price=2)]

不知你内心有没有疑惑?copy()方法不是浅拷贝方法吗?拷贝出来的新对象与旧对象不是持有同一内存引用地址么,怎么旧对象内容数据不会随着新对象一起更改呢?

因为在ItemData属性中,虽然price是可变属性,但也是可变的基本数据类型属性,拷贝对象拥有独立的值。

data class ItemData(
    val name: String,
    var price: Int//可变的基本数据类型属性
)

我们可以进一步测试一下,给ItemData添加上source来源,定义为不可变引用类型属性。

data class ItemData(
    val name: String,
    var price: Int,//可变的基本数据类型属性
    val source: Source//不可变引用类型属性
)

data class Source(
    var city: String
)

然后为我们的初始化数据添加上source属性。

private val itemDataList: MutableList<ItemData> = mutableListOf(
    ItemData("西瓜", 3, Source("宁夏")),
    ItemData("苹果", 2, Source("烟台"))
)

接着,我们修改一下getItemDataList()方法,随机修改价格与来源数据。

fun getItemDataList() {
    viewModelScope.launch {
        withContext(Dispatchers.IO) {
            repeat(10) {
                (0..1).random().let { index ->
                    Log.e(TAG, "getItemDataList: random change index = $it")
                    (1..100).random().let { randomPrice ->
                        itemDataList[index].apply {
                            //随机修改价格
                            price = (1..100).random()
                            //随机修改来源数据
                            source.city = "${source.city} + $randomPrice"
                        }
                    }
                }
                _itemDataListMld.postValue(itemDataList)
                delay(1000)
            }
        }
    }
}

打印Log查看一下:

11:08:42.304  8184-8184  onCreate: itemDataLd.observe oldItemDataList = []
11:08:42.304  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
11:08:42.304  8184-8184  onCreate: add 西瓜 3 result is -> true
11:08:42.304  8184-8184  onCreate: add 苹果 2 result is -> true

11:08:54.210  8184-8440  getItemDataList: random change index = 1
11:08:54.215  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85))]
11:08:54.215  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=32, source=Source(city=烟台 + 85))]
11:08:55.220  8184-8440  getItemDataList: random change index = 0
11:08:55.223  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85))]
11:08:55.223  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=33, source=Source(city=宁夏 + 29)), ItemData(name=苹果, price=32, source=Source(city=烟台 + 85))]
11:08:56.224  8184-8440  getItemDataList: random change index = 0
11:08:56.226  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85))]
11:08:56.227  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=26, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=32, source=Source(city=烟台 + 85))]
11:08:57.227  8184-8440  getItemDataList: random change index = 1
11:08:57.228  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19))]
11:08:57.228  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=26, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=60, source=Source(city=烟台 + 85 + 19))]
11:08:58.233  8184-8440  getItemDataList: random change index = 1
11:08:58.235  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78))]
11:08:58.236  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=26, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=75, source=Source(city=烟台 + 85 + 19 + 78))]
11:08:59.237  8184-8443  getItemDataList: random change index = 1
11:08:59.239  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78 + 52))]
11:08:59.240  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=26, source=Source(city=宁夏 + 29 + 19)), ItemData(name=苹果, price=42, source=Source(city=烟台 + 85 + 19 + 78 + 52))]
11:09:00.242  8184-8443  getItemDataList: random change index = 0
11:09:00.245  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19 + 14)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78 + 52))]
11:09:00.245  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=24, source=Source(city=宁夏 + 29 + 19 + 14)), ItemData(name=苹果, price=42, source=Source(city=烟台 + 85 + 19 + 78 + 52))]
11:09:01.247  8184-8443  getItemDataList: random change index = 1
11:09:01.249  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19 + 14)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14))]
11:09:01.250  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=24, source=Source(city=宁夏 + 29 + 19 + 14)), ItemData(name=苹果, price=52, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14))]
11:09:02.252  8184-8443  getItemDataList: random change index = 0
11:09:02.255  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19 + 14 + 9)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14))]
11:09:02.255  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=40, source=Source(city=宁夏 + 29 + 19 + 14 + 9)), ItemData(name=苹果, price=52, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14))]
11:09:03.255  8184-8443  getItemDataList: random change index = 1
11:09:03.257  8184-8184  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏 + 29 + 19 + 14 + 9)), ItemData(name=苹果, price=2, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14 + 52))]
11:09:03.257  8184-8184  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=40, source=Source(city=宁夏 + 29 + 19 + 14 + 9)), ItemData(name=苹果, price=44, source=Source(city=烟台 + 85 + 19 + 78 + 52 + 14 + 52))]

通过Log可以看出,oldItemDataList 中的ItemData.source是会跟随新数据一起更新。也进一步证实了使用浅拷贝的引用类型属性,拷贝对象与被拷贝对象之间持有同一内存引用地址。

如果,我们想对引用类型属性进行深拷贝操作,也就是对案例中的source属性进行深拷贝,我们可以这么操作。

var oldItemDataList: List<ItemData> = listOf()
var isInit = false

viewModel.itemDataLd.observe(this) { newItemDataList ->
    Log.e(TAG, "onCreate: itemDataLd.observe oldItemDataList = $oldItemDataList")
    Log.e(TAG, "onCreate: itemDataLd.observe newItemDataList = $newItemDataList")

    if (!isInit) {
        //手动进行设置Source属性
        oldItemDataList = newItemDataList.map { it.copy(source = Source(it.source.city)) }
        isInit = true
    }
}

打印Log看看:

20:17:33.655  9198-9198  onCreate: itemDataLd.observe oldItemDataList = []
20:17:33.655  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]

20:17:36.706  9198-9945  getItemDataList: random change index = 1
20:17:36.712  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:36.712  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=45, source=Source(city=烟台 + 100))]
20:17:37.714  9198-9945  getItemDataList: random change index = 1
20:17:37.715  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:37.715  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=34, source=Source(city=烟台 + 100 + 68))]
20:17:38.717  9198-9945  getItemDataList: random change index = 1
20:17:38.719  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:38.719  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=10, source=Source(city=烟台 + 100 + 68 + 10))]
20:17:39.719  9198-9945  getItemDataList: random change index = 1
20:17:39.719  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:39.719  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=6, source=Source(city=烟台 + 100 + 68 + 10 + 21))]
20:17:40.721  9198-9945  getItemDataList: random change index = 1
20:17:40.722  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:40.722  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=99, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9))]
20:17:41.730  9198-9947  getItemDataList: random change index = 1
20:17:41.732  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:41.733  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=8, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9 + 89))]
20:17:42.735  9198-9945  getItemDataList: random change index = 1
20:17:42.736  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:42.737  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=61, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9 + 89 + 85))]
20:17:43.740  9198-9945  getItemDataList: random change index = 1
20:17:43.744  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:43.744  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=19, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9 + 89 + 85 + 40))]
20:17:44.746  9198-9945  getItemDataList: random change index = 1
20:17:44.749  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:44.749  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=19, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9 + 89 + 85 + 40 + 51))]
20:17:45.752  9198-9945  getItemDataList: random change index = 0
20:17:45.755  9198-9198  onCreate: itemDataLd.observe oldItemDataList = [ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))]
20:17:45.755  9198-9198  onCreate: itemDataLd.observe newItemDataList = [ItemData(name=西瓜, price=87, source=Source(city=宁夏 + 40)), ItemData(name=苹果, price=19, source=Source(city=烟台 + 100 + 68 + 10 + 21 + 9 + 89 + 85 + 40 + 51))]

通过Log可以看出,oldItemDataList的数据不会随着新数据一起更新,始终保持着初始化数据[ItemData(name=西瓜, price=3, source=Source(city=宁夏)), ItemData(name=苹果, price=2, source=Source(city=烟台))],说明完成了对引用类型属性的深拷贝操作。

当拷贝的对象是可变的引用类型属性时,进行浅拷贝无法保证数据一致性。因为浅拷贝仅仅是拷贝对象的内存引用地址,多个对象引用同一个内存引用地址,这在多线程的环境下,就无法保证数据一致性了。所以针对可变的引用类型属性,就应该对其进行深拷贝,让复制的对象拥有独立的内存地址,保证其唯一性,从而避免出现一些奇怪的Bug。

总结

其实知道了原理之后,事情就变得简单了。当我们不想oldItemDataList中的元素数据跟随newItemDataList元素数据一起更改时,我们只需要保证两者的元素数据不是持有同一内存引用地址即可。所以,撇开深浅拷贝方法,化繁为简,我们可以直接实例化一个新对象出来,从而保证其内存引用地址的唯一性。

本文是由MutableList.addAll()延伸出来的学习文章,涉及到浅拷贝深拷贝以及data class的一些相关知识点,也是属于夯实基础的一篇文章,相信掌握了这些小知识点后,对日后分析问题会有很大的帮助。



到此本篇文章就结束啦,如果你有任何疑问或者不同的想法,欢迎在评论区留言与我一起探讨。

其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。

另外,如果你觉得文章不错,对你有所帮助,请帮我点个赞,就当鼓励,谢谢