标准库中的集合类

kotlin库中的集合关系图(注意Map并不属于Collection的子类)

Kotlin collection

Kotlin集合类中主要有三种类型

  • List:有序,元素可重复的集合类,默认实现是ArrayList.
  • Set: 无序,元素不重复的集合类,默认实现是LinkedHastSet,它保留了元素的插入顺序信息,因此可以使用first(),last()等方法
  • Map: 键值对,键是唯一的,值允许重复,默认实现是LinkedHashSet,同样保留了元素的插入顺序信息

集合的创建

从元素构建集合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 创建一个不可变的集合
val fruits = listOf("Apple", "Orange", "Pear", "Grape")
// 创建一个可变的集合
val mutableFruits = mutableListOf("Apple", "Orange", "Pear", "Grape")

// 创建一个不可变的Set集合
val fruitsSet = setOf("Apple", "Orange", "Pear", "Grape")
// 创建一个可变的Set集合
val mutableFruitsSet = mutableSetOf("Apple", "Orange", "Pear", "Grape")

// 创建一个不可变的Map集合
val fruitMap = mapOf("Apple" to 1, "Orange" to 2, "Pear" to 3, "Grape" to 4)
// 创建一个可变的Map集合
val mutableFruitsMap = mutableMapOf("Apple" to 1, "Orange" to 2, "Pear" to 3, "Grape" to 4)
// 使用to关键字的方式定义`Map`时一般适用与对性能要求不高的情况下,性能要求高时,可以使用apply()函数
val numbersMap = mutableMapOf<String, String>().apply { this["one"] = "1"; this["two"] = "2" }

创建空的集合

1
2
3
val emptyList = emptyList<String>()
val emptySet = emptySet<String>()
val emptyMap = emptyMap<String, String>()

使用构造函数创建集合

1
2
3
4
5
val doubled = List(3, { it * 2 })  // or MutableList if you want to change its content later
println(doubled) // [0, 2, 4]

val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)

复制已有的集合

集合的复制方法toList(), toMutableList, toSet()等方法,都会复制一个新的集合快照,

与原来的集合相互独立,修改时互补影响

1
2
3
4
5
6
7
8
val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toMutableList()
val readOnlyCopyList = sourceList.toList()
sourceList.add(4)
println("Copy size: ${copyList.size}")    // 3

//readOnlyCopyList.add(4)             // compilation error
println("Read-only copy size: ${readOnlyCopyList.size}") // 3

也可以使用两个变量引用同一个集合,修改之间可以相互影响

1
2
3
4
val sourceList = mutableListOf(1, 2, 3)
val referenceList = sourceList
referenceList.add(4)
println("Source size: ${sourceList.size}") // 4

另外,可以通过声明类型来限制引用集合的操作,比如声明一个List类型的变量来指向一个MutableList类型的变量,当通过List类型的变量修改集合时,同样会报错。

1
2
3
4
5
6
val sourceList = mutableListOf(1, 2, 3)
// 与上面的例子基本相同,只是这里限定了referenceList为List类型
val referenceList: List<Int> = sourceList
//referenceList.add(4)            //compilation error
sourceList.add(4)
println(referenceList) // shows the current state of sourceList

通过其它集合的函数来创建集合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 使用filter函数创建list
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3)

// 通过map函数创建list
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })

//通过Association创建map
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })

集合遍历

使用iterator

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

使用for

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

使用forEach

1
2
3
4
val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
    println(it)
}

ListIterator

对于List类,还有一个特殊的迭代器ListIterator,支持向前或向后遍历

  • hasPrevious(),previous(),previousIndex()
  • hasNext(),next(),nextIndex()
1
2
3
4
5
6
7
8
val numbers = listOf("one", "two", "three", "four")
val listIterator = numbers.listIterator()
while (listIterator.hasNext()) listIterator.next()
println("Iterating backwards:")
while (listIterator.hasPrevious()) {
    print("Index: ${listIterator.previousIndex()}")
    println(", value: ${listIterator.previous()}")
}

MutableIteratorMutableListIterator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 支持遍历的同时添加删除(remove)删除
val numbers = mutableListOf("one", "two", "three", "four")
val mutableIterator = numbers.iterator()

mutableIterator.next()
mutableIterator.remove()
println("After removal: $numbers")

// 支持遍历的同时添加(add)或修改(set)元素
val numbers = mutableListOf("one", "four", "four")
val mutableListIterator = numbers.listIterator()
mutableListIterator.next()
mutableListIterator.add("two")
mutableListIterator.next()
mutableListIterator.set("three")
println(numbers)

Sequences

除了集合Collection之外,Kotlin中还有Sequences类,它与Iterator类提供了相同的方法。

创建Sequences

使用sequencesOf()方法

1
val numbersSequence = sequenceOf("four", "three", "two", "one")

使用assequeneces()函数,把Iterable对象转换成Sequences对象

1
2
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()

使用genrateSequence()函数

1
2
3
4
5
// 以2作为初始元素,然后每个元素加2生成下一个元素
val oddNumbers = generateSequence(2) { it + 2 } // `it` is the previous element
// 取前5个元素
println(oddNumbers.take(5).toList()) // [2, 4, 6, 8, 10]
//    println(oddNumbers.count())   // error: the sequence is infinite

使用genrateSequence(),会得到一个无穷的序列,为了得到一个有限的序列,可以在方法里返回null

1
2
val oddNumbersLessThan10 = generateSequence(1) { if (it < 8) it + 2 else null }
println(oddNumbersLessThan10.count()) // 5

使用sequence()函数

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

sequenceIterable的区别

主要的区别在于多步运算的操作流程(具体区别可以看后面的示例)

  • Iterator在处理多个步骤时,是每步操作都对所有的元素执行完成后,再执行下一个步骤
  • Sequences则是在对一个元素执行完所有的步骤之后,再处理下一元素

Iterator的例子

1
2
3
4
5
6
7
8
val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)

输出为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
filter: The
filter: quick
filter: brown
filter: fox
filter: jumps
filter: over
filter: the
filter: lazy
filter: dog
length: 5
length: 5
length: 5
length: 4
length: 4
Lengths of first 4 words longer than 3 chars:
[5, 5, 5, 4]

Sequence的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
val words = "The quick brown fox jumps over the lazy dog".split(" ")
//convert the List to a Sequence
val wordsSequence = words.asSequence()

val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars")
// terminal operation: obtaining the result as a List
println(lengthsSequence.toList())

输出为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Lengths of first 4 words longer than 3 chars
filter: The
filter: quick
length: 5
filter: brown
length: 5
filter: fox
filter: jumps
length: 5
filter: over
length: 4
[5, 5, 5, 4]

常见的集合操作

标准库里面,集成的操作主要定义在两部分,一是作为集合的成员函数,二是集合的扩展函数。

定义在成员函数里的集合操作,包含了集合必要的一些操作,如检查集合是否为空,获取元素等。

而定义在扩展函数里的集合操作,则包含了一些其它的集合操作,如集合里的元素过滤,排序等。

当实现我们自己的集合接口时,可以通过继承AbstractCollection, AbstractList, AbstractSet, AbstractMap来实现。

集合场景的操作有如下几类

**上面这些操作都只会基于原来的集合生成一个新的集合,所做的修改不会对原来的集合产生任何影响。**当然,也有一类操作是直接修改原始集合的操作,比如增加或删除元素等,这个后面再介绍

集合转换操作Transformations

Map

map函数基于原集合的每个元素使用传入的Lambda表达式进行操作后再生成一个新的集合

1
2
3
4
5
6
7
8
9
val numbers = setOf(1, 2, 3) // [3, 6, 9]
println(numbers.map { it * 3 }) // [0, 2, 6]
// 需要使用索引信息时,可以使用mapIndexed
println(numbers.mapIndexed { idx, value -> value * idx })

// 同时,使用mapNotNull或mapIndexedNotNull还可以过滤掉转换结果为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]

挡在对一个Map对象做操作时,可以通过mapKeysmapValues来指定转换key还是value

1
2
3
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
println(numbersMap.mapKeys { it.key.uppercase() }) // {KEY1=1, KEY2=2, KEY3=3, KEY11=11}
println(numbersMap.mapValues { it.value + it.key.length }) // {key1=5, key2=6, key3=7, key11=16}

Zip

zip函数用于合并两个集合操作到一个集合的Pair对,当两个集合的长度不一致时,结果以集合个数小的为准,多出的元素不会包含在结果中,另外zip函数还可以使用infix的方式来调用

1
2
3
4
5
6
7
8
9
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors.zip(animals)) // [(red, fox), (brown, bear), (grey, wolf)]
// 使用infix形式
println(colors zip animals)  // [(red, fox), (brown, bear), (grey, wolf)]

val twoAnimals = listOf("fox", "bear")
// colors中多余的元素直接被丢失
println(colors.zip(twoAnimals)) // [(red, fox), (brown, bear)

zip函数还可以接受两个参数

1
2
3
4
5
6
7
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")

println(colors.zip(animals) { color, animal -> "The ${animal.replaceFirstChar { it.uppercase() }} is $color"})

// 输出
// [The Fox is red, The Bear is brown, The Wolf is grey]

zip函数操作相反的,还有unzip函数

1
2
3
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])

Associate

associate函数用于把一个集合转换成map的形式.

这里主要有两种形式:

  1. 使用associateWith把原始集合的元素作为key, lambda表达式的返回值作为value返回。当key重复的时候,只会保留最后一个。
1
2
3
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
// {one=3, two=3, three=5, four=4}
  1. 使用associateBy把原始集合的元素作为value, lambda表达式的返回值作为key返回。当key重复的时候,只会保留最后一个。
1
2
3
4
5
6
val numbers = listOf("one", "two", "three", "four")

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

// 也可以传入两个参数同时修改key和value
println(numbers.associateBy(keySelector = { it.first().uppercaseChar() }, valueTransform = { it.length })) // {O=3, T=5, F=4}

也可以使用associate函数,在lambda中返回一个Pair类型

1
2
3
4
5
val charCodes = intArrayOf(72, 69, 76, 76, 79)
val byCharCode = charCodes.associate { it to Char(it) }

// 76=L only occurs once because only the last pair with the same key gets added
println(byCharCode) // {72=H, 69=E, 76=L, 79=O}

Flatten

flatten函数主要用于将一个嵌套的集合展开为一个集合

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

还有flatMap()函数,它类似map()flatten()函数的组合

1
2
3
4
5
6
val containers = listOf(
    StringContainer(listOf("one", "two", "three")),
    StringContainer(listOf("four", "five", "six")),
    StringContainer(listOf("seven", "eight"))
)
println(containers.flatMap { it.values })

集合的字符串表达形式

使用joinToString()joinTo()可以把集合转出成字符串形式。

两者的区别在于: joinToString()直接返回一个字符串,而joinTo()是把返回结果添加到给的参数后面

1
2
3
4
5
6
7
8
val numbers = listOf("one", "two", "three", "four")

println(numbers)         // [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

也可以指定元素直接的分隔符,前缀以及后缀

1
2
3
val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
// start: one | two | three | four: end

对于大的集合,还可以通过limit参数限定元素多个,剩下的元素用truncated参数来表示

1
2
3
val numbers = (1..100).toList()
println(numbers.joinToString(limit = 10, truncated = "<...>"))
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...>

另外,还可以指定transform参数来改变每个元素的表现形式

1
2
val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString { "Element: ${it.uppercase()}"})

集合过滤操作Filter

filter函数既可以用于List,Set。也可以用于Map

1
2
3
4
5
6
7
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()filter()条件刚好相反

1
2
3
4
5
6
7
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()根据类型过滤

1
2
3
4
5
6
7
8
val numbers = listOf(null, 1, "two", 3.0, "four")
println("All String elements in upper case:")
numbers.filterIsInstance<String>().forEach {
    println(it.uppercase())
}
// All String elements in upper case:
// TWO
// FOUR

filterNotNull()过滤掉不为空的元素

1
2
3
4
5
6
val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
    println(it.length)   // length is unavailable for nullable Strings
}
// 3
// 3

Partition()

根据条件,将集合分成两个列表

1
2
3
4
5
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,当集合为空时,无论什么条件都返回true

    1
    2
    3
    4
    5
    6
    7
    
    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()返回true, none()函数与any()函数行为相反。

    1
    2
    3
    4
    5
    6
    7
    8
    
    val numbers = listOf("one", "two", "three", "four")
    val empty = emptyList<String>()
    
    println(numbers.any()) // true
    println(empty.any()) // false
    
    println(numbers.none()) // false
    println(empty.none()) // true
    

集合的加减操作(plus()/minus())

集合同时支持加减操作,第一个操作数是一个集合,第二操作数可以是一个集合,也可以是单个元素。

需要注意的是减法操作,当减数是单个元素时,从被减数里去掉这个首次出现的这个元素,当被减数是集合时,从被减数删掉所有的这个元素

1
2
3
4
5
6
7
8
9
val numbers = listOf("one", "two", "three", "four", "four")
val plusList = numbers + "five"
val minusList = numbers - listOf("three", "four")
val minusSingle = numbers - "four"
val minusSingleList = numbers - listOf("four")
println(plusList) // [one, two, three, four, four, five]
println(minusList) // [one, two]
println(minusSingle) // [one, two, three, four],只是减去了第一个出现的"four"
println(minusSingleList) // [one, two, three], 减去了所有出现的"four"

集合分组Grouping

GroupBy()函数接受一个Lambda表达式作为参数,然后返回一个Map类型,使用Lambda表达式返回的值作为Key,分组结果的值作为Value。

GroupBy()函数也可以接受两个参数,通过keySelector指定key,通过valueTransform转换返回的值

1
2
3
4
val numbers = listOf("one", "two", "three", "four", "five")

println(numbers.groupBy { it.first().uppercase() }) // {O=[one], T=[two, three], F=[four, five]}
println(numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.uppercase() })) // {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}

如果想在分组的同时,对每个分组执行操作,可以使用groupingBy()函数,它返回一个Grouping类型, Grouping类型支持以下操作

  • eachCount()计算每个分组的元素个数

    1
    2
    
    val numbers = listOf("one", "two", "three", "four", "five", "six")
    println(numbers.groupingBy { it.first() }.eachCount()) // {o=1, t=2, f=2, s=1}
    
  • fold()reduce()

    fold()reduce()的区别在于fold()函数接受一个初始值作为开始条件,而reduce()`函数使用第一个元素作为开始条件

    1
    2
    3
    4
    5
    6
    7
    
      val numbers = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
     // 根据是模2的结果分组
      println(numbers.groupBy { it % 2 }) // {0=[0, 2, 4, 6, 8], 1=[1, 3, 5, 7, 9]}
     // 把每个分组的元素相加
      println(numbers.groupingBy { it % 2 }.reduce { _, sum, element -> sum + element }) // {0=20, 1=25}
     // 把每个分组的元素相加,初始值为10
      println(numbers.groupingBy { it % 2 }.fold(10) { sum, element -> sum + element }) // {0=30, 1=35}
    
  • aggregate()

    aggregate()函数于fold()reduce()函数的行为类型,不过功能上更强大

    Groups elements from the Grouping source by key and applies operation to the elements of each group sequentially, passing the previously accumulated value and the current element as arguments, and stores the results in a new map.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      val numbers = listOf(3, 4, 5, 6, 7, 8, 9)
      val aggregated = numbers.groupingBy { it % 3 }.aggregate { key, accumulator: StringBuilder?, element, first ->
        if (first) // first element
          StringBuilder().append(key).append(":").append(element)
        else
          accumulator!!.append("-").append(element)
      }
    
      println(aggregated.values) // [0:3-6-9, 1:4-7, 2:5-8]