首先,并非所有复制列表都是不好的。有时副本可以利用 CPU 缓存并且速度非常快,这取决于列表、大小和其他因素。
其次,要“就地”修改列表,您需要使用可变的列表类型。在您的示例中,您使用listOf,它返回List<T> 接口,并且是只读的。您需要直接引用可变列表的类(即ArrayList),或者使用辅助函数arrayListOf 或linkedListOf 创建MutableList<T> 引用是惯用的Kotlin。一旦你有了它,你可以使用 listIterator() 迭代列表,它有一个突变方法 set()。
// create a mutable list
val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
// iterate it using a mutable iterator and modify values
val iterate = someList.listIterator()
while (iterate.hasNext()) {
val oldValue = iterate.next()
if (oldValue <= 20) iterate.set(oldValue + 20)
}
这将在迭代发生时更改列表中的值,并且对所有列表类型都有效。为了使这更容易,请创建可以重复使用的有用的扩展函数(见下文)。
使用简单的扩展函数进行变异:
您可以为 Kotlin 编写扩展函数,为任何 MutableList 实现进行就地可变迭代。这些内联函数的执行速度与迭代器的任何自定义使用一样快,并且内联以提高性能。非常适合 Android 或任何地方。
这是一个mapInPlace 扩展函数(它保留了这些函数的典型命名,例如map 和mapTo):
inline fun <T> MutableList<T>.mapInPlace(mutator: (T)->T) {
val iterate = this.listIterator()
while (iterate.hasNext()) {
val oldValue = iterate.next()
val newValue = mutator(oldValue)
if (newValue !== oldValue) {
iterate.set(newValue)
}
}
}
示例调用此扩展函数的任何变体:
val someList = arrayListOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
someList.mapInPlace { if (it <= 20) it + 20 else it }
这并不适用于所有Collection<T>,因为大多数迭代器只有remove() 方法,而不是set()。
数组的扩展函数
您可以使用类似的方法处理泛型数组:
inline fun <T> Array<T>.mapInPlace(mutator: (T)->T) {
this.forEachIndexed { idx, value ->
mutator(value).let { newValue ->
if (newValue !== value) this[idx] = mutator(value)
}
}
}
对于每个原始数组,使用以下变体:
inline fun BooleanArray.mapInPlace(mutator: (Boolean)->Boolean) {
this.forEachIndexed { idx, value ->
mutator(value).let { newValue ->
if (newValue !== value) this[idx] = mutator(value)
}
}
}
关于仅使用引用相等的优化
上面的扩展函数优化了一点,如果它没有更改为不同的实例,则不设置值,检查使用=== 或!== 是Referential Equality。不值得检查equals() 或hashCode(),因为调用它们具有未知的成本,并且实际上引用相等捕获了任何更改值的意图。
扩展函数的单元测试
这里是显示功能工作的单元测试用例,以及与复制的 stdlib 函数map() 的小比较:
class MapInPlaceTests {
@Test fun testMutationIterationOfList() {
val unhappy = setOf("Sad", "Angry")
val startingList = listOf("Happy", "Sad", "Angry", "Love")
val expectedResults = listOf("Happy", "Love", "Love", "Love")
// modify existing list with custom extension function
val mutableList = startingList.toArrayList()
mutableList.mapInPlace { if (it in unhappy) "Love" else it }
assertEquals(expectedResults, mutableList)
}
@Test fun testMutationIterationOfArrays() {
val otherArray = arrayOf(true, false, false, false, true)
otherArray.mapInPlace { true }
assertEquals(arrayOf(true, true, true, true, true).toList(), otherArray.toList())
}
@Test fun testMutationIterationOfPrimitiveArrays() {
val primArray = booleanArrayOf(true, false, false, false, true)
primArray.mapInPlace { true }
assertEquals(booleanArrayOf(true, true, true, true, true).toList(), primArray.toList())
}
@Test fun testMutationIterationOfListWithPrimitives() {
val otherList = arrayListOf(true, false, false, false, true)
otherList.mapInPlace { true }
assertEquals(listOf(true, true, true, true, true), otherList)
}
}