1. 概述
Kotlin 集合是功能强大的数据结构,相较于 Java 集合提供了更多实用的方法。本文将介绍 Kotlin 集合中常用的几种过滤操作方法,掌握这些方法后,读者可以轻松理解并使用未在本文中明确列出的其他相关方法。
📌 所有这些方法都不会修改原始集合,而是返回一个新的集合。
我们会用到 Kotlin 的 lambda 表达式来实现过滤逻辑,如果你对 lambda 不太熟悉,可以参考我们的 Kotlin Lambda 文章。
2. drop 操作
drop
是一种简单的方式来裁剪集合。它会从集合中移除指定数量的元素,并返回一个新集合:
@Test
fun whenDroppingFirstTwoItemsOfArray_thenTwoLess() {
val array = arrayOf(1, 2, 3, 4)
val result = array.drop(2)
val expected = listOf(3, 4)
assertIterableEquals(expected, result)
}
如果你想要移除最后几个元素,可以使用 dropLast
:
@Test
fun givenArray_whenDroppingLastElement_thenReturnListWithoutLastElement() {
val array = arrayOf("1", "2", "3", "4")
val result = array.dropLast(1)
val expected = listOf("1", "2", "3")
assertIterableEquals(expected, result)
}
还可以使用 dropLastWhile
,它会从尾部开始遍历,直到遇到不符合条件的元素为止:
@Test
fun whenDroppingLastUntilPredicateIsFalse_thenReturnSubsetListOfFloats() {
val array = arrayOf(1f, 1f, 1f, 1f, 1f, 2f, 1f, 1f, 1f)
val result = array.dropLastWhile { it == 1f }
val expected = listOf(1f, 1f, 1f, 1f, 1f, 2f)
assertIterableEquals(expected, result)
}
✅ dropLastWhile
会从末尾开始删除元素,直到遇到第一个不满足条件的元素为止。
📌 dropWhile
与 dropLastWhile
类似,但方向相反,它从索引 0 开始删除。
⚠️ 如果你尝试删除的元素数大于集合本身长度,结果将是一个空列表。
3. take 操作
take
与 drop
类似,但它保留指定数量的元素:
@Test
fun `when predicating on 'is String', then produce list of array up until predicate is false`() {
val originalArray = arrayOf("val1", 2, "val3", 4, "val5", 6)
val actualList = originalArray.takeWhile { it is String }
val expectedList = listOf("val1")
assertIterableEquals(expectedList, actualList)
}
📌 take
保留符合条件的元素;drop
删除符合条件的元素。
⚠️ takeIf
并不是集合操作方法,它是对单个对象的操作,用于判断是否返回对象本身或 null,类似于 Java 的 Optional#filter
。
要根据条件筛选出所有匹配的元素,应该使用 filter
方法。
4. filter 操作
filter
根据给定的谓词条件创建一个新列表:
@Test
fun givenAscendingValueMap_whenFilteringOnValue_ThenReturnSubsetOfMap() {
val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 }
val expectedMap = mapOf("key1" to 1)
assertTrue { expectedMap == filteredMap }
}
如果你想将多个集合的过滤结果合并到一个可变集合中,可以使用 filterTo
:
@Test
fun whenFilteringToAccumulativeList_thenListContainsAllContents() {
val array1 = arrayOf(90, 92, 93, 94, 92, 95, 93)
val array2 = sequenceOf(51, 31, 83, 674_506_111, 256_203_161, 15_485_863)
val list1 = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
val primes = mutableListOf<Int>()
val expected = listOf(2, 3, 5, 7, 31, 83, 15_485_863, 256_203_161, 674_506_111)
val primeCheck = { num: Int -> Primes.isPrime(num) }
array1.filterTo(primes, primeCheck)
list1.filterTo(primes, primeCheck)
array2.filterTo(primes, primeCheck)
primes.sort()
assertIterableEquals(expected, primes)
}
📌 filterNotNull
和 filterNotNullTo
可以方便地移除所有 null 元素。
📌 如果你需要使用索引进行过滤,可以使用 filterIndexed
或 filterIndexedTo
。
5. slice 操作
使用 slice
可以通过范围提取集合的子集:
@Test
fun whenSlicingAnArrayWithDotRange_ThenListEqualsTheSlice() {
val original = arrayOf(1, 2, 3, 2, 1)
val actual = original.slice(3 downTo 1)
val expected = listOf(2, 3, 2)
assertIterableEquals(expected, actual)
}
📌 范围可以是升序也可以是降序。
📌 使用 range
时还可以指定步长(step)。
⚠️ 如果你使用带步长的范围超出集合边界,会抛出 ArrayIndexOutOfBoundsException
:
@Test
fun whenSlicingBeyondRangeOfArrayWithStep_thenOutOfBoundsException() {
assertThrows(ArrayIndexOutOfBoundsException::class.java) {
val original = arrayOf(12, 3, 34, 4)
original.slice(3..8 step 2)
}
}
6. distinct 操作
distinct
可用于获取集合中唯一的元素:
@Test
fun whenApplyingDistinct_thenReturnListOfNoDuplicateValues() {
val array = arrayOf(1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9)
val result = array.distinct()
val expected = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
assertIterableEquals(expected, result)
}
还可以使用 distinctBy
,它接受一个选择器函数来决定唯一性的判断依据:
data class SmallClass(val key: String, val num: Int)
val original = arrayOf(
SmallClass("key1", 1),
SmallClass("key2", 2),
SmallClass("key3", 3),
SmallClass("key4", 3),
SmallClass("er", 9),
SmallClass("er", 10),
SmallClass("er", 11))
val actual = original.distinctBy { it.key }
val expected = listOf(
SmallClass("key1", 1),
SmallClass("key2", 2),
SmallClass("key3", 3),
SmallClass("key4", 3),
SmallClass("er", 9))
📌 选择器函数不一定要直接返回对象属性,也可以是计算值,例如根据数值范围进行去重:
val actual = array.distinctBy { Math.floor(it.num / 10.0) }
7. chunked 操作
Kotlin 1.2 引入了 chunked
方法,用于将一个 Iterable
切分成多个子块:
@Test
fun givenDNAFragmentString_whenChunking_thenProduceListOfChunks() {
val dnaFragment = "ATTCGCGGCCGCCAA"
val fragments = dnaFragment.chunked(3)
assertIterableEquals(listOf("ATT", "CGC", "GGC", "CGC", "CAA"), fragments)
}
还可以在切分的同时进行转换:
@Test
fun givenDNAString_whenChunkingWithTransformer_thenProduceTransformedList() {
val codonTable = mapOf(
"ATT" to "Isoleucine",
"CAA" to "Glutamine",
"CGC" to "Arginine",
"GGC" to "Glycine")
val dnaFragment = "ATTCGCGGCCGCCAA"
val proteins = dnaFragment.chunked(3) { codon ->
codonTable[codon.toString()] ?: error("Unknown codon")
}
assertIterableEquals(listOf(
"Isoleucine", "Arginine",
"Glycine", "Arginine", "Glutamine"), proteins)
}
📌 如果集合长度不能被 chunk size 整除,最后一个 chunk 会比其他 chunk 小。
⚠️ 不要假设每个 chunk 都是满的,否则可能遇到 ArrayIndexOutOfBoundsException
。
更多关于
chunked
的示例请参考 Kotlin 官方文档:chunked
8. 总结
Kotlin 集合提供了丰富的过滤方法,它们都支持使用 lambda 表达式进行条件筛选。虽然不是所有方法都能用于 Map
,但适用于 Map
的方法也都能用于 Array
。
📌 建议查阅 Kotlin 官方文档 了解每个方法支持的数据结构。
📌 所有示例代码都可以在 GitHub 仓库 中找到。