Kotlin集合
本文收錄于: https://github.com/mengdd/KotlinTutorials
集合創(chuàng)建
Kotlin標(biāo)準(zhǔn)庫提供了set, list和map這三種基本集合類型的實現(xiàn), 每種類型又都分為可變和不可變(只讀)兩種類型.
創(chuàng)建不同類型的集合:
// set
val numbersSet = setOf("one", "two", "three", "four")
val numbersSetMutable = mutableSetOf("one", "two", "three", "four")
// list
val numbersList = listOf("one", "two", "three", "four")
val numbersListMutable = mutableListOf("one", "two", "three", "four")
// map
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val numbersMapMutable = mutableMapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
其中不可變的類型不支持添加, 刪除和更改元素.
標(biāo)準(zhǔn)庫為每個集合都提供了empty的只讀單例:
val emptySet = emptySet<String>()
val emptyList = emptyList<String>()
val emptyMap = emptyMap<String, Int>()
可以用于在某種情況下返回空結(jié)果.
集合引用賦值和拷貝
可以通過賦值把mutable的變量付給只讀的引用, 從而禁止其修改行為. 比如傳遞函數(shù)參數(shù)等.
val sourceList = mutableListOf(1, 2, 3)
val referenceList: List<Int> = sourceList
//referenceList.add(4) //compilation error
sourceList.add(4)
println(sourceList)
println(referenceList) // shows the current state of sourceList
但是對源數(shù)據(jù)的修改仍然會反映到新的只讀引用上, 它們的數(shù)據(jù)保持同步.
toList, toSet會拷貝元素, 創(chuàng)建新的集合. 之后對源的修改不會作用于拷貝的集合上, 反之亦然.
val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toList()
sourceList.add(4)
println(sourceList)
println(copyList) // is different from current sourceList
還有toMutableList()和toMutableSet(), 除了可以進行可變/不可變的轉(zhuǎn)換以外, 還可以進行set和list之間的相互轉(zhuǎn)換.
遍歷集合
遍歷集合是比較常見的操作. 舉個例子, 現(xiàn)在有一個list, 里面的元素類型是Book, 我想遍歷這個集合, 輸出所有的書名.
data class Book(val name: String, val author: String)
做法很多, 這里列出比較常規(guī)的幾種寫法:
// way 1
for (book in books) {
println(book.name)
}
// way 2
books.forEach { println(it.name) }
// way 3
val iterator = books.iterator()
while (iterator.hasNext()) {
println(iterator.next().name)
}
// way 4
for (i in books.indices) {
println(books[i].name)
}
這里數(shù)據(jù)結(jié)構(gòu)是list, 所以可以用[]加索引的方式訪問.
最有用/常用的操作符
Kotlin提供了很多有用的集合操作符, 之前用Java的時候借助RxJava來做的一些集合元素過濾轉(zhuǎn)換排序等等, 現(xiàn)在Kotlin的標(biāo)準(zhǔn)庫就支持了. (用過RxJava的同學(xué)可以感覺到這里學(xué)習(xí)起來很平滑.)
這里舉例說明一下比較常用的場景.
filter和map
過濾操作用于篩選符合條件的元素, 比如在很多書中查找某一個作者的書:
fun getBooksOfAuthor(books: List<Book>, author: String): List<Book> {
return books.filter { it.author == author }
}
這里返回的仍然是書的列表, 如果我想返回書名的列表呢?
fun getBooksNamesOfAuthor(books: List<Book>, author: String): List<String> {
return books
.filter { it.author == author }
.map { it.name }
}
這里利用map做了一個轉(zhuǎn)換, Book變成了String, 最后返回的是List<String>.
map和flatMap
map和flatMap都是對集合中每個元素進行轉(zhuǎn)換, 它們有什么區(qū)別呢?
這里還是通過例子來看, 除了前面的Book之外, 我們的數(shù)據(jù)類型增加了Student和Order:
data class Student(val name: String, val orders: List<Order>)
data class Order(val books: List<Book>)
場景: 學(xué)生要買書, 在網(wǎng)站上下訂單, 每個人可以下多個訂單, 訂單里又可以有多本書.
求解:
- 問題1: 某一個學(xué)生都定了什么書?
- 問題2: 有多個學(xué)生, 要所有的學(xué)生定的書的集合?
針對第一個問題, 是這樣解決的:
fun getBooksOrderedByStudent(student: Student): Set<Book> {
return student.orders.flatMap { it.books }.toSet()
}
這里用的flatMap, 如果改成map呢?
fun getBooksListsOrderedByStudent(student: Student): Set<List<Book>> {
return student.orders.map { it.books }.toSet()
}
可以看到返回值變了, 不再是書的集合Set<Book>了, 而是Set<List<Book>>. 這不是我們想要的答案.
二者的區(qū)別點進源碼就可以發(fā)現(xiàn):
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
雖然都是進行轉(zhuǎn)換, 區(qū)別主要是transform的類型:
-
map是:transform: (T) -> R,map的轉(zhuǎn)換是一對一的, 元素的個數(shù)不會變. -
flatMap是:transform: (T) -> Iterable<R>, 說明flatMap的轉(zhuǎn)換是一對多的, 轉(zhuǎn)換后元素變成了集合, 有一個鋪平的過程, 轉(zhuǎn)換后生成的集合元素會被合并返回.
想明白了flatMap的用法, 解決問題2就比較容易了. 有多個學(xué)生, 那么我們需要統(tǒng)計每個學(xué)生訂單里的書:
fun getAllBooksOrderedByAllStudents(students: List<Student>): Set<Book> {
return students.flatMap { student ->
student.orders.flatMap { order ->
order.books
}
}.toSet()
}
排序和查找
實際的應(yīng)用里可能還有搜索排序等需求.
排序, 可以sortedBy()指定根據(jù)哪個字段, 也可以用sortedWith()寫一個自定義的Comparator.
這里代碼例子, 比如按書名/書名長度排序:
fun sortBooksByName(books: List<Book>): List<Book> {
return books.sortedBy { it.name }
}
fun sortBooksByNameLength(books: List<Book>): List<Book> {
return books.sortedBy { it.name.length }
}
fun sortBooksByComparator(books: List<Book>): List<Book> {
return books.sortedWith(
Comparator { book1, book2 ->
return@Comparator book1.name.length - book2.name.length
}
)
}
還有可能會有查找的需求, 查找是否存在元素, 返回第一個元素等等.
是否存在某個作者的書:
fun hasBookOfAuthor(books: List<Book>, author: String): Boolean {
return books.any { it.author == author }
}
返回的是布爾值.
查找某個作者的書, 返回第一個結(jié)果:
fun findOneBookOfAuthor(books: List<Book>, author: String): Book? {
return books.find { it.author == author }
}
注意這里返回的類型是Book?, 如果沒有符合條件的會返回null.
查找某個實例是否在集合中, 除了用contains()之外, 還可以用in:
if (bookBrownBear in books) {
println("we have book Brow Bear")
}
其他有用的操作符比如first()和firstOrNull, 還有統(tǒng)計個數(shù)的count().
其他有用/有意思的東東
學(xué)習(xí)一個語言的最好方法是邊學(xué)邊用, 在看別人的代碼的時候總是能發(fā)現(xiàn)一些新東西.
下面列出幾點:
- 可以在遍歷的時候刪除元素了. ->
MutableIterator. - 根據(jù)某一標(biāo)準(zhǔn)進行分組, 把list轉(zhuǎn)換成map. ->
groupBy(). 注意和associateBy()的區(qū)別:associateBy會把符合條件的最后一個值作為value, 而groupBy()的value是所有符合條件的元素的list. - 更加方便的獲取元素的方法:
elementAtOrElse(),elementAtOrNull(),getOrElse(),getOrDefault(). - 列表元素去重除了轉(zhuǎn)換成set以外還可以用
distinct().
集合這部分的知識點實在太多, 也很細(xì)節(jié), 無法一一列舉, 感興趣的話可以看看官方文檔.
參考
- 官網(wǎng): Kotlin Collections Overview
- Kotlin練習(xí): Kotlin Koans
- Learn Kotlin by Example: Learn Kotlin by Example