Kotlin基础语法

2022-05-13/2022-05-13

getter/setter

直接看例子

open class Person {
    var age: Int = 10
        //getter缺省为默认
        //setter设置参数前打印参数
        set(value) {
            println("setter $value")
            //field关键字指向属性本身
            field = value
        }
}

@JvmStatic
fun main(args: Array<String>) {
    val p = Person()
    println(p.age)
    p.age = 30
    println(p.age)
}
10
setter 30
30

修饰符

修饰符JavaKotlin
public任意可见同Java
private本类内部可见同Java
protected本类或子类可见同Java
缺省包和子包中可见同public
internal--同模块可见

运算符

常规的数学运算符与Java一致,要关注一下位运算符。

位运算JavaKotlin
&and
|or
取反~inv
异或^xor
左移<<shl
右移>>shr
无符号右移>>>ushr

?. 和 ? :

?.在对象非空时会执行后面的调用,对象为空时就会返回 null

可以使用 Kotlin 中的 ?: 来兜底

?:左侧表达式如果结果为空,则返回右侧的值

val str: String? = "Hello"
                             👇
val length: Int = str?.length ?: -1

此时会返回-1

标准函数With,apply,run,also,takeif

with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回

用处:可以在连续调用同一个对象的多个方法时让代码变得更加精简

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for (fruit in list) {
 builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
println(result)


val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = with(StringBuilder()) {
 append("Start eating fruits.\n")
 for (fruit in list) {
 append(fruit).append("\n")
 }
 append("Ate all fruits.")
 toString()
}
println(result)

run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数是一样的,包括也会使用Lambda表达式中的最后一行代码作为返回值返回

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run {
 append("Start eating fruits.\n")
 for (fruit in list) {
 append(fruit).append("\n")
 }
 append("Ate all fruits.")
 toString()
}
println(result)

//run函数原理
public inline fun <T, R> T.run(block: T.() -> R): R = block()
//run是任何类型T的通⽤扩展函数,run中执⾏了返回类型为R的扩展函数block,最终返回该扩展函数的结果。

apply函数和run函数也是极其类似的,都要在某个对象上调用,并且只接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply {
 append("Start eating fruits.\n")
 for (fruit in list) {
 append(fruit).append("\n")
 }
 append("Ate all fruits.")
}
println(result.toString())

//apply原理
public inline fun <T> T.apply(block: T.() -> Unit): T { block();return this}

also

also是Kotlin 1.1版本中新加⼊的内容,它像是let和apply函数的加强版。

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

takeif

让我们来看看takeIf可以实现判空和加⼊条件,是更强大的let

val result = student.takeIf { it.age >= 18 }.let { ... }

扩展函数

语法结构:

fun ClassName.methodName(param1: Int, param2: Int): Int {
 return 0
}

//示例:
fun String.lettersCount(): Int {
 var count = 0
 for (char in this) {
 if (char.isLetter()) {
 count++
 }
 }
 return count
}

来看看使用扩展函数来解决findViewById麻烦的问题

fun <T : View> Activity._view(@IdRes id: Int): T { return findViewById(id) as T }

现在,我们就可以这样写了

oginButton = _view(R.id.btn_login);
nameEditText = _view(R.id.et_name);

但是R.id.看着也很烦,我们可以接着这样改

fun Int.onClick(click: ()->Unit){
// _view 为我们之前定义的简化版findViewById 
val tmp = _view <View>(this)
.apply { setOnClickListener
{ click() } 
	} 
		}
R.id.btn_login.onClick { println("Login…") }

这其实就是Kotlin-android-extensions的原理

密封类SealedClass

用来在when情况下优化代码

interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result

fun getResultMsg(result: Result) = when (result) {
 is Success -> result.msg
 is Failure -> result.error.message
 else -> throw IllegalArgumentException()
}

在这里我们不得不编写一个else条件,否则Kotlin编译器会认为这里缺少条件分支,代码将无法编译通过。但实际上Result的执行结果只可能是Success或者Failure,这个else条件是永远走不到的,所以我们在这里直接抛出了一个异常,只是为了满足Kotlin编译器的语法检查而已。

另外,编写else条件还有一个潜在的风险。如果我们现在新增了一个Unknown类并实现Result接口,用于表示未知的执行结果,但是忘记在getResultMsg()方法中添加相应的条件分支,编译器在这种情况下是不会提醒我们的,而是会在运行的时候进入else条件里面,从而抛出异常并导致程序崩溃。

优化:

sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()

fun getResultMsg(result: Result) = when (result) {
 is Success -> result.msg
 is Failure -> "Error is ${result.error.message}"
}

运算符重载

以加法为例

class Obj {
 operator fun plus(obj: Obj): Obj {
 // 处理相加的逻辑
 }
}

val obj1 = Obj()
val obj2 = Obj()
val obj3 = obj1 + obj2

image-20220423142402871

== 与 ===

在 Java 中,== 比较的如果是基本数据类型则判断值是否相等,如果比较的是 String 则表示引用地址是否相等, String 字符串的内容比较使用的是 equals()

在Kotlin中

  • == :可以对基本数据类型以及 String 等类型进行内容比较,相当于 Java 中的 equals
  • === :对引用的内存地址进行比较,相当于 Java 中的 ==

委托by

类委托

类委托的核心思想在于将一个类的具体实现委托给另一个类去完成

比如我们要去扩展一个HashSet,可以这样做

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
 override val size: Int
 get() = helperSet.size
 override fun contains(element: T) = helperSet.contains(element)
 override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
 override fun isEmpty() = helperSet.isEmpty()
 override fun iterator() = helperSet.iterator()
}

但是这种写法也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个方法的话,每个都去这样调用辅助对象中的相应方法实现,就很麻烦

在Kt中可以这样写

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
 fun helloWorld() = println("Hello World")
 override fun isEmpty() = false
}

属性委托

类委托的核心思想是将一个类的具体实现委托给另一个类去完成,而委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成

语法结构:

class MyClass {
 var p by Delegate()
}

使用by关键字将p属性的具体实现委托给了Delegate类去完成

class Delegate {
 var propValue: Any? = null
 operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
 return propValue
 }
 operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
 propValue = value
 }
}

getValue()方法要接收两个参数:第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里写成MyClass表示仅可在MyClass类中使用;第二个参数KProperty<*>是 Kotlin中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在方法参数上进行声明。

实现一个懒加载

class Later<T>(val block: () -> T) {
 var value: Any? = null
 operator fun getValue(any: Any?, prop: KProperty<*>): T {
 if (value == null) {
 value = block()
 }
 return value as T
 }
}

fun <T> later(block: () -> T) = Later(block)

顶层函数,顶层属性与中缀调用

创建一个名为HighestLevelFun.kt得文件

注意 这个函数并没有写在class 里面

fun myAdd(a: Int, b: Int) = a + b
//在主函数中调用
fun main() {
    println(myAdd(1, 2))
}

Kotlin 顶层函数相当于 Java 中的静态函数,往往我们在 Java 中会用到类似 Utils 的类来放置不属于任何类的公共静态函数。

const val UNIX_LINE_SEPARATOR = "\n"

相当于

public static final String  UNIX_LINE_SEPARATOR = "\n";

中缀调用是基于扩展函数得

fun Int.add(toAddNum:Int) = this + toAddNum

这是一个Int的扩展函数,常规我们要用它应该是 1.add(2) 然后返回值是3

但是写成

infix fun Int.add(toAddNum:Int) = this + toAddNum

我们就可以省略 . 和括号 可以直接写 1 add 2

infix fun Int.add(toAddNum:Int) = this + toAddNum

中缀调用只能有一个参数

Kotlin中的高阶函数与内联函数

高阶函数

高阶函数:一个函数接收了另一个函数作为参数,或者返回值的类型是另一个函数

什么意思,要高阶函数有什么用?我一个函数接收另一个函数能干啥子?

看下面这个例子:

在java中,我们是没有办法把方法作为参数传递的,但是我们有一个历史悠久的变通方案:接口。我们可以通过接口的方式来把方法包装起来,比如我们给一个view设置OnClickListener

OnClickListener listener1 = new OnClickListener() {
  @Override
  void onClick(View v) {
    doSomething();
  }
};
view.setOnClickListener(listener1);

实际上我们的核心是onClick里面的代码,我们之所以写OnClickListener是因为我们没有办法将onClick这么一个方法直接传进去,所以退而求其次了,而在Kotlin中我们就可以直接将方法传进去。

那么在Kotlin中怎么将一个函数传到另一个函数中呢?

fun a(funParam: (Int) -> Int) {
            println(funParam(2))
        }

        fun b(num:Int):Int{
            return num+1
        }


        fun main(args: Array<String>) {
            a(::b)
        }

就像这样,a函数就能接受一个函数参数是Int,返回值是Int的函数类型了

上面的代码输出结果为3

::b是什么意思?用专业的术语来讲它叫函数引用,什么意思呢,其实我们给函数给函数传一个函数是不可能的对吧,函数他就是个函数,怎么能传递呢,只有对象才能被传递是吧,所以其实::b的意思是实现了一个对象这个对象它的作用和b函数是一样的,然后将这个对象传给了a函数

既然::b是个对象,我们当然也可以这样写

fun main(args: Array<String>) {
            val c=::b
            a(c)
        }

那么既然funParam是一个对象,他是怎么做到funParam(2)的呢?其实这是一个语法糖,它实际调用的是funParam.invoke(2),关于这个funParam.invoke()我们后面还会再提一嘴。

我们也能使用匿名函数的方法实现和上面一样的效果

a({num -> num+1})

然后当Lamda表达式是函数最后一个参数,可以讲Landa表达式移到括号外面

a (){ num->num + 1 }

又当Landa表达式是a的唯一一个参数时,我们可以把()去掉

a { num->num + 1 }

这个匿名函数实际也是作为一个对象的形式传给a的

最后,还记得最开始讲的那个关于设置点击事件那个例子吗?在Kotlin中我们就可以这样写了

fun setOnClickListener(onClick: (View) -> Unit) {
  this.onClick = onClick
}
view.setOnClickListener(fun(v: View): Unit) {
  switchToNextPage()
})
view.setOnClickListener({ v: View ->
  switchToNextPage()
})

因为当Lamda表达式是函数最后一个参数,可以这样写

view.setOnClickListener() { v: View ->
  switchToNextPage()
}

而如果 Lambda 是函数唯一的参数,你还可以直接把括号去了

另外,如果这个 Lambda 是单参数的,它的这个参数也省略掉不写:

view.setOnClickListener {
  switchToNextPage()
}

是不是方便简洁多了

最后,记住一句话:

在 Kotlin 里「函数并不能传递,传递的是对象」和「匿名函数和 Lambda 表达式其实都是对象」

拥有上下文:

fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
 block()
 return this
}

fun main() {
 val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
 val result = StringBuilder().build {
 append("Start eating fruits.\n")
 for (fruit in list) {
 append(fruit).append("\n")
 }
 append("Ate all fruits.")
 }
 println(result.toString())
}

在函数类型的前面加上ClassName. 就表示这个函数类型是定义在哪个类当中的。

那么这里将函数类型定义到StringBuilder类当中有什么好处呢?好处就是当我们调用build函数时传入的Lambda表达式将会自动拥有StringBuilder的上下文

Kotlin中用高阶函数写回调的方式

class LoggerLocalDataSource(private val logDao: LogDao) {

    private val executorService: ExecutorService = Executors.newFixedThreadPool(4)
    private val mainThreadHandler by lazy {
        Handler(Looper.getMainLooper())
    }

    fun getAllLogs(callback: (List<Log>) -> Unit) {
        executorService.execute {
            val logs = logDao.getAll()
            mainThreadHandler.post { callback(logs) }
        }
    }

}

logger.getAllLogs { logs ->
            recyclerView.adapter =
                LogsViewAdapter(
                    logs,
                    dateFormatter
                )
        }

高阶函数解耦的例子

Shaw因为旅游喜欢上了地理,然后他建了⼀个所有国家的数据库。作为⼀名程序员,他设计了⼀个CountryApp类对国家数据进⾏操作。Shaw偏好欧洲的国家,于是他设计了⼀个程序来获取欧洲的所有国家。

data class Country(val name: String, val continient: String, val population: Int)
class CountryApp {
    fun filterCountries(countries: List<Country>): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countries) {
            if (c.continient == "EU") {
// EU代表欧洲
                res.add(c)
            }
        }
        return res
    }
}

但是如果我们现在想要程序通用性更强,可以根据输入的参数筛选不同的州呢?

image-20220511195731637

现在已经具备一定的复用性了,但是还可以传入一个用来判断条件的高阶函数,入参为country,返回值为true,这样就可以根据传入的高阶函数来判断某个州是否符合筛选条件

image-20220511195958300

函数与Lambda的区分

fun在没有等号、只有花括号的情况下,是我们最常见的代码块函数体,如果返回⾮Unit值,必须带return。

fun foo(x: Int) { print(x) } 
fun foo(x: Int, y: Int): Int { return x * y }

不管是⽤val还是fun,如果是等号加花括号的语法,那么构建的就是⼀个Lambda表达式,Lambda的参数在花括号内部声明。所以,如果左侧是fun,那么就是Lambda表达式函数体,也必须通过()或invoke来调⽤Lambda,如:

val foo = { x: Int, y: Int -> x + y } // foo.invoke(1, 2)
或foo(1, 2) fun foo(x: Int) = { y: Int -> x + y } // foo(1).invoke(2)或foo(1)(2)

内联函数

内联函数和Kotlin高阶函数的实现原理有关

对于上面咱们举得这个例子

fun a(funParam: (Int) -> Int) {
            println(funParam(2))
        }

        fun b(num:Int):Int{
            return num+1
        }


        fun main(args: Array<String>) {
            a(::b)
        }

我们上面就说了在 Kotlin 里「函数并不能传递,传递的是对象」和「匿名函数和 Lambda 表达式其实都是对象」

它实际的工作原理换成java代码是这样的

void a(Function operation) {
            println(operation.invoke(2));
        }

        int b(num:Int){
            return num+1;
        }


        main() {
            a(new Function(){
                @Override
                public Integer invoke(Integer n1){
                    return n1+1;
                }
            }
            )
        }

可以看到,实际和我们讲的最开始java代码设置监听器的例子差不多

如果我们是使用Lamda表达式每次去传递函数类型的参数,就会导致每一次都创建一个对象,造成了额外的性能开销

我们可以使用内联函数来解决这个问题

Kotlin中 inline 关键字不止可以内联自己的内部代码,还可以内联自己的函数类型的参数

经过这种优化,就避免了函数类型的参数所造成的临时对象的创建了

inline使用场景:

你写的是高阶函数,会有函数类型的参数,加上 inline 就对了。

noinline

作用与函数的函数类型的参数,让这个函数类型的参数不被内联

为什么要这样做?

因为如果要将函数类型的参数return就会报错,因为此时这个函数被内联了,它没有被创建为对象,因此无法被返回

noinline使用场景:

不用判断,Android Studio 会告诉你的。当你在内联函数里对函数类型的参数使用了风骚操作,Android Studio 拒绝编译的时候,你再加上 noinline 就可以了。

Crossinline

Lambda 表达式里不允许使用 return,**除非——**这个 Lambda 是内联函数的参数

为什么Lambda 表达式里不允许使用 return?看这个例子:

http://image.rengwuxian.com/2021/03/25/37aaef195582a.png

假如我往这个 Lambda 表达式里加一个 return:

http://image.rengwuxian.com/2021/03/25/201e423facad6.png

实际上是这样的:

http://image.rengwuxian.com/2021/03/25/53b6f2d9d54b3.png

这会导致 return 结束哪个函数,要看这个函数是不是内联函数

这种不一致性会给我们带来极大困扰,因此 Kotlin 制定了一条规则:Lambda 表达式里不允许使用 return,**除非——**这个 Lambda 是内联函数的参数。

因此有以下结论:

  1. Lambda 里的 return,结束的不是直接的外层函数,而是外层再外层的函数;
  2. 但只有内联函数的 Lambda 参数可以使用 return。(但因为函数被内联了,所以实际上结束的也是外层再外层的函数)

但再看一种情况

http://image.rengwuxian.com/2021/03/25/91731372432d1.png

当内联函数的 Lambda 参数在函数内部是间接调用的时候,Lambda 里面的 return 会无法按照预期的行为进行工作。

本来在调用处最后那行的 return 是要结束它外层再外层的函数的,但现在因为它被放在了 runOnUiThread() 里,hello() 对它的调用就变成了间接调用。所谓间接调用,直白点说就是它和外层的 hello() 函数之间的关系被切断了。和 hello() 的关系被切断,那就更够不着更外层的 main() 了,也就是说这个间接调用,导致 Lambda 里的 return 无法结束最外面的 main() 函数了。

Kotlin 内联函数里的函数类型的参数,不允许这种间接调用。

那如果我们确实有这种需求呢?

crossinline 也是一个用在参数上的关键字。当你给一个需要被间接调用的参数加上 crossinline,就对它解除了这个限制,从而就可以对它进行间接调用了

不过内联函数里被 crossinline 修饰的函数类型的参数,不能再使用 return

Reified

可以用来泛型实化,但必须在内联函数中使用(它之所以可以做到泛型实化也是因为内联的特性)

应用泛型实化我们可以拿到泛型的具体类型

例子:

inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

startActivity<NewActivity>(context)
inline fun <reified T> Resources.dpToPx(value: Int): T {
    val result = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        value.toFloat(), displayMetrics)

    return when (T::class) {
        Float::class -> result as T
        Int::class -> result.toInt() as T
        else -> throw IllegalStateException("Type not supported")
    }
}

// Caller
val intValue: Int = resource.dpToPx(64)
val floatValue: Float = resource.dpToPx(64)

集合的高阶函数API

1.map

val list = listOf(1, 2, 3, 4, 5, 6)

上⾯是⼀个Kotlin的列表,如果要对其中的每个元素都乘以2,我们可以这样去做:

val newList = list.map {it * 2}

使⽤map⽅法之后,会产出⼀个新的集合,并且集合的⼤⼩与原集合⼀样

map原理

/**
 * Returns a list containing the results of applying the given [transform] function
 * to each element in the original collection.
 * 
 * @sample samples.collections.Collections.Transformations.map
 */
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

internal fun <T> Iterable<T>.collectionSizeOrDefault(default: Int): Int = if (this is Collection<*>) this.size else default

/**
 * Applies the given [transform] function to each element of the original collection
 * and appends the results to the given [destination].
 */
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
}

在上面的代码中,首先定义了map扩展⽅法,它的实现主要是依赖mapTo方法。mapTo方法接收两个参数,第1个参数类型是集合(MutableCollection),第2 个参 数为 ⼀个 ⽅法 (transform:(T)-> R)),最终返回⼀个集合。在mapTo⽅法内部的实现其实很简单, 就是将transform⽅法产⽣的结果添加到⼀个新集合里面去,最终返回这个新的集合。

对集合进行筛选:filter、count

假如我们定义了⼀个学⽣组成的列表,现在我们要获取⼀份由所有男学⽣组成的列表

可以使用filter集合高阶函数

val mStudents = students.filter {it.sex == "m"}

通过使⽤filter⽅法,我们就筛选出了性别为男的学⽣。该⽅法与 map类似,也是接收⼀个函数,只是该函数的返回值类型必须是 Boolean。该函数的作⽤就是判断集合中的每⼀项是否满⾜某个条件,如果满⾜,filter⽅法就会将该项插⼊新的列表中,最终就得到了⼀个满⾜给定条件的新列表。

filter原理

/**
 * Returns a list containing only elements matching the given [predicate].
 * 
 * @sample samples.collections.Collections.Filtering.filter
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

/**
 * Appends all elements matching the given [predicate] to the given [destination].
 * 
 * @sample samples.collections.Collections.Filtering.filterTo
 */
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

fold、reduce

fold⽅法需要接收两个参数,第1个参数initial通常称为初始值,第2个参数operation是⼀个函数。

在实现的时候,通过for语句来遍历集合中的每个元素,每次都会调⽤operation函数,⽽该函数的参数有两个,⼀个是上⼀次调⽤该函数的结果(如果是第⼀次调⽤,则传⼊初始值initial),另外⼀个则是当前遍历到的集合元素。简单来说就是:每次都调⽤operation函数,然后将产⽣的结果作为参数提供给下⼀次调⽤。

val scoreTotal = students.fold(0) {accumulator, student -> accumulator + student}

通过上⾯的⽅式我们能得到所有学⽣的总分

reduce⽅法和fold⾮常相似,唯⼀的区别就是reduce⽅法没有初始值

val scoreTotal = students.reduce {accumulator, student -> accumulator + student}

通过上⾯的⽅式我们也能得到所有学⽣的总分

扁平化——处理嵌套集合:flatMap、flatten

flatten可以对嵌套集合扁平化。

比如这样一个嵌套集合

val list = listOf(listOf(jilen, shaw, lisa), listOf(yison, pan), listOf(jack))

如果我们希望嵌套集合中各个元素能够被拿出来,然后组成⼀个只有这些元素的集合

val newList = listOf(jilen, shaw, lisa, yison, pan, jack)

可以这样做list.flatten()

flatMap原理

/**
 * Returns a single list of all elements from all collections in the given collection.
 * @sample samples.collections.Iterables.Operations.flattenIterable
 */
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
    val result = ArrayList<T>()
    for (element in this) {
        result.addAll(element)
    }
    return result
}

首先声明一个新数组result。然后遍历嵌套集合,将每一个子集合中的元素通过addAll方法添加到result中。最终就得到了一个扁平化的集合。

假如我们并不是想直接得到一个扁平化之后的集合,而是希望将子集合中的元素“加工”一下,然后返回一个“加工”之后的集合。
比如我们要得到一个由姓名组成的列表,应该如何去做呢?Kotlin还给我们提供了一个方法——flatMap,可以用它实现这个需求:

>>> list.flatMap {it.map{it.name}}
[Jilen, Shaw, Lisa, Yison, Pan, Jack]

flatMap原理

/**
 * Returns a single list of all elements yielded from results of [transform] function being invoked on each element of original collection.
 * 
 * @sample samples.collections.Collections.Transformations.flatMap
 */
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}

/**
 * Appends all elements yielded from results of [transform] function being invoked on each element of original collection, to the given [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
}

kt中的集合

Kt中集合的继承关系

image-20220513164034348

从图中可以看出,lteratable为Kotlin集合库的顶层接口。
每一个集合都分为俩种,一种是带Mutable的,另一种则是不带的。比如我们常见的列表就分为MutableList和List,List实现了Collection接口,MutableList实现了MutableCollection和List (MutableList表示可变的List,而List则表示只读的List).
其实Kotlin的集合都是以Java的集合厍为基础,而Kotlin通过扩展函数增强了它。

惰性集合

val list = listOf(1, 2, 3, 4, 5) 
list.filter {it > 2}.map {it * 2}

在前面我们介绍过,filter方法和map方法都会返回一个新的集合,也就是说上面的操作会产生两个临时集合,因为list会先调⽤filter⽅法,然后产⽣的集合会再次调⽤map⽅法。如果list中的元素⾮常多, 这将会是⼀笔不⼩的开销。为了解决这⼀问题,序列(Sequence) 就出现了。

上面的写法可以改成这样

list.asSequence().filter {it > 2}.map {it * 2}.toList()

首先通过asSequence()将列表转换为序列,然后在这个序列上进⾏相应的操作,最后通过toList()⽅法将序列转换为列

表。

而在使⽤序列的时候,filter⽅法和map⽅法的操作都没有创建额外的集合,这样当集合中的元素数量巨⼤的时候,就减少了⼤部分开销。

这是因为Kt中序列中元素的求值是惰性的,惰性求值指的是在需要时才进⾏求值的计算⽅式。在使⽤惰性求值的时候,表达式不在它

被绑定到变量之后就⽴即求值,⽽是在该值被取⽤时才去求值。

list
.asSequence()
.filter { println("filter($it)") it > 2 }
.map { println("map($it)") it * 2 } 
//结果 kotlin.sequences.TransformingSequence@7d8abe58

看到以上代码

上⾯的操作中的println⽅法根本就没有被执⾏,这说明filter⽅法和map⽅法的执⾏被延迟了,这就是惰性求值的体现

只有在我们需要最后的这个结果时,这些个中间操作如filter,map才会开始运算

list
.asSequence()
.filter { println("filter($it)") it > 2 }
.map { println("map($it)") it * 2 }
.toList() 
//结果 filter(1) filter(2) filter(3) map(3) filter(4) map(4) filter(5) map(5) [6, 8, 10]

作为对⽐,我们先来看看上⾯的操作如果不⽤序列⽽⽤列表来实现会有什么不同之处:

list
.filter { println("filter($it)") it > 2 }
.map { println("map($it)") it * 2 } 
//结果 filter(1) filter(2) filter(3) filter(4) filter(5) map(3) map(4) map(5) [6, 8, 10]

普通集合在进⾏链式操作的时候会先在list上调⽤filter,然后产⽣⼀个结果列表,接下来map就在这个结果列表上进⾏操作。⽽序列则会将所有的操作都应⽤在⼀个元素上,也就是说,第1个元素执⾏完所有的操作之后,第2个元素再去执⾏所有的操作,以此类推。


标题:Kotlin基础语法
作者:OkAndGreat
地址:http://zhongtai521.wang/articles/2022/05/13/1652433339229.html

评论
发表评论
       
       
取消