[Kotlin Tutorials 7] Kotlin集合

Kotlin集合

本文收录于: https://github.com/mengdd/KotlinTutorials

集合创建

Kotlin标准库提供了set, list和map这三种基本集合类型的实现, 每种类型又都分为可变和不可变(只读)两种类型.

创建不同类型的集合:

// 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)

其中不可变的类型不支持添加, 删除和更改元素.

标准库为每个集合都提供了empty的只读单例:

val emptySet = emptySet<String>()
val emptyList = emptyList<String>()
val emptyMap = emptyMap<String, Int>()

可以用于在某种情况下返回空结果.

集合引用赋值和拷贝

可以通过赋值把mutable的变量付给只读的引用, 从而禁止其修改行为. 比如传递函数参数等.

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

但是对源数据的修改仍然会反映到新的只读引用上, 它们的数据保持同步.

toList, toSet会拷贝元素, 创建新的集合. 之后对源的修改不会作用于拷贝的集合上, 反之亦然.

val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toList()
sourceList.add(4)
println(sourceList)
println(copyList) // is different from current sourceList

还有toMutableList()toMutableSet(), 除了可以进行可变/不可变的转换以外, 还可以进行set和list之间的相互转换.

遍历集合

遍历集合是比较常见的操作. 举个例子, 现在有一个list, 里面的元素类型是Book, 我想遍历这个集合, 输出所有的书名.

data class Book(val name: String, val author: String)

做法很多, 这里列出比较常规的几种写法:

// 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)
}

这里数据结构是list, 所以可以用[]加索引的方式访问.

最有用/常用的操作符

Kotlin提供了很多有用的集合操作符, 之前用Java的时候借助RxJava来做的一些集合元素过滤转换排序等等, 现在Kotlin的标准库就支持了. (用过RxJava的同学可以感觉到这里学习起来很平滑.)

这里举例说明一下比较常用的场景.

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做了一个转换, Book变成了String, 最后返回的是List<String>.

mapflatMap

mapflatMap都是对集合中每个元素进行转换, 它们有什么区别呢?

这里还是通过例子来看, 除了前面的Book之外, 我们的数据类型增加了StudentOrder:

data class Student(val name: String, val orders: List<Order>)

data class Order(val books: List<Book>)

场景: 学生要买书, 在网站上下订单, 每个人可以下多个订单, 订单里又可以有多本书.

求解:

  • 问题1: 某一个学生都定了什么书?
  • 问题2: 有多个学生, 要所有的学生定的书的集合?

针对第一个问题, 是这样解决的:

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>>. 这不是我们想要的答案.

二者的区别点进源码就可以发现:

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
}

虽然都是进行转换, 区别主要是transform的类型:

  • map是: transform: (T) -> R, map的转换是一对一的, 元素的个数不会变.
  • flatMap是: transform: (T) -> Iterable<R>, 说明flatMap的转换是一对多的, 转换后元素变成了集合, 有一个铺平的过程, 转换后生成的集合元素会被合并返回.

想明白了flatMap的用法, 解决问题2就比较容易了. 有多个学生, 那么我们需要统计每个学生订单里的书:

fun getAllBooksOrderedByAllStudents(students: List<Student>): Set<Book> {
    return students.flatMap { student ->
        student.orders.flatMap { order ->
            order.books
        }
    }.toSet()
}

排序和查找

实际的应用里可能还有搜索排序等需求.

排序, 可以sortedBy()指定根据哪个字段, 也可以用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 }
}

返回的是布尔值.

查找某个作者的书, 返回第一个结果:

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, 还有统计个数的count().

其他有用/有意思的东东

学习一个语言的最好方法是边学边用, 在看别人的代码的时候总是能发现一些新东西.

下面列出几点:

  • 可以在遍历的时候删除元素了. -> MutableIterator.
  • 根据某一标准进行分组, 把list转换成map. -> groupBy(). 注意和associateBy()的区别: associateBy会把符合条件的最后一个值作为value, 而groupBy()的value是所有符合条件的元素的list.
  • 更加方便的获取元素的方法: elementAtOrElse(), elementAtOrNull(), getOrElse(), getOrDefault().
  • 列表元素去重除了转换成set以外还可以用distinct().

集合这部分的知识点实在太多, 也很细节, 无法一一列举, 感兴趣的话可以看看官方文档.

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容