转载

Kotlin知识归纳(七) —— 集合

Kotlin没有自己的集合库,完全依赖Java标准库中的集合类,并通过扩展函数增加特性来增强集合。意味着Kotlin与Java交互式,永远不需要包装或者转换这些集合对象,大大增强与Java的互操作性。

只读集合和可变集合

Kotlin与Java最大的不同之一就是:Kotlin将集合分为只读集合和可变集合。这种区别源自最基础的集合接口:kotlin.collections.Collection。该接口可以对集合进行一些基本操作,但无任何添加和移除元素的方法。

只有实现 kotlin.collections.MutableCollection 接口才可以修改集合的数据。MutableCollection 接口继承自 Collection,并提供添加、移除和清空集合元素的方法。当一个函数接收 Collection,而不是 MutableCollection,即意味着函数不对集合做修改操作。

Kotlin知识归纳(七) —— 集合

可变集合一般都带有 “Mutable” 前缀修饰,意味着能对集合中的元素进行修改。 Iterable<T> 定义了迭代元素的操作, Collection 继承自 Iterable<T> 接口,从而具有对集合迭代的能力。

Kotlin知识归纳(七) —— 集合

创建集合

Kotlin中创建集合一般都是通过 Collection.kt 中的顶层函数进行创建。具体方法如下:

集合类型 只读 可变
List listOf mutableList、arrayListOf
Set setOf mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
Map mapOf mutableMapOf、hashMapOf、linkeMapOf、sortedMapOf

像 arrayListOf 这些指明集合类型的顶层函数,创建时都是对应着Java相应类型的集合。为了弄清楚 Kotlin 的生成的只读集合(listOf、setOf 和 mapOf)与可变集合(mutableList、mutableSetOf 和 mutableMapOf)生成的是什么Java类型集合,做了一个小实验:

  • 1、使用在Java类中编写一些打印集合类型的静态方法:
#daqiJava.java
public static void collectionsType(Collection collection){
    System.out.println(collection.getClass().getName());
}

public static void mapType(Map map){
    System.out.println(map.getClass().getName());
}
复制代码
  • 2、在Kotlin中创建只读集合和可变集合,并将其传入之前声明的Java静态方法中进行打印:
#daqiKotlin.kt
fun main(args: Array<String>) {
    val list = listOf("")
    val set = setOf("")
    val map = mapOf("" to 1)
    val mutableList = mutableListOf("")
    val mutableSet = mutableSetOf("")
    val mutableMap = mutableMapOf("" to 1)
  
    println("只读集合类型:")
    collectionsType(list)
    collectionsType(set)
    mapType(map)
    println("可变集合类型:")
    collectionsType(mutableList)
    collectionsType(mutableSet)
    mapType(mutableMap)
}
复制代码

结果:

Kotlin知识归纳(七) —— 集合

可以得出只读集合(listOf、setOf 和 mapOf)与可变集合(mutableList、mutableSetOf 和 mutableMapOf)对应Java集合的关系表:

方法 Java类型
listOf java.util.Collections$SingletonList
setOf java.util.Collections$SingletonSet
mapOf java.util.Collections$SingletonMap
mutableList java.util.ArrayList
mutableSetOf java.util.LinkedHashSet
mutableMapOf java.util.LinkedHashMap

型变

只读集合类型是型变的。当类 Rectangle 继承自 Shape,则可以在需要 List<Shape> 的任何地方使用 List<Rectangle>。 因为集合类型与元素类型具有相同的子类型关系。 Map在值类型上是型变的,但在键类型上不是。

可变集合不是型变的。 MutableList 是 MutableList 的子类型,当你插入其他 Shape 的继承者(例如,Circle),从而违反了它的 Rectangle 类型参数。

集合的可空性

对于任何类型,都可以对其声明为可空类型,集合也不例外。你可以将集合元素的类型设置为可空,也可以将集合本身设置为可空,需要清楚是集合的元素可空还是集合本身可空。

Kotlin知识归纳(七) —— 集合

Kotlin集合的秘密:平台相关声明

寻找集合的java.util.ArrayList

学习 Kotlin 的时候,常常被告知 Kotlin 直接使用的是原生 Java 集合,抱着探究真相的心态,点进了创建集合的顶层方法 mutableListOf() 。

#Collections.kt
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
    if (elements.size == 0) 
        ArrayList() 
    else
        ArrayList(ArrayAsCollection(elements, isVarargs = true))
复制代码

在源码中看到了熟悉的 ArrayList ,但你以为这就结束了吗?就是使用Java的ArrayList嘛?继续点进ArrayList,发现是一个Kotlin定义的ArrayList:

#ArrayList.kt
expect class ArrayList<E> : MutableList<E>, RandomAccess {
    constructor()
    constructor(initialCapacity: Int)
    constructor(elements: Collection<E>)
    
    //... 省略一些来自List、MutableCollection和MutableList的方法
    //这些方法只有声明,没有具体实现。
}
复制代码

逛了一大圈,并没有找到一丝 Java 的 ArrayList 的痕迹.... Excuse me??? 说好的使用 Java 的 ArrayList ,但自己又创建了一个ArrayList.... 。最后将目标锁定在类声明的 expect 关键字,这是什么?最后在Kotlin官网中查到,这是Kotlin平台相关声明的 预期声明

平台相关声明

在其他语言中,通常在公共代码中构建一组接口,并在平台相关模块中实现这些接口来实现多平台。然而,当在其中某个平台上已有一个实现所需功能的库,并且希望 直接使用 该库的API而无需额外包装器时,这种方法并不理想。

Kotlin 提供平台相关声明机制。 利用这种机制,公共模块中定义 预期声明 ,而平台模块提供与预期声明相对应的 实际声明

要点:

  • 公共模块中的预期声明与其对应的实际声明始终具有完全相同的完整限定名。
  • 预期声明标有 expect 关键字;实际声明标有 actual 关键字。
  • 与预期声明的任何部分匹配的所有实际声明都需要标记为 actual。
  • 预期声明 决不包含任何实现代码。

官网提供一个简单的例子:

#kt
//在公共模块中定义一个预期声明(不带任何实现)
expect class Foo(bar: String) {
    fun frob()
}

fun main() {
    Foo("Hello").frob()
}
复制代码

相应的 JVM 模块提供实现声明和相应的实现:

#kt
//提供实际声明
actual class Foo actual constructor(val bar: String) {
    actual fun frob() {
        println("Frobbing the $bar")
    }
}
复制代码

如果有一个希望用在公共代码中的平台相关的库,同时为其他平台提供自己的实现。(像Java已提供好完整的集合库)那么可以将 现有类的别名 作为实际声明:

expect class AtomicRef<V>(value: V) {
  fun get(): V
  fun set(value: V)
  fun getAndSet(value: V): V
  fun compareAndSet(expect: V, update: V): Boolean
}

actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
复制代码

而Java集合类作为实际声明的别名被定义在 TypeAliases.kt 中。这是我不知道 TypeAliases.kt 时的查找流程:

Kotlin知识归纳(七) —— 集合
# TypeAliases.kt
@SinceKotlin("1.1") public actual typealias RandomAccess = java.util.RandomAccess

@SinceKotlin("1.1") public actual typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public actual typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public actual typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public actual typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public actual typealias HashSet<E> = java.util.HashSet<E>
复制代码

Kotlin定义一些集合类作为集合的通用层(使用 expect 定义预期声明),并将现有的Java集合类的别名作为实际声明,从而实现在JVM上直接使用Java的集合类。

ArrayList的变迁

可以从Kotlin官方文档中集合的变迁来观察(ArrayList为例):

  • 1.0版本ArrayList:
    Kotlin知识归纳(七) —— 集合
  • 1.1版本ArrayList:
    Kotlin知识归纳(七) —— 集合
  • 1.3版本ArrayList:
    Kotlin知识归纳(七) —— 集合

从原本无ArrayList.kt,只有一系列对ArrayList.java的扩展属性与方法

-> 使用别名引用Java的ArrayList.java,ArrayList.kt服务于Js模块。

-> 使用平台相关声明,将ArrayList.kt作为 预期声明 ,并在JVM模块、Js模块、Native模块中提供具体的 实际声明 。使Kotlin对外提供"通用层"API,在不改变代码的情况下,实现跨平台。

只读集合与平台相关声明

但很奇怪....,创建只读集合的顶层函数(listOf、setOf和mapOf)源码中,直接使用java的 Collections$SingletonList、Collections$SingletonSet 和 Collections$SingletonMap。望大佬告知其中原由 0.0。

//listOf
public fun <T> listOf(element: T): List<T> =
java.util.Collections.singletonList(element)

//setOf
public fun <T> setOf(element: T): Set<T> =
java.util.Collections.singleton(element)

//mapOf
public fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> =
java.util.Collections.singletonMap(pair.first, pair.second)
复制代码

集合的函数式API

了解了一波Kotlin的集合后,需要回归到对集合的使用上——集合的函数式API。

filter函数

基本定义:

filter函数遍历集合并返回给定lambda中返回true的元素。

源码:

#_Collection.kt
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    //创建一个新的集合并连同lambda一起传递给filterTo()
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    //遍历原集合
    for (element in this) 
        //执行lambda,如返回为true,则将该元素添加到新集合中
        if (predicate(element)) 
            destination.add(element)
    //返回新集合
    return destination
}
复制代码

解析:

创建一个新的ArrayList对象,遍历原集合,将lambda表达式返回true的元素添加到新ArrayList对象中,最后返回新的ArrayList对象。

Kotlin知识归纳(七) —— 集合

map函数

基本定义:

map函数对集合中每一个元素应用给定的函数,并把结果收集到一个新集合。

源码:

#_Collection.kt
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    //创建一个新的集合并连同lambda一起传递给mapTo()
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    //遍历旧集合元素
    for (item in this)
        //执行lambda,对元素进行处理,将返回值添加到新集合中
        destination.add(transform(item))
    //返回新集合
    return destination
}
复制代码

解析:

创建一个新的ArrayList集合,遍历原集合,将函数类型对象处理过的值添加到新ArrayList对象中,并返回新的ArrayList对象。

Kotlin知识归纳(七) —— 集合

groupBy函数

基本定义:

对集合元素进行分组,并返回一个Map集合,存储元素分组依据的键和元素分组

源码:

#_Collection.kt
public inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>> {
    //创建一个新的map并连同lambda一起传递给groupByTo()
    return groupByTo(LinkedHashMap<K, MutableList<T>>(), keySelector)
}

public inline fun <T, K, M : MutableMap<in K, MutableList<T>>> Iterable<T>.groupByTo(destination: M, keySelector: (T) -> K): M {
    //遍历旧集合元素
    for (element in this) {
        //执行lambda,对元素进行处理,将返回值作为key
        val key = keySelector(element)
        //使用得到的key在新的map中获取vlaue,如果没有则创建一个ArrayList对象,作为value存储到map中,并返回ArrayList对象。
        val list = destination.getOrPut(key) { ArrayList<T>() }
        //对ArrayList对象添加当前元素
        list.add(element)
    }
    //返回新集合
    return destination
}
复制代码

解析:

创建一个LinkedHashMap对象,遍历旧集合的元素,将函数类型对象处理过的值作为key,对应的元素存储到一个ArrayList中,并将该ArrayList对象作为map的value进行存储。返回LinkedHashMap对象。

Kotlin知识归纳(七) —— 集合

flatMap函数

基本定义:

根据实参给定的函数对集合中的每个元素做交换(映射),然后把多个列表平铺成一个列表。

源码:

#_Collection.kt
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    //创建一个新的集合并连同lambda一起传递给flatMapTo()
    return flatMapTo(ArrayList<R>(), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {   
    ////遍历旧集合元素
    for (element in this) {
        //执行lambda,对元素进行处理,返回一个集合
        val list = transform(element)
        //在得到的集合添加到新的集合中。
        destination.addAll(list)
    }
    //返回新集合
    return destination
}
复制代码

解析:

创建一个新的ArrayList集合,遍历原集合,对原集合的元素转换成列表,最后将转换得到的列表存储到新的ArrayList集合中,并返回新的ArrayList对象。

Kotlin知识归纳(七) —— 集合

all函数 和 any函数

基本定义:

检查集合中的所有元素是否都符合或是否存在符合的元素。

源码:

#_Collection.kt
//any
public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
    //判断他是否为空,如果集合为空集合,直接返回false,因为肯定不存在
    if (this is Collection && isEmpty()) 
        return false
    for (element in this) 
        //遍历元素的过程中,如果有其中一个元素满足条件,则直接返回true
        if (predicate(element)) 
            return true
    //最后都不行,就返回false
    return false
}

//all
public inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean {
    //如果集合为空集合,直接返回true
    if (this is Collection && isEmpty()) 
        return true
    for (element in this) 
        //遍历元素的过程中,只要有其中一个元素满足条件,则直接返回false
        if (!predicate(element)) 
            return false
    return true
}
复制代码

count函数

基本定义:

检查有多少满足条件的元素数量。

源码:

#_Collection.kt
public inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int {
    if (this is Collection && isEmpty()) 
        return 0
    //弄一个临时变量记录数量
    var count = 0
    //遍历元素
    for (element in this) 
        //如果满足添加,则数量+1
        if (predicate(element)) 
            checkCountOverflow(++count)
    return count
}
复制代码

find函数

基本定义:

寻找第一个符合条件的元素,如果没有符合条件的元素,则返回null。

源码:

#_Collection.kt
public inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
    //将lambda传给firstOrNull()
    return firstOrNull(predicate)
}

public inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T? {
    for (element in this) 
        //遍历的元素中,返回第一个符合满足添加的元素。
        if (predicate(element)) 
            return element
    //没找到,则返回null
    return null
}
复制代码

集合使用的注意事项

  • 优先使用只读集合,只有在需要修改集合的情况下才使用可变集合。
  • 只读集合不一定是不可变的。如果你使用的变量是只读接口的类型,该变量可能是引用的是一个可变集合。因为只读接口Collection是所有集合"基类"
  • 只读集合并不总是线程安全的。如果需要在多线程环境中处理数据,必须使用支持并发访问的数据结构。

数组

Kotlin数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数。

在Kotlin中提供以下方法创建数组:

  • arrayOf函数,该函数的实参作为数组的元素。
  • arrayOfNulls函数,创建一个给定大小的数组,包含的是null值。一般用来创建元素类型可空的数组
  • Array构造方法,接收一个数组的大小和lambda表达式。lambda表达式用来创建每一个数组元素,不能显式地传递每一个元素。
val array = Array<String>(5){
    it.toChar() + "a"
}
复制代码

Kotlin最常见的创建数组的情况是:调用需要数组为参数的Java方法,或调用带有vararg参数的Kotlin函数。这时需要使用toTypeArray()将集合转换成数组。

val list = listOf("daqi","java","kotlin")
//集合转数组
list.toTypedArray()

val array = arrayOf("")
//数组转集合
array.toList()
复制代码

Array类的类型参数决定了创建的是一个基本数据类型装箱的数组。当需要创建没有装箱的基本数据类型的数组时,必须使用基本数据类型数组。Kotlin为每一种基本数据类型提供独立的基本数据类型数组。例如:Int类型的数组叫做IntArray。基本数据类型数组会被编译成普通的Java基本数据类型的数组,如int[].因此基本数据类型数组在存储值时并没有装箱。

创建基本数据类型数组:

  • 工厂方法(例如intArrayOf)接收变长参数并创建存储这些值的数组。
  • 基本数据类型数组的构造方法。

Kotlin标准库中对集合的支持扩展库(filter、map等)一样适用于数组,包括基本数据类型的数组。

原文  https://juejin.im/post/5d1045bc5188255a4f75be69
正文到此结束
Loading...