Kotlin学习整理

Kotlin整理

  • 1. Kotlin基础知识点整理
    • 1.1 变量、函数、类型
    • 1.2 类、对象
  • 2. Kotlin与Java写法重要区分
    • 2.1 单例类写法
    • 2.2 匿名类写法
    • 2.3 参数(命名,默认,可变)
    • 2.4 顶层函数、属性
  • 3.集合
    • 3.1 常用的集合方法
    • 3.2 集合的Map和FlatMap
  • 4.标准函数
  • 5.高阶函数和内联函数inline、noinline、crossinline
    • 5.1 内联函数inline
    • 5.2 noinline和crossinline
  • 6.委托、by、lazy
    • 6.1 委托
    • 6.2 by和lazy
  • 7.infix函数
  • 8.泛型实化、协变、逆变
    • 8.1 泛型实化
    • 8.2 协变和逆变
  • 9.协程
    • 9.1 开启协程
    • 9.2 取消协程
    • 9.3 挂起协程
  • 10.DSL(Domain Specific Language)
  • 其他(小知识点)

本文章是 Kotlin知识点整理,有一定 Kotlin基础,适合快速复习查缺补漏,会不断更新。

1. Kotlin基础知识点整理

1.1 变量、函数、类型

变量:

//可变 变量,可以被重新赋值
var name:String = 1
//不可变 变量,不可被重新赋值
val age = 1
//不可变 变量定义的 list,map等,本身不可变,但是如果是可变列表,则本身不可变,但内部的数据可以变化
class Test {
    val listNumber: List<Int> = listOf(1, 2, 3, 4, 5, 6)
    val mutableListNumber: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6)

    fun test() {
        listNumber = listOf() // 该句报错,不可变类型
        listNumber[1] = 1 // 报错,不是可变列表

        mutableListNumber = mutableListOf() // 该句报错,不可变类型
        mutableListNumber[0] = 2 // 正常
    }
}

函数:用fun声明,默认是public类型

//冒号后是返回值类型
fun say(message: String): String {
    return "说了一句:$message"
}
//返回值可以直接用等号=处理
fun say(message: String): String = "说了一句:$message"

//函数中也可以嵌套函数,只能局部使用
fun out() {
    inner() // 编译报错,要在内部声明的函数之后
    fun inner() {
    }
    inner()
}

类型:没有基本数据类型,都作为对象存在

var number: Int = 10//还有Double Float Long Short Byte
var char: Char = 'c'
var isFood: Boolean = true
var ages: IntArray = intArrayOf(1, 2, 3)//还有FloatArray DoubleArray CharArray,都属于kotlin 的 built-in函数
var str: String = "Lili"

Kotiln中的Unit,相当于JavaVoid,空类型,但Kotlin还有Any类型,表示所有非空类型。
比如有返回结果,但不在意是什么类型:

fun getSomething(): Any {
}

1.2 类、对象

类:默认不是public,需要被继承,则要加open

//有open,可以被继承
open class KotlinTestActivity : AppCompatActivity(){

	//初始化代码块,优先于构造器执行
    init {

    }

	//加final,关闭了 override 的遗传性
    final override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        println("hello world")
    }

}

对象:实例化不用加new

//实例化一个学生对象
var studen: Student= Student()

对象的属性的get()、set()方法

class Student {
    var name: String = "jack"
        get() = "我是 $field"
        set(value) {
            field = "我叫 $value"
        }
}

避免写set()get()方法,可以使用元类data class

//注意属性是写在括号中的,会自动有set和get方法,中括号可以不用写
data class Student(
    var name:String = ""
){
    
}

2. Kotlin与Java写法重要区分

2.1 单例类写法

Java里单例双重锁,枚举等一堆,懒汉饿汉的。Kotlin里直接用object标记类即是单例。

object TestConstant {
	//静态常量
    const val MAX_NUM: Int = 10
}

静态常量还可以写到某个类中

//不用单例修饰的时候,要写companion object 伴生对象处理静态常量
class TestConstant {
    companion object {
        const val MAX_NUM: Int = 10
    }
}

2.2 匿名类写法

Java中匿名类,比如设置点击事件,都是直接new() 即可,Kotlin中要使用object来处理。比如

//java中
view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        
    }
});
//kotlin中
view.setOnClickListener(object :View.OnClickListener{
    override fun onClick(p0: View?) {
        
    }
})
//lambda更简洁
view.setOnClickListener{
}

2.3 参数(命名,默认,可变)

Kotlin中有默认参数可以设置,即可以简化Java中的方法重载

class Test {

    private fun getUserName(name: String, age: Int = 10, address: String? = ""): String {
    	// kotlin中if语句也是表达式,可以有返回值
        val add = if (address.isNullOrEmpty()) "" else " $address"
        return "我叫 $name 今年 ${age}$add"
    }

    fun test() {
        getUserName("Jack")
        getUserName("Jack", 22)
        getUserName("Jack", 22, "北京")
        getUserName("Jack",  "北京") // 编译出错,因为第二个参数默认应该是age是Int类型
        getUserName("Jack",  address = "北京") // 正常,命名可以指定某个参数的值
    }
}

Kotlin中的可变参数,即任意数量的参数

fun setNames(vararg name: String) {
    println(name.size) // 打印出传递的参数个数
    name.forEach {
        print(it) // 打印每个名字
    }
}
//调用
setNames("jack", "Bob", "lili")

2.4 顶层函数、属性

Kotlin中可以写顶层函数来直接引用,将属性或者函数放到class之外即可全局引用,Kotlin中常用的扩展函数,工具类等都可以简便的使用顶层函数处理。

package com.test.ext

import android.widget.EditText

//EditText的扩展函数,放到和package同级
fun EditText.getStr(isTrim: Boolean = false): String = this.text.toString().apply {
    if (isTrim) trim()
}

3.集合

3.1 常用的集合方法

fun test() {
        val list = listOf(1, 2, 3, 4, 5, 6) // 是个内联函数

        //最大最小、加减、总和
        list.any { it > 4 } // 只要有符合条件的,就返回true
        list.all { it > 3 } // 集合的全部元素都符合条件,才返回true
        list.none { it < 1 } // 集合的全部元素都不符合条件,才返回true
        list.count { it > 3 } // 返回集合中符合条件的总数
        list.sum() // 请求总和,只有Int类型的list可使用
        list.sumOf { // 每个元素都做一定操作后的值相加获得所有的元素总和
            it + 1
        }
        list.maxOrNull() // 返回集合中最大的元素,可为null,元素继承Comparable类型的list可使用
        list.minOrNull() // 返回集合中最小的元素,可为null,元素继承Comparable类型的list可使用


        //遍历
        list.forEach { // 遍历每一个元素,没有返回值
            print(it)
        }
        list.onEach { // 遍历每一个元素,返回集合本身
            print(it)
        }.size // 可以引用到数量
        list.forEachIndexed { index, value -> // 遍历每一个元素,包含下标和值
            print("下标 $index$value")
        }
        //过滤
        list.drop(3) // 从0开始,去除前3个元素,返回剩余的元素列表
        list.dropWhile { it > 3 } //从0开始,去除符合条件的前面的元素(该元素不去除),返回剩余的元素列表
        list.dropLastWhile { it > 3 } // 同上,只不过从最后开始去除
        list.filter { it > 3 } //返回所有符合条件的元素的列表
        list.filterNot { it > 3 } //返回所有不符合条件的元素的列表
        list.slice(2..5) //切片,返回2到5的元素组成的列表
        list.take(3) // 返回前3个元素组成列表
        list.takeLast(3) // 返回最后的3个元素组成的列表
        list.takeWhile { it > 3 } //从0开始,返回符合条件的前面的元素(该元素不返回)组成的集合,和drop相反
        list.takeLastWhile { it > 3 }// 同上,只不过从最后开始取


        val students: List<Student> = listOf()
        val mapResult = students.map {
            "你好" + it.name
        }
        println("mapResult $mapResult")
        val flatmapResult: List<String> = list.flatMap {
            listOf("数字是" + 1)
        }
        println("flatmapResult $flatmapResult")
}
data class Student(
        val name: String = "",
        val age: Int = 0,
)

3.2 集合的Map和FlatMap

定义数据类User

data class User(
    val name: String = "Jack",
    val age: Int = 28,
    val ability: List<String> = listOf()
)

调用测试,如果都只用name字段,则相同结果,因为name字段不是list,无法平铺

val list = listOf(
    User("Jack", 18, listOf("吃饭", "睡觉")),
    User("Tom", 18, listOf("跑步", "游泳")),
    User("Lili", 18, listOf("篮球", "跳远"))
)
//map:将List中每个元素转换成新的元素,并添加到一个新的List中,最后将新List返回
val mapResult = list.map {
    it.name
}
//flatMap:返回指定list的所有元素,组成的一个list (表达式中必须返回list)
val flatMapResult = list.flatMap {
    listOf(it.name)  // name不是list,需要包装成list
}
logDebug("mapResult = $mapResult flatMapResult = $flatMapResult", tag = "test")

结果

mapResult = [Jack, Tom, Lili] flatMapResult = [Jack, Tom, Lili]

如果替换返回的结果name改为ability,如下

//map:将List中每个元素转换成新的元素,并添加到一个新的List中,最后将新List返回
val mapResult = list.map {
    it.ability 
}
//flatMap:返回指定list的所有元素,组成的一个list (表达式中必修返回list)
val flatMapResult = list.flatMap {
    it.ability  //本身是list不用再包装
}

则打印结果

mapResult = [[吃饭, 睡觉], [跑步, 游泳], [篮球, 跳远]] flatMapResult = [吃饭, 睡觉, 跑步, 游泳, 篮球, 跳远]

4.标准函数

Kotlin中的标准函数withrunapply等,标准函数是Standard.kt文件中定义的函数。

val user = User("Jack", 18, listOf("吃饭", "喝水"))

with 最后一行为返回值,参数传入任意对象,且表达式中有该对象的上下文 (注意:需要直接调用

val result = with(user) { //最后一行为返回值
    "年龄是 $age"
}
logDebug(result, "test")

结果是

年龄是 18

run 基本同with,不用传参数,需要对象调用

val result = user.run { //最后一行为返回值
    "年龄是 $age"
}
logDebug(result, "test")

结果是

年龄是 18

apply 基本相同,但是无法指定返回值,以当前对象本身,即上下文作为返回值。

val result = user.apply { //返回该对象本身
    age = 999
}
logDebug(result, "test")

结果是

User(name=Jack, age=999, ability=[吃饭, 喝水])

5.高阶函数和内联函数inline、noinline、crossinline

高级函数 参数是函数或者返回值是函数的函数

kotlin里的函数类型表示

(String, Int) -> Unit

比如对两个数字操作,具体操作通过传入的方法来处理。

//参数包含一个执行函数
fun handle2Numbers(num1: Int, num2: Int, handle: (Int, Int) -> Int): Int = handle(num1, num2)
//两数相加
fun add(num1: Int, num2: Int): Int = num1 + num2
//两数相减
fun sub(num1: Int, num2: Int): Int = num1 - num2

函数当做参数,可指定方法去处理两个数字

handle2Numbers(1, 2, ::add)
handle2Numbers(1, 2, ::sub)

kotlin 高阶函数支持Lambda表达式,所以可以不用专门再定义addsub函数,直接使用Lambda表达式

handle2Numbers(1, 2) { n1, n2 ->
    n1 + n2
}
handle2Numbers(1, 2) { n1, n2 ->
    n1 - n2
}

5.1 内联函数inline

内联函数的关键字 inline
高阶函数一般实现的Lambda表达式,实际是被转换成了匿名内部类,即每调用一次Lambda表达式,就会创建一个新的匿名内部类。因此,直接用inline内联函数处理,kotlin在编译的时候自动替换到需要调用的地方,不会有性能开销。注意:inline函数内不能引用该类的私有变量

//函数fun前加inline关键字
inline fun handle2Numbers(num1: Int, num2: Int, handle: (Int, Int) -> Int): Int = handle(num1, num2)

如果一个内联函数有多个函数参数,默认所有的Lambda表达式都会进行替换,但是某些函数参数不想替换。或者某些情况,传入的函数参数,还要传递给其他的函数处理,inline关键字的函数内部只能把函数参数传递给inline函数,此时就可以用noinline关键字

//fun2不是内联函数
fun fun2(block: () -> Unit){
}
//fun1是内联函数,内部调用不是内联的fun2,会报错
inline fun fun1(age: Int, block1: () -> Unit, block2: () -> Unit) {
    fun2(block1) // 编译不通过
}

5.2 noinline和crossinline

可以把fun1的参数类型新加关键字noinline可编译通过,加上noinline的函数就不会走替换代码的流程

inline fun fun1(age: Int, noinline block1: () -> Unit, block2: () -> Unit) {
    fun2(block1)
}

普通函数的Lambda表达式中,可以用return关键字返回,

fun fun1(block: () -> Unit) {
    block()
    logDebug("fun1 end")
}

调用如下

fun1 {
    logDebug("block start")
    logDebug("block 测试普通函数的Lambda返回")
    return@fun1 // 普通函数不能只写return
    logDebug("block end")
}

结果

block start
block 测试普通函数的Lambda返回
fun1 end

可以看到,局部代码确实返回了,且后续的代码会继续执行
但是如果是inline内联的函数的Lambda表达式中的return语句

//改为了内联函数
inline fun fun1(block: () -> Unit) {
    block()
    logDebug("fun1 end")
}

调用用return,则会影响外层代码,直接return,如果用return@fun1,则还是局部返回

fun1 {
    logDebug("block start")
    logDebug("block 测试普通函数的Lambda返回")
    return
    logDebug("block end")
}

结果,后续的代码也不再执行,因为底层逻辑是把代码直接替换过去,相当于整块代码里走了return语句

block start
block 测试普通函数的Lambda返回

但如果内联函数的内部又创建匿名函数,则会报错

inline fun runRunnable(block: () -> Unit) {
    val runnable = Runnable {
        block() // 此句报错
    }
    runnable.run()
}

因为普通的Lambda表达式不允许直接return(非局部),而内联函数里的Lambda表达式允许直接return(非局部),因此要加上crossinline关键字

inline fun runRunnable(crossinline block: () -> Unit) {
    val runnable = Runnable {
        block()
    }
    runnable.run()
}

crossinline关键字相当于明确指出,本来内联函数里的Lambda表达式可以return(非局部)返回,现在有crossinline关键字标记,则该内联函数里的Lambda表达式不可以直接return(非局部)返回了。

6.委托、by、lazy

6.1 委托

委托类和委托属性
委托类 将一个类的实例化借助其他类实现
说白了就是借助辅助对象,实例化一个实际的对象,比如

//借助构造函数里的HashSet去实例化一个实现Set接口的对象
class MySet<T>(private val helperSet: HashSet<T>) : Set<T> {

    override val size: Int
        get() = helperSet.size

    override fun contains(element: T): Boolean = helperSet.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)

    override fun isEmpty(): Boolean = helperSet.isEmpty()

    override fun iterator(): Iterator<T> = helperSet.iterator()

}

6.2 by和lazy

这么看直接用辅助对象也没差别,但是当实现的对象需要其他方法,而大部分方法都需要辅助对象实现的时候就可以用委托的方法。kotlin里有更简单的写法

//自动已经实现各个方法,需要可重载单独的方法
class MySet<T>(private val helperSet: HashSet<T>) : Set<T> by helperSet

委托属性 就是将属性的实现委托给另一个类

class MyClass {
    var p by MyDelegate()
}

该代码表示 调用p属性的时候,会自动调用 MyDelegategetValue()方法,设置属性的时候,会自动调用setValue()方法。所以,MyDaelegate类必须有getValue()setValue()方法,且用operator关键字声明。

class MyDelegate() {
    var p: Any? = null
    operator fun getValue(myClass: MyClass, property: KProperty<*>): Any {
        return p ?: ""
    }

    operator fun setValue(myClass: MyClass, property: KProperty<*>, any: Any) {
        p = any
    }
}

lazy 函数

//注意不能用var,因为lazy里没有setValue函数
val p by lazy {
    ""
}

lazy函数延迟加载,其实就是生成了一个委托类,会返回一个Delegate对象,对象的getValue()方法,其实就是传入的Lambda表达式,即p的值,就是在首次调用的时候 Lambda表达式最后一行的返回值

看一个Android里常用的方法,在Activity页面中获取ViewModel,会使用下边的代码
注意:要先添加依赖,否则会引用不到该扩展函数

implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.2"
private val myViewModel by viewModels<MyViewModel>()

viewModels扩展函数其实就是返回了一个Lazy,有封装的getValue方法,相当于委托该属性由该扩展函数获取

7.infix函数

infix函数,其实就是用更接近语言的方式写的代码,相当于自定义了关键字,比如map里的 to

val map: MutableMap<String, Any> = mutableMapOf(
    "name" to "Jack",
    "age" to 66, 
    "address" to "北京",
)

to不是关键字,但是可以已这种语法写代码。例如,自定义一个字符串检测函数

infix fun String.begin(pre: String): Boolean = this.startsWith(pre)

调用

"你好".begin("你") // 普通扩展函数的用法
"你好" begin "你"  // 类似to的语法

限制条件:infix函数必须是某个类的成员函数,可以定义成扩展类,同时必须只有一个参数。

8.泛型实化、协变、逆变

8.1 泛型实化

泛型擦除 :泛型的约束只在编译时期,运行的时候就擦除其类型。比如Java中的List

private List<String> list = new ArrayList<>();
public void test(){
    list.add("你好");
    list.add(1); // 报错,不可以添加字符串以外的类型
}

但是到运行时候,JVM不管是什么类型,只能识别出是个List。因此,在代码里也无法使用如下的代码

fun <T> test() {
    T::class.java // 报红
}

因为在运行的时候是不知道泛型的具体类型的,而Kotlin因为有内联函数inline的存在,会把代码在编译时期自动替换到调用的地方,所以类型还是可以直接确定的。即在inline函数里,泛型可以使用上述的语句,但要加上reified关键字表示该泛型要实化。
修改代码

inline fun <reified T> test() {
    T::class.java // 无问题
}

泛型实化的实际应用
最常见的就是Activity的跳转,直接写一各Context的扩展函数,注意Intent的实例化方式,使用了T::class.java

inline fun <reified T> Context.start(vararg params: Pair<String, Any?> = arrayOf(), flag: Int = -1) {
    val intent = Intent(this, T::class.java).apply {
        if (flag != -1) {
            this.addFlags(flag)
        }
        if (params.isNotEmpty()){ // 有参数
            val bundle: Array<Pair<String, Any?>?> = arrayOfNulls(params.size)
            for (i in params.indices){
                bundle[i] = params[i]
            }
            putExtras(bundle.toBundle()) // 是一个array的扩展函数,直接把array转换成budle
        }
    }
    startActivity(intent)
}

调用

start("name" to "jack", "age" to 10)

TestActivity中获取参数

intent.extras?.let {
    val name = it.getString("name", "")
	val age = it.getInt("age", 1)
}

8.2 协变和逆变

协变
先看一些代码

//定义Person类,Student和Teacher继承该类
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)

//定义一个泛型类
class TestData<T> {
    private var data: T? = null
    fun set(t: T?) { // 可以设置值
        data = t
    }

    fun get(): T? = data
}

class Test {

    fun test() {
        val student = Student("jack", 18)
        val data = TestData<Student>() // 实例化一个指定泛型是Student的类
        data.set(student)
        handleData(data) // 报错,因为需求的是TestData,传入的是TestData
    }

    fun handleData(data: TestData<Person>) {
        val teacher = Teacher("tom", 20)
        data.set(teacher)
    }
}

报错原因,提示是需求的是TestData,传入的是TestData,因为如果传递了,在handleData里可能会把数据更换为其他的,比如Student换为Teacher,类型不统一,无法替换。所以编译是不通过的。即虽然StudentPerson的子类,但是TestData不是TestData的子类。当做不同的参数类型,所以无法传递。其实是因为在handleData里对参数进行了set操作,因为不是一个类型,类型转换错误,所以无法编译通过,但是如果我只是get数据,即该类型是只读,本质上仍旧是可以传递的。

关键字处理:协变-只读-out

修改代码

//定义Person类,Student和Teacher继承该类
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)

//定义一个泛型类
class TestData<out T> {
    private var data: T? = null
//    fun set(t: T?) {
//        data = t
//    }

    fun get(): T? = data
}

class Test {

    fun test() {
        val student = Student("jack", 18)
        val data = TestData<Student>() // 实例化一个指定泛型是Student的类
//        data.set(student)
        handleData(data) // 报错,因为需求的是TestData,传入的是TestData
    }

    fun handleData(data: TestData<Person>) {
        //这里只能读取
//        val teacher = Teacher("tom", 20)
//        data.set(teacher)
    }
}

这次是可以编译通过的,可以看到泛型前加了out关键字,表示他只读。但是只读了,要怎么赋值呢?可以放到构造函数里。

//定义一个泛型类
class TestData<out T>(val data: T) {
    fun get(): T? = data
}

如果改成var则报错,因为val是声明后不可更改的,var的话可能更改,就不符合只读out,不过可以把var声明成private,则可正常,因为外界改不了,就还是只读out
把最开始的例子里handleData(data: TestData)方法换为handleData(data: List),则Java不可以,照样报错,Kotlin则可以正常通过,因为Kotlin里有好多内置的支持协变的数据结构,比如List源码就可以看到

public interface List<out E> : Collection<E>{
	override val size: Int
	override fun isEmpty(): Boolean
	override fun contains(element: @UnsafeVariance E): Boolean
	override fun iterator(): Iterator<E>
	...
}

直接就是out只读,但是会发现contains函数里,还是传参修改了该泛型类型,因为该方法只是判断是否包含某个元素,并没有赋值改变,虽然出现在in的位置,表示可能会改变,和只读out冲突,但是明确函数里没有改变的代码,可以加个@UnsafeVariance,注解,表示编译时期不再检查处理,一切由自己承担。
可以总结定义

协变:  定义MyClass 泛型类,若A是B子类,同时MyClass也是MyClass子类,则MyClass在T类型上协变。
逆变:  定义MyClass 泛型类,若A是B子类,同时MyClass也是MyClass子类,则MyClass在T类型上逆变。

逆变则只能set值,不可以读取get
逆变应用例子Comparable

public interface Comparable<in T> {
    public operator fun compareTo(other: T): Int
}

逆变 关键字in,表示只读,即Student是Person子类,Comparable也是Comparable子类。因为谁是谁子类 对比较的逻辑无关,可以设置成逆变。

9.协程

9.1 开启协程

线程需要依赖操作系统调度,但是协程是依赖于线程。即协程允许在单线程模式下模拟多线程效果。

注意:并不是多线程,还是单个显示中模拟并发场景,例如Android要求网络请求在子线程,就不能在主线程开启协程后执行,必须到子线程开启执行。
创建协程
GlobalScope创建的是顶层协程,程序退出才会取消。一般不使用。

GlobalScope.launch {
    logDebug("协程工作开始")
}

RunBlocking创建的协程作用域,可以保证在作用域内代码执行完前阻塞当前线程

runBlocking { 
    logDebug("创建协程")
}

RunBlocking只在测试环境用,因为会挂起外部线程,如果在主线程使用,则可能会ANR
创建多个协程,只要在协程作用域中调用launch

runBlocking { // 外层作用域结束,子协程也会结束
    launch {
        logDebug("协程1开始,当前线程是 ${Thread.currentThread().name}")
    }
    launch {
        logDebug("协程2开始,当前线程是 ${Thread.currentThread().name}")
    }
}

结果可以看到,是在同一线程,但是并发执行

15:18:56.424 协程1开始,当前线程是 main
15:18:56.425 协程2开始,当前线程是 main

launch函数中如果调用其他函数,则其他函数需要被声明成挂起函数suspend 挂起函数中是没有作用域的,不能直接新开启协程,但是可以调用其他挂起函数。可以用coroutineScope 新开启协程作用域,coroutineScope 其实是个挂起函数,会继承外部的协程作用域并创建子协程,同runBlocking,外部的协程会一直挂起,但只会阻塞当前协程,不影响线程。

fun test() {
    runBlocking { // 外层作用域结束,子协程也会结束
        launch {
            logDebug("协程1开始")
            count()
        }
    }
}
suspend fun count() {
    logDebug("倒计时")
    coroutineScope { // 会继承作用域且创建子协程
    }
}

9.2 取消协程

val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
    logDebug("协程1开始")
}
scope.launch {
    logDebug("协程2开始")
}
job.cancel()

这样取消的时候,只用调用一次cancel即可

9.3 挂起协程

有时候需要协程返回的结果,而不是job,则可以使用async,会返回一个Deferred对象,然后调用其await(),该函数会等待执行结果后才会继续执行。

val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
    logDebug("scope launch开始")
    val result1 = scope.async {
        delay(1000)
        10 * 10
    }.await()
    val result2 = scope.async {
        delay(1000)
        10 * 10
    }.await()
    logDebug("结果是 result2+result2 = ${result1 + result2}")
}

结果

15:42:43.794 scope launch开始
15:42:45.805 结果是 result2+result2 = 200

因为await会挂起协程,所以相当于是串行执行。可以修改代码

val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
    logDebug("scope launch开始")
    val d1 = scope.async {
        delay(1000)
        10 * 10
    }
    val d2 = scope.async {
        delay(1000)
        10 * 10
    }
    logDebug("结果是 result2+result2 = ${d1.await() + d2.await()}")
}

withContext函数,强制要求指定一个线程参数,先看代码

scope.launch {
    logDebug("scope launch开始")
    val result = withContext(Dispatchers.IO) { // withContext函数要指定线程参数
        10 * 10
    }
    logDebug("结果是 result = $result")
}

withContext相当于ayanc+await的逻辑。给协程指定的线程参数有

Dispatchers.Default  默认低并发
Dispatchers.IO       较高并发,开启子线程
Dispatchers.Main     主线程

10.DSL(Domain Specific Language)

领域特定语言,可以构建一些专有的语法结构。
先写个例子,类似Gradle里添加依赖,并且对比下,DSL写法和普通调用方法的差别。

class Dependency {
    val libraries = ArrayList<String>()
    fun implementation(lib: String) {
        libraries.add(lib)
    }

    fun dependencies(block: Dependency.() -> Unit): List<String> {
        val dependency = Dependency()
        dependency.block()
        return dependency.libraries
    }

	//DSK方式调用
    fun test1() {
        val result = dependencies {
            implementation("测试添加依赖")
        }
        logDebug("result = $result")
    }

	//普通方式调用
    fun test2() {
        val d = Dependency()
        d.implementation("测试添加依赖")
        val result = d.libraries
        logDebug("result = $result")
    }
}

测试

val d = Dependency()
d.test1()
val d2 = Dependency()
d2.test2()

结果是相同的

result = [测试添加依赖]
result = [测试添加依赖]

再一个动态制作HTML表格的例子

class Td(var content: String = "") {
    fun html() = "\n\t\t$content"
}

class Tr {
    val children = mutableListOf<Td>()

    fun td(block: Td.() -> String) {
        val td = Td()
        td.content = td.block()
        children.add(td)
    }

    fun html(): String {
        val builder = StringBuilder()
        builder.append("\n\t")
        for (childTag in children) {
            builder.append(childTag.html())
        }
        builder.append("\n\t")
        return builder.toString()
    }
}

class Table {
    val children = mutableListOf<Tr>()

    fun tr(block: Tr.() -> Unit) {
        val tr = Tr()
        tr.block()
        children.add(tr)
    }

    fun html(): String {
        val builder = StringBuilder()
        builder.append("")for(childTag in children){
            builder.append(childTag.html())}
        builder.append("\n
"
) return builder.toString() } }

调用

fun test1() {
    val result = table {
        tr {
            td { "1" }
            td { "2" }
            td { "3" }
        }
        tr {
            td { "4" }
            td { "5" }
            td { "6" }
        }
    }
    logDebug(result)
}
fun test2() {
    val table = Table()
    val tr1 = Tr()
    val td1 = Td("1")
    val td2 = Td("2")
    val td3 = Td("3")
    tr1.children.add(td1)
    tr1.children.add(td2)
    tr1.children.add(td3)
    val tr2 = Tr()
    val td4 = Td("4")
    val td5 = Td("5")
    val td6 = Td("6")
    tr2.children.add(td4)
    tr2.children.add(td5)
    tr2.children.add(td6)
    table.children.add(tr1)
    table.children.add(tr2)
    val result = table.html()
    logDebug(result)
}

结果是一样的

其他(小知识点)

1.判断延迟初始化的变量是否初始化

if(!::a.isInitialized){ // 如果还没有初始化
}

2.密封类,可以省去when语句最后没必要的else分支
注意:密封类及其子类 必须在同一个文件中

sealed class A
class B : A() 
class C : A()

//调用when的时候
when(a:A){
	is B -> "B"
	is C -> "C" // 不用再写else
}

3.运算符重载
关键字operator,部分函数(plus()、minus()等)前加上operator关键字,即可使对象使用+/-操作符。

class Student(val grade: Int) {
    //两个学生对象相加,返回一个新的对象
    operator fun plus(student: Student): Student {
        return Student(grade + student.grade)
    }
    //常用的函数
	//plus()加、minus()减、times()乘、div()除
}

常用例子,字符串重复n次

//可以直接用字符串*次数 eg:你好*8
operator fun String.times(n: Int): String {
    val builder = StringBuilder()
    repeat(n) { builder.append(this) }
    return builder.toString()
}
//可以再简化
// 这个repeat是字符串内置的重复次数方法
operator fun String.times(n: Int): String = repeat(n) 

调用方式

"重复这段话3次".times(3) //正常调用
//或者
"重复这段话3次" * 3 // 操作符调用

你可能感兴趣的:(Android,kotlin,学习,android)