過濾(Filtering)是 Collection 操作裡很常用的動作,這個功能一如前面幾個章節裡的範例是以 Lambda 來實作,要注意的是,這個實作並不會更動原本的 Collection,所以在使用過濾的功能時,記得要將結果用一個新的變數來儲存。
要過濾 Collection 裡的資料,只要傳入一個有條件判斷的 Lambda,Collection 就會把所有元素一個個用 Lambda 做驗證,只要回傳 true 的就會留下,反之則被過濾掉。各 Collection 間稍為有點不同的是,List
和 Set
在 filter()
後都會回傳 List
,而 Map
則是回傳 Map
。
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 } // [three, four]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10} // {key11=11}
val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 } // {key1=1}
假如你需要過濾的 Lambda 是反向來符合語意的話,Collection 也有一個 filterNot{}
,可以使用,只要 Lambda 回傳 true 的就會濾掉,反之則被留下。
val numbers = listOf("one", "two", "three", "four")
val filteredNot = numbers.filterNot { it.length <= 3 } // [three, four]
假如在過濾的邏輯裡,index 對你來說是重要的,那可以用 filterIndexed{}
,Collection 會把 index 及 element 傳進 Lambda 裡,你可以用這些參數來做條件判斷。
val numbers = listOf("one", "two", "three", "four")
val filterIndexed = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5) } // [two, four]
假如當初這個 Collection 的彈性設計的很大,比方說是一個 List<Any>
,則這個 List 裡就有可能有各種 Type 的元素。假如我們想要過濾出指定 Type 的元素的話,可以用 filterIsInstance<T>()
。
val numbers = listOf(null, 1, "two", 3.0, "four")
val filterIsString = numbers.filterIsInstance<String>() // [two, four]
若你的 Collection 是 List<T?>
,也就是說元素有可能是 null,則可以用 filterNotNull()
快速地把所有 null 過濾掉,回傳的就是 List<T: Any>
,方便處理空安全。
val numbers = listOf(null, "one", "two", null)
val filterNotNull = numbers.filterNotNull() // [one, two]
Array 和 List 內的元素是可以重覆的,假如需要使用這兩種 Collection,但一時需要把重覆的元素過濾掉呢?這時可以用 distinct()
,會自動去除掉 Collection 裡重覆的元素成一個新的 List。
val array = arrayOf(1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9)
val uniqueArray = array.distinct() // [1, 2, 3, 4, 5, 6, 7, 8, 9]
val list = listOf("one", "one", "two", "one", "three")
val uniqueList = list.distinct() // [one, two, three]
假如過濾重覆的邏輯比較複雜想要自行定義的話,則可以用 distinctBy{}
,一樣透過傳入 Lambda 的方式來過濾元素即可。
data class SmallClass(val key: String, val num: Int)
val listOfSmallClass = listOf(
SmallClass("key1", 1),
SmallClass("key2", 3),
SmallClass("key3", 3),
SmallClass("key4", 4),
)
val distinctList = listOfSmallClass.distinctBy { it.num } // [SmallClass(key=key1, num=1), SmallClass(key=key2, num=3), SmallClass(key=key4, num=4)]
一種比較特別的情境,你想用 Lambda 過濾出符合條件與不符合條件的兩個清單,也就是說想要拿到一個包含兩個 List
的 Pair
,這時我們可以用 partition()
。其回傳值會是一個 Pair,一個清單是符合 Lambda 條件的所有元素,另一個清單是所有沒通過的元素,我們可以直接用兩個變數把 List 接起來。
val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }
// match 是 [three, four]
// rest 是 [one, two]
在這個章節裡,我們討論許多過濾 Collection 的方式,學會這些動作後,對於操作 Collection 會更上手!為了一覽這些 API 在不同 Collection 上的行為,以下用表格來整理本章所討論到的 method:
行為 | Array | List | Set | Map | |
---|---|---|---|---|---|
filter{} | 依 Lambda 過濾 | v | v | v | v |
filterNot{} | 依 Lambda 反向過濾 | v | v | v | v |
filterIndexed{} | Lambda 過濾含 index | v | v | v | x |
filterIsInstance() | 過濾出相同型別的元素 | v | v | v | x |
filterNotNull() | 過濾掉 null 的元素 | v | v | v | x |
distinct() | 過濾重覆的元素 | v | v | v | x |
distinctBy{} | 依 Lambda 過濾重覆 | v | v | v | x |
partition{} | 依 Lambda 回傳 Pair | v | v | v | x |