Kotlin学习笔记(三) 集合(上)

集合

一、概述

  • 集合类型:

List 有序集合。可通过索引访问。

Set 唯一元素的集合。无重复对象。

Map(字典)是一组键值对。键是唯一的。

  • 接口类型

一个 只读 接口,提供访问集合元素的操作。

一个 可变 接口,通过写操作扩展相应的只读接口:添加、删除、更新。

更改集合不需要它以var定义:写操作修改同一个可变集合对象,因此引用不会改变。

Kotlin集合接口关系:

collections-diagram
  • List

索引:0 ~ list.size - 1

如果两个List在相同位置具有相同大小和相同结构的元素,则它们相等。

data class Person(var name: String, var age: Int)

fun main() {
    val bob = Person("Bob", 31)
    val people = listOf<Person>(Person("Adam", 20), bob, bob)
    val people2 = listOf<Person>(Person("Adam", 20), Person("Bob", 31), bob)
    println(people == people2)  // true
    bob.age = 32
    println(people == people2)  // false
}

ListArray的区别:

Array的大小在初始化时定义,无法改变;List没有预定义的大小,写操作的结果可以更改List的大小。

List 的默认实现是ArrayList

  • Set

存储唯一的元素。null元素也是唯一的。当两个 set 具有相同的大小并且对于一个 set 中的每个元素都能在另一个 set 中存在相同元素,则两个 set 相等。

Set的默认实现—LinkedHashSet—保留元素的插入顺序。可以调用first()last()函数返回可预测结果。

另一种实现—HashSet—不声明元素的顺序。

  • Map

Map<K, V>不是Collection接口的继承者。

键是唯一的,不同的键可以与相同的值配对。

无论键值对的顺序如何,包含相同键值对的两个 Map 是相等的。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)    
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)

numberMap == anotherMap  // true

MutableMap是一个具有写操作的 Map 接口。

二、构造集合

  • 由元素构造

listOf<T>()setOf<T>()mutableListOf<T>()mutableSetOf<T>()

如果以都好分隔的集合元素列表作为参数,编译器会自动检测元素类型。创建空集合时,必须指定类型。

val numbersList = listOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()

mapOf()mutableMapOf()

映射的键和值作为Pair对象传递(通常使用中缀函数to创建)。

val numbersMap = mapOf(Pair("key1", 1), "key2" to 2, "key3" to 3, "key4" to 1)

to 符号创建了一个短时存活的 Pair 对象,为避免过多的内存使用,可以创建可写Map并使用写入操作填充。

val numbersMap = mutableMapOf<String, Int>().apply { this["one"] = 1; this["two"] = 2 }
  • 空集合

emptyList()emptySet()emptyMap()

  • List的初始化函数

List有一个接受List大小与初始化函数的构造函数。

val doubled = List(3) { it * 2 }  // [0, 2, 4]
  • 具体类型构造函数
val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)
  • 复制
val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toMutableList()
val readOnlyList = sourceList.toList()

copyList.add(4)
// readOnlyList.add(4) // 编译异常
println(copyList)      // [1, 2, 3, 4]
println(readOnlyList)  // [1, 2, 3]
val sourceList = mutableListOf(1, 2, 3)    
val copySet = sourceList.toMutableSet()
copySet.add(3)
copySet.add(4)    
println(copySet)  // [1, 2, 3, 4]

可以创建对同一集合实例的新引用。使用现有集合初始化集合变量时,将创建新引用。 因此,当通过引用更改集合实例时,更改将反映在其所有引用中。

val sourceList = mutableListOf(1, 2, 3)
val referenceList = sourceList
referenceList.add(4)
println(sourceList.size)  // 4

集合的初始化可用于限制其可变性。

val sourceList = mutableListOf(1, 2, 3)
val referenceList: List<Int> = sourceList
//referenceList.add(4)  // 编译错误
sourceList.add(4)
println(referenceList)  // 显示 sourceList 当前状态
  • 调用其他集合的函数

过滤列表会创建与过滤器匹配的新元素列表:

val numbers = listOf(1, 2, 3, 4)
val odds = numbers.filter { it % 2 != 0 }
println(odds)  // [1, 3]

映射生成转换结果列表:

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })  // [3, 6, 9]
println(numbers.mapIndexed { idx, value -> value * idx })  // [0, 2, 6]

关联生成Map:

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
// {one=3, two=3, three=5, four=4}

三、迭代器

Iterable<T>接口的继承者(包括ListSet)通过调用iterator()函数获得迭代器。

val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
    println(numbersIterator.next())
}

一旦迭代器通过了最后一个元素,它就不能再用于检索元素;也无法重新指向到以前的任何位置。要再次遍历集合,需要创建一个新的迭代器。

遍历Iterable集合的另一种方法是for循环。在集合中使用for循环,将隐式获取迭代器。

val numbers = listOf("one", "two", "three", "four")
for (item in numbers) {
    println(item)
}

除了上述两种方式,还有forEach()函数,可以自动迭代集合并为每个元素执行给定的代码。

val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
    println(it)
}
  • List迭代器

ListIterable支持列表双向迭代。反向迭代由hasPrevious()previous()实现。

val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.listIterator()

// 先将iterator迭代至最后一个元素
while (numbersIterator.hasNext()) numbersIterator.next()

while (numbersIterator.hasPrevious()) {
    println(numbersIterator.previous())
}
  • 可变迭代器

MutableiteratorMutable集合的Iterator)使Iterator具有元素删除函数remove()

val numbers = mutableListOf("one", "two", "three", "four") 
val mutableIterator = numbers.iterator()

mutableIterator.next()
mutableIterator.remove()    
println(numbers)  // [two, three, four]

mutableListIteratorMutableListIterator)还可以在迭代时插入和替换元素。

val numbers = mutableListOf("one", "four", "four")
val mutableListIterator = numbers.listIterator()

mutableListIterator.next()
mutableListIterator.add("two")
mutableListIterator.next()
mutableListIterator.set("three")
println(numbers)  // [one, two, three, four]

四、区间与数列

整数类型区间有一个拓展特性:可以对其进行迭代。

for (i in 1..4) print(i)  // 1234

反向迭代。

for (i in 4 downTo 1) print(i)  // 4321

任意步长:通过step函数设置。

for (i in 1..8 step 2) print(i)  // 1357
for (i in 8 downTo 1 step 2) print(i)  // 8642

不包含区间:使用until函数。

for (i in 0 until 9 step 3) print(i)  // 036

数列实现Iterable<N>N分别是IntLongChar)可以在集合函数中使用。

println((1..10).filter { it % 2 == 0 }) // [2, 4, 6, 8, 10]

五、序列

Iterable 的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合,在此集合上执行以下步骤。序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。

  • 构造
  1. 由元素
val numbersSequence = sequenceOf("four", "three", "two", "one")
  1. 由Iterable
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
  1. 由函数
val oddNumbers = generateSequence(1) { it + 2 } // it是上一个元素
println(oddNumbers.take(5).toList())  // [1, 3, 5, 7, 9]
// println(oddNumbers.count())  // 错误:此序列是无限的。

要使用generateSequence()创建有限序列,需要提供一盒函数,该函数在需要的最后一个元素之后返回null

val oddNumbersLessThan10 = generateSequence(1) { if (it < 10) it + 2 else null }
println(oddNumbersLessThan10.toList())  // [1, 3, 5, 7, 9, 11]
println(oddNumbersLessThan10.count())  // 6
  • 由组块

sequence()函数可以逐个或按任意大小的组块生成序列元素。该函数采用一个lambda表达式,其中包含yield()yieldAll()函数的调用。它们将一个元素返回给序列使用者,并暂停sequecne()的执行,直到使用者请求下一个元素。yield()使用单个元素作为参数;yieldAll()中可以采用Iterable或其他SuquenceyieldAll()Sequence参数可以是无限的。

val oddNumbers = sequence {
    yield(1)
    yieldAll(listOf(3, 5))
    yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())  // [1, 3, 5, 7, 9]
  • 序列操作

如果序列操作返回延迟生成的另一个序列,则称为中间序列。 否则,该操作为末端操作。如toList()sum()为 末端操作。

六、集合操作概述

  • 公共操作

转换、过滤、加减操作符、分组、取集合的一部分、取单个元素、排序、聚合操作。

上述操作将返回结果而不影响原始集合。

val numbers = listOf("one", "two", "three", "four")  
numbers.filter { it.length > 3 }
println(numbers)  // [one, two, three, four]
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3)  // [three, four]

对于某些集合操作,有一个选项可以指定目标对象。 目标是一个可变集合,该函数将其结果项附加到该可变对象中,而不是在新对象中返回它们。对于带有目标的操作,有单独的函数,如filterTo()代替filter()associateTo()代替associate()

val numbers = listOf("one", "two", "three", "four")
val filterResults = mutableListOf<String>()  // 目标对象
numbers.filterTo(filterResults) { it.length > 3 }
numbers.filterIndexedTo(filterResults) { index, _ -> index == 0 }
println(filterResults)  // [three, four, one]
  • 写操作

对于某些操作,有成对的函数可以执行相同的操作:一个函数就地应用该操作,另一个函数将结果作为单独的集合返回。如sort()就地对可变集合进行排序;sorted()创建一个新集合。

val numbers = mutableListOf("one", "two", "three", "four")
val sortedNumbers = numbers.sorted()
println(numbers)  // [one, two, three, four]
println(sortedNumbers)  // [four, one, three, two]
numbers.sort()
println(numbers)  // [four, one, three, two]

七、转换

  • 映射

基本映射函数:map()

需要使用元素索引作为参数:mapIndexed()

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })  // [3, 6, 9]
println(numbers.mapIndexed { idx, value -> value * idx }) // [0, 2, 6]

如果转换在某些元素上产生null值,则用mapNotNull()取代map()mapIndexedNotNull()取代mapIndexed()来从结果中过滤到null值。

val numbers = setOf(1, 2, 3)
println(numbers.mapNotNull {
    if (it == 2) null else it * 3
})  // [3, 9]
println(numbers.mapIndexedNotNull {
    idx, value -> if (idx == 0) null else value * idx
})  // [2, 6]

转换键:mapKeys();转换值:mapValues()

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key11" to 11)

println(numbersMap.mapKeys { it.key.toUpperCase() })
// {KEY1=1, KEY2=2, KEY11=11}

println(numbersMap.mapValues { it.value + it.key.length })
// {key1=5, key2=6, key11=16}
  • 双路合并

双路合并转换是根据两个集合中具有相同位置的元素构建配对。使用zip()函数完成。在一个集合(或数组)上以另一个集合(或数组)作为参数调用时,zip() 返回 Pair 对象的列表(List)。如果集合的大小不同,则 zip() 的结果为较小集合的大小。

val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")

println(colors zip animals)
// [(red, fox), (brown, bear), (grey, wolf)]

val twoAnimals = listOf("fox", "bear")
println(colors.zip(twoAnimals))
// [(red, fox), (brown, bear)]

也可以使用带有两个参数的转换函数来调用 zip():接收者元素和参数元素。结果 List 包含在具有相同位置的接收者对和参数元素对上调用的转换函数的返回值。

println(colors.zip(animals) { color, animal -> "The ${animal.capitalize()} is $color"})
// [The Fox is red, The Bear is brown, The Wolf is grey]

可以使用unzip()对键值对进行分割,产生两个列表:第一个列表包含每个键,第二个列表包含每个值。

val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip())  // ([one, two, three, four], [1, 2, 3, 4])
  • 关联

使用associateWith()创建一个Map,原始集合的元素是键,通过给定的转换函数产生值。如果两个元素相等,仅第一个保留在Map中。

val numbers = listOf("one", "two", "three", "four", "two")
println(numbers.associateWith { it.length })  // {one=3, two=3, three=5, four=4}

如果需要使用集合元素作为值,可以使用associateBy()函数。如果两个元素想到,仅最后一个保留在Map中。

val numbers = listOf("one", "two", "three", "four")

println(numbers.associateBy { it.first().toUpperCase() })
// {O=one, T=three, F=four}

println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))
// {O=3, T=5, F=4}

另一种构建Map的方法是使用associate()函数,其中Map键和值都是通过集合元素生成的。

val numbers = listOf("one", "two", "three", "four")
println(numbers.associate { it.toUpperCase() to it.length })
// {ONE=3, TWO=3, THREE=5, FOUR=4}
  • 打平

用于操作嵌套的集合。

  1. flatten()

可以在一个集合的集合上调用。

val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(7, 8))
println(numberSets.flatten())  // [1, 2, 3, 4, 5, 6, 7, 8]
  1. flatMap()

需要一个函数将一个集合元素映射到另一个集合。flatMap()返回单个列表其中包含所有元素的值。flatMap()表现为map()flatten()的连续调用。

data class StringContainer(val values: List<String>)

fun main() {
    val containers = listOf(
        StringContainer(listOf("one", "two", "three")),
        StringContainer(listOf("four", "five", "six")),
        StringContainer(listOf("seven", "eight"))
    )
    println(containers.flatMap { it.values })
    // [one, two, three, four, five, six, seven, eight]
}
  • 字符串表示

joinToString()从集合元素构建单个StringjoinTo()执行相同的操作,但将结果附加到给定的Appendable对象。

val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString())  // one, two, three, four

val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
println(listString)  // The list of numbers: one, two, three, four

joinToString参数:

separator:分隔符

prefixpostfix:前后缀

limit:数量限制

truncated:省略部分,默认...

transform:自定义元素的表现形式

val numbers = listOf("one", "two", "three", "four")

println(numbers.joinToString(separator = ";",
        prefix = "start-- ",
        postfix = " --end",
        limit = 3,
        truncated = "<...>",
        transform = {
            it.toUpperCase()
        }))
// start-- ONE;TWO;THREE;<...> --end

val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
println(listString)
// The list of numbers: one, two, three, four

八、过滤

过滤操作即可用于可变集合也可用于只读集合。

  • 按谓词过滤

基本过滤函数是filter()ListSet的过滤结果都是ListMap的过滤结果还是Map

val numbers = listOf("one", "two", "three", "four")  
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3)  // [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}
println(filteredMap)  // {key11=11}

如果想在过滤中使用元素的索引,应该使用filterIndexed(),它接受带有两个参数的谓词:元素索引与元素值。

如果要使用否定条件来过滤,需要使用filterNot(),它返回一个让谓词产生false的元素列表。

val numbers = listOf("one", "two", "three", "four")

val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5)  }
val filteredNot = numbers.filterNot { it.length <= 3 }

println(filteredIdx)  // [two, four]
println(filteredNot)  // [three, four]

过滤给定类型的元素

filterIsInstance()返回给定类型的集合元素。

val numbers = listOf(null, 1, "two", 3.0, "four")
println(numbers.filterIsInstance<String>())  // [two, four]

filterNotNull返回所有的非空元素。

val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
    println(it.length)   // 对可空的 String 来说长度不可用
}
  • 划分

partition()将不匹配的元素存放在一个单独的列表,返回一个ListPair:第一个列表包含与谓词匹配的元素,第二个列表包含其他元素。

val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }

println(match)  // [three, four]
println(rest)  // [one, two]
  • 检验谓词

any():如果至少有一个元素匹配,返回true

none():如果没有元素匹配,返回true

all():如果所有元素都匹配,返回true

注:在一个空集合上使用任何有效的谓词去调用all()都会返回true,这种行为在逻辑上被称为vacuous truth

val numbers = listOf("one", "two", "three", "four")

println(numbers.any { it.endsWith("e") })  // true
println(numbers.none { it.endsWith("a") })  // true
println(numbers.all { it.endsWith("e") })  // false
println(emptyList<Int>().all { it > 5 })  // true

any()none()也可以不带谓词使用。如果集合中有元素,any()返回truenone()返回false;如果集合为空,any()返回falsenone返回true

val numbers = listOf("one", "two", "three", "four")
val empty = emptyList<String>()

println(numbers.any())  // true
println(numbers.none())  // false

println(empty.any())  // false
println(empty.none())  // true

九、加减操作符

plus:包含原始集合与第二个操作数中的元素。

minus:包含从原始集合中除去第二个操作数中的元素后剩下的元素。如果第二个操作数是一个元素,minus移除其在原始集合中的第一次出现;如果是一个集合,则移除所有出现。

val numbers = listOf("one", "two", "three", "four", "one")
val plusOne = numbers + "five"
val plusList = numbers + listOf("five", "six")
val minusOne = numbers - "one"
val minusList = numbers - listOf("one", "two", "five")
println(plusOne)  // [one, two, three, four, one, five]
println(plusList)  // [one, two, three, four, one, five, six]
println(minusOne)  // [two, three, four, one]
println(minusList)  // [three, four]

十、分组

groupBy()函数使用一个lambda函数并返回一个Map

val numbers = listOf("one", "two", "three", "four", "five")

println(numbers.groupBy { it.first().toUpperCase() })
// {O=[one], T=[two, three], F=[four, five]}

println(numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.toUpperCase() }))
// {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}

如果要对元素进行分组,然后一次将操作应用于所有分组,需要使用groupingBy()函数。

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