[Kotlin Tutorials 7] Kotlin集合

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í)起來很平滑.)

這里舉例說明一下比較常用的場景.

filtermap

過濾操作用于篩選符合條件的元素, 比如在很多書中查找某一個作者的書:

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>.

mapflatMap

mapflatMap都是對集合中每個元素進行轉(zhuǎn)換, 它們有什么區(qū)別呢?

這里還是通過例子來看, 除了前面的Book之外, 我們的數(shù)據(jù)類型增加了StudentOrder:

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é), 無法一一列舉, 感興趣的話可以看看官方文檔.

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容