我有一个清单:
val someList = listOf(1, 20, 10, 55, 30, 22, 11, 0, 99)
我想在修改一些值时迭代它.我知道我可以这样做,map
但是它会复制一份清单.
val copyOfList = someList.map { if (it <= 20) it + 20 else it }
如果没有副本我该怎么办?
注意: 这个问题是由作者故意编写和回答的(自答案问题),因此对于常见问题的Kotlin主题的惯用答案存在于SO中.还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的.
首先,并非所有复制列表都是错误的.有时副本可以利用CPU缓存并且速度非常快,这取决于列表,大小和其他因素.
其次,要"就地"修改列表,您需要使用一种可变的列表.在您使用的示例中,listOf
它返回List
接口,并且是只读的.您需要直接引用可变列表的类(即ArrayList
),或者是惯用的Kotlin来使用辅助函数arrayListOf
或linkedListOf
创建MutableList
引用.完成后,您可以使用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 funMutableList .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
,因为大多数迭代器只有一个remove()
方法,而不是set()
.
您可以使用类似的方法处理通用数组:
inline funArray .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) } } }
扩展函数上述通过不设置值,如果它没有改变到一个不同的实例,检查,使用优化一点 ===
或!==
是参考平等.它不值得检查,equals()
或者hashCode()
因为调用它们的成本未知,实际上参考平等会捕获任何改变值的意图.
以下是显示函数工作的单元测试用例,以及与map()
复制的stdlib函数的小比较:
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) } }