kotlin笔记

基础

  1. 变量 & 常量:
    • 可变性: var 可变; val 不可变
    • 编译期常量: val定义的是运行时常量, 如果定义编译器常量, 需要添加 const
    • 变量的声明和赋值
      var p0 = 1 // 类型推断
      var p1: Int = 2 // 指明类型
  2. 基本类型

    • 数字(Number): (Kotlin中的数字没有隐式拓宽, 比如 Int 不能自动转换为 Long)
      • 类型
        • Byte 8 bit
        • Short 16bit
        • Int 32bit
        • Long 64bit 整数默认是 Int, 可以使用 L 表示 Long 123L
        • Float 32bit 浮点数默认为 Double, 可以使用 f/F表示 Float 1.1f
        • Double 64bit
      • 进制
        • 二进制: 0b00001011
        • 八进制: 不支持
        • 十进制: 123
        • 十六进制: 0x0F
      • 数字下划线: 提高数字常量的易读性
        val a = 1_000_000;
        val b = 123_456L
        var c = 0xFF_EC_DE
        var d = 0b11010010_01101001
      • 显示转换:
        • 显示转换: toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()、toChar()、、、、、
          val b: Byte = 1
          val i: Int = b.toInt()
        • 计算时自动转换
          val l = 1L + 3 // Long + Int => Long
    • 字符(Char):
      • 字符字面量用 单引号 表示 val c: Char = ‘a’
      • 特殊字符可以使用 \ 转义: \t、\n、\b、\r、\、\$、…
      • 字符不能直接作为数字使用, 但是可以通过方法显示的转为 Int
        fun decimalDigit(c: Char): Int{
        if(c !in '0'..'9')
            throw IllegalArgumentException("out of range")
        return c.toInt() - '0'.toInt
        
        }
    • 布尔(Boolean)
      • 两个取值: true, false
      • 内置的布尔运算符: ||, &&, !
    • 数组(Array)
      • 定义数组的方式
        var arr0 = arrayOfNulls() // 创建一个指定大小、元素都为空的数组
        var arr1 = arrayOf(1, 2, 3) // [1, 2, 3]
        val arr2 = Array(5, { i -> (i * i).toString() }) // 创建一个 Array 初始化为 [“0”, “1”, “4”, “9”, “16”]
      • 通过 [] 访问(取值 和 赋值)数组元素 (运算符重载, 实际调用 get set 方法)
      • 无装箱开销的原生类型数组: ByteArray、 ShortArray、IntArray …
        • 这些类和 Array 并没有继承关系,但是 它们有同样的方法属性集。它们也都有相应的工厂方法:
          val x: IntArray = intArrayOf(1, 2, 3)
          x[0] = x[1] + x[2]
    • 字符串(String)

      • 字符串的不可变性
      • 转义字符串: 用 “ “ 表示, 其内部可以使用 转义字符
        val s0: String = “Hello \n World”
      • 原始字符串: 用 “”” “”” 表示, 其内部可以包含换行和任意字符(都表示字面量), 不识别 转义字符
        val s1: String = “””

           for (c in "foo")
             print(c)
        """
        

        PS: 因为原始字符串内部任意字符($除外)都表示字面量, 所以其每一行前面可能都会带有空格, 可以使用 trimMargin() 去除每一行前面的空格

        - trimMargin() 默认参数 "|", 所以可以使用 | 作为每一行的前缀
            val text1 = """
                            |第一行
                            |第二行
                        """.trimMargin()
        - 也可以自定义前缀, 比如使用 >
            val text2 = """
                            >第一行
                            >第二行
                        """.trimMargin(">")
        
      • 字符串模板(转义字符串 和 原始字符串 都支持)
        val a = 1
        val s = “$a + $a = ${a + a}”
        PS: 因为原始字符串也支持 字符串模板, 但是不支持 转义符号, 所有如果要在原始字符串中表示 $ 字面量, 方式如下

        val price = """
                ${'$'}9.99
                """
        
      • 字符串 就是 一串字符
        • 用字符数组构建字符串:
          val s3: String = String(charArrayOf(‘a’,’b’,’c’))
        • 可以通过下标访问 字符串 中的 字符
          val c: Char = s3[0];
        • 可以使用 for 循环迭代字符串
          for (c in str) {
          println(c)
          
          }
  3. 同一性 & 相等性
    val a: Int = 1000;
    val a1: Int? = a;
    val a2: Int? = a;
    print(a1 === a2); // false, 可空引用会对变量装箱(不要和Java的装箱混淆), 不再相等
    print(a1 == a2); // true,
  4. 运算符
    • 基本运算符: + - * / % += -=
    • 比较符: == === != !== // === 比较地址, == 比较字面量
    • 逻辑运算符: || && !
    • 位运算(只用于 Int 和 Long)
      shl(bits) – 有符号左移 (Java 的 <<)
      shr(bits) – 有符号右移 (Java 的 >>)
      ushr(bits) – 无符号右移 (Java 的 >>>)
      and(bits) – 位与
      or(bits) – 位或
      xor(bits) – 位异或
      inv() – 位非
    • 自定义基本运算
      operator fun plus(xxx){xxx} // 要对某个类自定义运算符, 用 operator 修饰相关成员方法
      infix fun on(place: String){} // 可以通过 xxx on “aaa” 调用, 类似于运算符的效果, 其实等效于 xxx.on(“aaa”)
  5. 空类型 & 空安全
    • 可空类型
      var s: String? = null
    • 安全的调用
      val l1: Int? = s?.length // 如果 s 为 null , 则直接返回 null
      val l2: Int = s?.length ?: -1 // 如果 ?: 左侧表达式非空, 就返回其左侧表达式,否则返回右侧表达式。
      val l3: Int = s!!.length // 如果 s 为 null, 则抛出异常
    • 安全的类型转换
      val aInt: Int? = a as? Int // 如果转换失败, 则返回 null
    • 空条件
      data?.let{
      // 如果不为空执行该语句块
      
      }
      data?:let{
      // 当data为空时才会执行
      
      }
    • 可空类型的集合
      val nullableList: List = listOf(1, 2, null, 4)
      val intList: List = nullableList.filterNotNull() // 过滤非空元素
  6. 类型检查与类型转换
    • 智能类型转换: 检查类型后, 可以直接按照该类型使用
      if (obj is String) {
      print(obj.length)
      
      }
      if (obj !is String) { // 与 !(obj is String) 相同
      print("Not a String")
      
      } else {
      print(obj.length)
      
      }
    • 智能类型转换适用的情况
      • val 局部变量
      • val 属性(private 或 internal),或者该检查在声明属性的同一模块中执行.不适用于 open 的属性或者具有自定义 getter 的属性;
      • var 局部变量——如果变量在检查和使用之间没有修改、并且没有在会修改它的 lambda 中捕获;
    • 强制类型转换
      val x: String = y as String
      val x: String? = y as? String // 转换失败则返回 null
    • 源文件通常以包声明开头: 包名可不与文件路径一致; 源文件的内容都在包名所在空间(外部使用需要导包), 如果不声明包, 则文件内容都属于无名称的默认包
      package my.demo
    • 导入包
      import foo.Bar // 现在 Bar 可以不用限定符访问
      import foo.* // “foo”中的一切都可访问
      import bar.Bar as bBar // 名字冲突时, 使用别名 bBar 代表“bar.Bar”
    • import不仅限于导入类, 还可以导入: 顶层函数及属性、枚举常量、对象中声明的函数和属性
  7. 流程控制
    • 区间 (Ranges)
      for (i in 1..100) { … } // [1, 100]
      for (i in 1 until 100) { … } // [1, 100)
      for (x in 2..10 step 2) { … } // 步进
      for (x in 10 downTo 1) { … } // 倒序
      if (x in 1..10) { … } // 判断
    • 分支
      • if
        • 传统用法:
          var max = a
          if (a < b) max = b
        • 在 kotlin 中, if 可以作为表达式使用, 它每个分支的最后的表达式作为该分支的返回值
          val max = if (a > b) a else b
          val max = if (a > b) {
          print("Choose a")
          a
          
          } else {
          print("Choose b")
          b
          
          }
      • when: 增强版的 switch
        • 最简单的用法
          when (x) {
          1 -> print("x == 1")
          2 -> print("x == 2")
          else -> { // 注意这个块
              print("x is neither 1 nor 2")
          }
          
          }
        • 如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
          when (x) {
          0, 1 -> print("x == 0 or x == 1")
          else -> print("otherwise")
          
          }
        • 可以用任意表达式(而不只是常量)作为分支条件
          when (x) {
          parseInt(s) -> print("s encodes x")
          else -> print("s does not encode x")
          
          }
        • 也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:
          when (x) {
          in 1..10 -> print("x is in the range")
          in validNumbers -> print("x is valid")
          !in 10..20 -> print("x is outside the range")
          else -> print("none of the above")
          
          }
        • 检测一个值是(is)或者不是(!is)一个特定类型的值
          val hasPrefix = when(x) { // 和if一样, 也可以作为表达式使用, 每个分支的最后的表达式作为该分支的返回值
          is String -> x.startsWith("prefix")
          else -> false
          
          }
        • when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
          when {
          x.isOdd() -> print("x is odd")
          x.isEven() -> print("x is even")
          else -> print("x is funny")
          
          }
    • 异常
      • 普通用法
        • 抛异常:
          throw MyException(“Hi There!”)
        • 捕获异常
          try { }catch (e: SomeException) { }finally { }
      • 作为表达式使用, 每个分支中最后的表达式作为该分支的结果
        val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
    • 循环
      • for
        • 循环 数组 或 list 的索引
          for (index in list.indices) {
          println("item at $index is ${items[index]}")
          
          }
        • 遍历 索引 和 值
          for ((index, value) in array.withIndex()) {
          println("the element at $index is $value")
          
          }
        • 可以对任何提供迭代器(iterator)的对象进行遍历
          for (item in collection) print(item)
        • 遍历 Map 集合
          for ((k, v) in map) {
          print("$k -> $v")
          
          }
        • 遍历 区间 (如上)
      • while & do … while
        while (x > 0) {
        x--
        
        }
        do {
        val y = retrieveData()
        
        } while (y != null) // y 在此处可见
  8. 返回与跳转: return、break、continue
    • 基本用法
      return 默认从最直接包围它的函数或者匿名函数返回
      break 终止最直接包围它的循环
      continue 继续下一次最直接包围它的循环
    • 使用标签来限制 break 和 continue
      loop@ for (i in 1..100) {
      for (j in 1..100) {
          if (……) break@loop
      }
      
      }
    • 使用标签来限制 reutrn : 函数可以嵌套使用, return会从最直接包围它的函数返回, 但是会忽略 lambda表达式, 可以通过标签使其从 lambda表达式返回
      fun foo() {
      ints.forEach lit@ {
          if (it == 0) return@lit
          print(it)
      }
      
      }
      fun foo() {
      ints.forEach {
          if (it == 0) return@forEach            // 隐式标签, 该标签与接受该 lambda 的函数同名
          print(it)
      }
      
      }
    • return 的标签语法也可以带有返回值
      return@a 1 // 从标签 @a 返回 1

    • 类的声明:
      • class关键字 + 类名 + 类头(主构造函数、参数) + 类体
        class Person public constructor(name: String){}
      • 如果没有类体, 可生路 {}:
        class Person public constructor(name: String)
      • 如果主构造函数没有 注解 或 权限修饰(默认 public), 可省略 constructor 关键字
        class Person()
      • 如果主构造函数没有参数, 类头也可以省略
        class Person
    • 构造函数
      • 构造函数中的参数可在 init 代码块中访问
        class Person public constructor(name: String){
        val age = 18                                 // 只读属性
        var xxx = "yyy"                              // 读写属性
        init{ print(name) }                          // 初始化代码块: 按照其在类中的顺序执行(包括属性初始化)
        
        }
      • 构造函数中的参数也可以在类属性的初始化器中使用
        class Person (name: String){
        val upperName : String = name.toUpperCase()
        
        }
      • 可以直接在主构造函数中声明 属性
        class Person (val name: String, val age: Int = 18, var xxx: String){ … }
      • 次级构造函数: 可在类体中通过 constructor 关键字 声明任意个次级构造函数
        class Person{
        constructor(name: String){
            print(name)
        }
        
        }
      • 如果存在主构造函数, 那么所有 次级构造函数必须 直接(或间接)委托给 主构造函数.
        class Person(val name: String, val age: Int){
        constructor(name: String) : this(name, 18) {
            print(name)
        }
        
        }
      • 实际上即使没有主构造函数, 次级构造函数任然隐式的存在这种委托关系
        class Person{
        init{
            print("这段代码会在次级构造函数体之前执行, 因为 init 代码块是主构造函数的一部分, 而委托代码是在次级构造函数的含数体之前")
        }
        constructor(name: String){
            print(name)
        }
        
        }
      • 如果没有声明任何构造函数(主和次), 默认会自动有一个 public 的无参主构造函数
    • 属性和字段

      • 声明属性的方式
        class A{
        // 可变属性: 初始化器、getter、setter都是可选的; 如果类型可推断(从初始化器或 getter返回值), 那么类型也可以省略
        var <propertyName> [: PropertyType] [= <property_initializer>]      
            [<getter>]                                                      // 就是一个函数 get() {}
            [<setter>]                                                      // set(value){}
        val <propertyName> [: PropertyType] [= <property_initializer>]      // 只读属性, 没有setter
            [<getter>]
        
        }
      • 改变访问器的可见性(或添加注解): 默认情况下 属性访问器的可见性和属性时一致的, 但是也可以单独指定
        var setterVisibility: String = “abc”
        private set                                   // 可以只添加修饰符, 而不改变其默认实现
        
        var setterWithAnnotation: Any? = null
        @Inject set
        
      • 幕后字段: 如果属性至少有一个访问器使用默认实现, 或者通过 field 引用幕后字段,将会为该属性生成一个幕后字段
        var counter = 0
        set(value){
            field = value
        }
        
      • 编译期常量: 使用 const 修饰, 位于顶层或是 object 的成员, 使用 String或原生类型初始化, 没有自定义 getter
      • 属性延迟初始化: 一般情况下属性必须通过 构造函数 或 初始化器 或 getter 初始化, 使用以下方式可以延迟初始化时机
        class Test{
        lateinit var a: String       // 方式一: 必须是可变属性, 必须是非空类型, 不能自定义 getter 和 setter
        var/val b by lazy{}          // 方式二:
        
        }
        -
    • 创建类的实例: 调用构造方法, 没有 new 关键字
      val p = Person()

  1. 继承
    • Any 是所有类的基类
    • 继承的写法
      • 如果基类有主构造函数, 则必须使用该主构造函数就地初始化
        class Derived(p: Int) : Base(p)
      • 如果基类没有主构造函数, 那么每个次构造函数必须使用 super 关键字初始化基类, 或者委托另一构造函数做到这一点
        class MyView : View {
        constructor(ctx: Context) : super(ctx)
        
        }
    • super 关键字
      • 调用基类的函数和属性访问器
        open class F{
        open fun test() { print("aaaaa") }
        open val x: Int get() = 1
        
        }
        class S: F(){
        override test(){
            super.test()
            print("bbbbbb")
        }
        override val x: Int get() = super.x + 1
        
        }
        -
      • 在内部类中, 通过外部类类名限定的 super, 访问外部类的超类
        class S: F(){
        ...
        inner class SI{
            fun test2(){
                super@S.test()
                print(super@S.x)
            }
        }
        
        }
  2. 修饰符号
    • open/final:
      • 类默认为 final, 如果希望被继承, 需要加上 open 修饰
      • 函数默认为 final, 如果希望被重写, 需加上 open 修饰, 且子类重写时必须加上 override修饰, 如果子类不希望再被重写, 加上 final
      • 属性和函数类似, 此外: val 可以重写为 var, var不能重写为 val
    • abstract: 可用于修饰 类 或 函数 (抽象类或函数肯定可以被继承和重写, 不需要再添加 open 修饰)
    • 可见性修饰符 (作用目标: 类、对象、接口、构造函数、方法、属性及其setter, getter可见性总是和属性本身一致)
      private(自己可见)、 protected(不适用顶层声明)、 internal(相同模块内可见) 和 public(缺省默认)
  3. 接口
    • 使用 interface 关键字, 多继承
    • 可以有抽象方法 和 实现方法(java8)
    • 可以有属性, 但必须为 抽象的 或者 提供访问器实现
      interface A{
      val a: Int
      val b: String
          get() = "bbb"                  // 接口中不支持 幕后字段
      
      }
    • 多继承与 覆盖冲突
      interface A{
      fun a(){}
      
      }
      interface B{
      fun a(){}
      
      }
      class C: A, B{
      override fun a() {                // 虽然 a 方法在父类中是实现方法, 但这里必须要重写, 且可以通过 super<父类名>选择性调用父类实现
          super<A>.a()
      }
      
      }
  4. 扩展
    • 定义扩展函数
      fun 类型名称.方法名(){ // 扩展函数体可通过 this 访问调用对象 }
    • 扩展函数式静态解析的: 并没有在目标类中插入新的成员, 仅仅表示可以通过该类型变量使用点语法调用函数
    • 如果扩展函数和成员方法冲突了, 那么成员方法总是优先的
    • 可空接受者
      fun Any?.toString(): String? = if(this == null){ null } else { this.toString() }
    • 扩展属性: 扩展属性不能使用幕后字段, 不能有初始化器, 只能有 访问器
      val List.lastIndex: Int
      get() = size - 1
      
    • 为伴生对象 定义扩展
      class A{
      companion object{}
      
      }
      fun A.Companion.test(){ … }
      A.test(); // 可直接通过类名访问
    • 定义扩展的位置
      • 一般情况下定义在顶层
      • 也可以在一个类(扩展分发者) 内部 为 另一个类(扩展接受者) 定义扩展
        • 在此扩展方法中可以同时访问 扩展分发者 以及 扩展接受者 的成员
        • 如果 扩展分发者 和 扩展接受者的成员名称冲突时, 默认访问的是 扩展分发者的成员, 但是也可以通过 this 的限定语法指定访问
          class A{
          fun a1(){}
          fun a2(){}
          
          }
          class B{
          fun a2(){}
          fun b(){}
          fun A.aa(){
              b();            // 调用了 B 的方法
              a1();           // 调用了 A 的方法
              a2();           // 调用了 B 的方法
              this@A.a2();     // 通过 this 的限定语法, 调用了 A 的方法
          }
          
          }
    • 扩展的作用: 代替 工具类
  5. 数据类
    • 定义数据类
      data class Uer(val name: String, val age: Int)
    • 数据类的要求
      • 必须有主构造函数, 且至少有一个参数, 且参数必须声明为属性(使用 var 或 val 标记)
      • 如果希望使用无参构造函数, 可以给属性设置默认值
      • 数据类不能是抽象、开放、密封或者内部的
    • 编译器会通过主构造函数为数据类自动生成以下函数 (没有收到声明, 且父类没有设置为 final)
      • equals()、 hashCode()
      • toString() // 格式是 “User(name=John, age=42)” …
      • componentN() // 按声明顺序对应于所有属性
      • copy() // 用于复制一个对象, 并只改变部分属性
        val jack = User(name = “Jack”, age = 1)
        val olderJack = jack.copy(age = 2)
    • 在类体中声明属性: 编译器自动生成的函数只会使用主构造函数中的属性, 而不会使用类体中声明的属性
    • 数据类的解构
      val jane = User(“Jane”, 35)
      val (name, age) = jane
  6. 内部类
    • 在类的内部定义的类
      class Outer{
      private val bar: Int = 1
      class Inner1{                // 表示静态内部类                     ->  val i1 = Outer.Inner2().foo()
          fun foo() = 2
      }
      inner class Inner2{          // 使用inner关键字标记, 表示非静态内部类 ->  val i2 = Outer().Inner2().foo()
          fun foo() = bar          // 可访问外部类成员, 并且可通过 this@Outer 引用外部类实例
      }
      
      }
    • 匿名内部类: 使用 对象表达式. // 如果是函数式java接口的实例, 可使用lambda chipLayout.setOnClickListener { v -> print(v?.id) }
      view.setOnClickListener(object: View.OnClickListener{
      override fun onClick(v: View?) { }
      
      })
  7. 密封类
    • 定义一个密封类
      sealed class XXX
    • 密封类的限制:
      • 不允许有非 private 的构造函数(其构造函数默认是 private), 其所有直接子类必须和其写在同一个文件中
      • 在使用 when表达式时, 如果各个分支条件可以覆盖所有情况, 那么就不需要 else 分支. 密封类可以满足这一点
  8. 枚举类
    • 最基本的用法: (每一个枚举常量都是对象)
      enum class Direction {
      NORTH, SOUTH, WEST, EAST
      
      }
    • 每一个枚举常量都是枚举类的实例
      enum class Color(val rgb: Int) {
      RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF)
      
      }
    • 枚举常量也可以声明自己的匿名类
      enum class ProtocolState {
      WAITING {
          override fun signal() = TALKING
      },
      TALKING {
          override fun signal() = WAITING
      };
      abstract fun signal(): ProtocolState
      
      }

对象

  1. 对象表达式 (匿名对象)
    • 使用 object 关键字表示匿名对象
      view.setOnClickListener(object: View.OnClickListener{
      override fun onClick(v: View?) { }
      
      })
    • 可以有多个超类型(逗号分隔), 如果超类型存在构造函数, 则必须传递参数
      val ab: A = object : A(1), B {
      // 重写方法 或 属性
      
      }
    • 甚至可以不指定超类型
      val adHoc = object {
      var x: Int = 0
      var y: Int = 0
      
      }
    • 使用匿名对象作为 共有函数 的返回值 或 参数时, 其实际类型是匿名对象的超类型(如果为指明, 则是 Any 类型)
    • 使用匿名对象作为 私有函数 的返回值 或 参数时, 其实际类型是匿名对象类型
  2. 对象声明 (不是表达式, 不能用在赋值语句的右边)
    • 使用 object 关键字 声明对象 (等效于 java 中的 饿汉式单例)
      object AppScope{
      fun saveData(key: String, value: String){ ... }
      
      }
    • 如何访问对象成员
      • kotlin中: AppScope.saveData(…)
      • java中: AppScope.INSTANCE.saveData(…)
    • 对象可以有超类型
      object MyLisener: MessageListener(){
      override fun ...
      
      }
  3. 伴生对象
    • 定义伴生对象: 类内部的object 用 companion 关键字标记, 一个类只能有一个伴生对象,
      class MyClass {
      companion object Factory {
          @JvmStatic   
          fun create(): MyClass = MyClass()
          @JvmField
          val TAG: String = "tag"
      }
      
      }
    • 访问伴生对象的成员
      • kotlin中: val instance = MyClass.create()
      • java中:
        • 如果没有添加 @JvmStatic 、@JvmField 注解: val instance = MyClass.Factory.create()
        • 如果添加了 @JvmStatic 、@JvmField 注解: val instance = MyClass.create()
    • 伴生对象的名称可以省略, 此时将使用默认的名称 Companion
      class MyClass {
      companion object {
          fun create(): MyClass = MyClass()
      }
      
      }
      MyClass.Companion.create()
    • 虽然访问伴生对象的成员类似 java中的静态成员, 但实际在运行时他们任然是真实对象的实例成员
    • 在 jvm平台, 如果使用了 @JvmStatic 、@JvmField 注解, 可以将伴生对象的成员生成为真正的静态成员
    • kotlin中使用静态成员时, 应考虑是否有必要, 是否用 包级函数/变量 替代
  4. 单例模式
    • 饿汉式:
      objcet APIHelper{}
    • 懒汉式:
      class SingletonDemo private constructor() {
      companion object {
          private var instance: SingletonDemo? = null
              get() {
                  if (field == null) {
                      field = SingletonDemo()
                  }
                  return field
              }
          // @Synchronized   添加该注解表示 同步
          fun get(): SingletonDemo{
              return instance!!
          }
      }
      
      }
    • 双重校验锁式
      class SingletonDemo private constructor() {
      companion object {
          val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SingletonDemo() }
      }
      
      }
    • 静态内部类式
      class SingletonDemo private constructor() {
      companion object {
          val instance = SingletonHolder.holder
      }
      private object SingletonHolder {
          val holder = SingletonDemo()
      }
      
      }
      • 带参 ?
        class SToast private constructor(val context: Context) : Toast(context) {
        private object Builder{
            var instance: SToast? = null
        }
        companion object{
            fun with(context: Context): SToast{
                Builder.instance?:let{
                    Builder.instance = SToast(context)
                }
                return Builder.instance!!
            }
        }
        
        }

代理(委托)

  1. kotlin中可以简单的实现代理模式 (静态代理)
    interface Animal{
    fun bark()
    
    }
    class Dog :Animal {
    override fun bark() {
        println("Wang Wang")
    }
    
    }
    class Cat(animal: Animal) : Animal by animal // 将 Cat 的所有公有成员都委托给指定的对象
    Cat(Dog()).bark() // 用 Dog 作为 Cat 的代理
  2. 委托属性
    • 属性委托的语法: val/var <属性名>: <类型> by <表达式>
      class Example {
      var p: String by Delegate()
      
      }
      class Delegate {
      // 必须有 getValue方法, 对应 getter,  参数: 属性的拥有者, 对属性的描述
      operator fun getValue(thisRef: Any?, property: KProperty<* >): String {
          return "$thisRef, thank you for delegating '${property.name}' to me!"
      }
      // var属性还要有 setValue方法, 对应 setter,  参数: 属性的拥有者, 对属性的描述, 属性的值
      operator fun setValue(thisRef: Any?, property: KProperty<* >, value: String) {
          println("$value has been assigned to '${property.name} in $thisRef.'")
      }
      
      }
    • kotlin为委托提供的几个工厂方法
      • 延迟属性 Lazy: lazy()接受一个 lambda表达式 并返回一个 Lazy实例(在第一次访问属性的getter时执行 lambda并返回结果)
        val propLazy: Int by lazy{1} // 默认是所有线程同步的
        val v: Int by lazy(LazyThreadSafetyMode.PUBLICATION, {1}) // 指定线程不安全
        val v: Int by lazy(LazyThreadSafetyMode.NONE, {1}) // 不会有任何线程安全的保证和相关的开销
      • 可观察属性 Observable: (接受两个参数: 初始值, 处理函数)
        • 在赋值之后执行
          var name: String by Delegates.observable(“初始值”) {
          // 被赋值的属性、旧值和新值
          prop, old, new ->
          println("$old -> $new")
          
          }
        • 在赋值之前执行(可以拦截)
          var name: String by Delegates.vetoable(“初始值”) {
          // 被赋值的属性、旧值和新值
          prop, old, new ->
          println("$old -> $new")
          false
          
          }
      • 把属性储存在映射中
        class User(val map: Map) {
        val name: String by map
        val age: Int     by map
        
        }
        val user = User(mapOf(
        "name" to "John Doe",
        "age"  to 25
        
        ))
        // var 属性
        class MutableUser(val map: MutableMap) {
        var name: String by map
        var age: Int     by map
        
        }

函数

  1. 定义一个函数
    fun double(x: Int): Int { return 2 * x }
  2. 关于参数
    • 参数的默认值: (可以减少重载数量, 注意: 重写带默认值的函数时, 需要省略默认值))
      fun read(b: Array, off: Int = 0, len: Int = b.size()) {}
    • 命名参数: 调用函数时, 可以显示的指定名称传参
      fun foo(p1: Int = 0, p2: Int){ … }
      foo(p2 = 10)
    • 可变参: 使用关键字 vararg (可变参可以不是最后一个, 但是对于其后面的参数, 需要使用 命名参数 的语法传参)
      fun asList(vararg ts: T): List {
      val result = ArrayList<T>()
      for (t in ts) // ts is an Array
          result.add(t)
      return result
      
      }
      // 对于可变参可以一个一个的传, 但如果已经有了一个数组, 可以使用 伸展符号( )
      val arr = arrayOf(1, 2, 3)
      val list = asList(-1, 0,
      arr, 4)
    • 泛型参数
      fun singletonList(item: T): List {}
  3. 关于返回值
    • 无返回值: 如果一个函数没有返回值, 那么其返回类型应该是 Unit, 或者直接省略返回类型
    • 单表达式函数: 当函数返回单个表达式时, 可省略 {}, 并使用 = 表示返回值
      fun double(x: Int): Int = x 2
      fun double(x: Int) = x
      2 // 当返回类型可以推断时, 可省略返回类型
  4. 中缀语法:
    • 使用 infix 关键字标识函数
      infix fun Int.sh1(x: Int): Int{ …}
      1 sh1 2 // 使用中缀语法调用函数, 等效于 1.sh1(2)
    • 中缀函数必须满足一下条件
      • 必须是成员函数 或 扩展函数
      • 必须且只有一个参数
      • 参数不能是 可变参, 且不能有默认值
  5. 函数的作用域
    • 成员函数
    • 顶层函数: kotlin中的函数可以直接声明在 顶层
    • 局部函数: kotlin中的函数可以声明在另一个函数内部 (闭包: 内部函数可访问外部函数中的变量)
  6. 尾递归函数用: 使用 tailrec 关键字标记, 并满足要求(递归调用必须是最后一步,并且不能用在 try/catch/finally 块中), 只有后端支持
    tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

高阶函数 & Lambda

  1. 函数类型
    • 如何声明函数类型
      val click : (View) -> Unit = …
    • 函数类型的一些说明
      • 如果没有返回值, Unit 也不能省略
      • 如何表示可空类型: val a: ((Int) -> String)? = …
      • 可选择性的设置参数名(提高可读性): val click : (view: View) -> Unit = …
      • 函数类型的取值
        • 使用匿名函数:
          val click : (View) -> Unit = fun (v: View){ print(v.id) }
        • 使用Lambda表达式:
          val click : (View) -> Unit = { v -> print(v.id) }
        • 使用已有函数的引用
          fun test(view: View){ print(view.id) }
          val click : (View) -> Unit = ::test
        • 使用函数类型的实现类的实例
          class Test : (View) -> Unit{
          override fun invoke(view: View) {
              print(view.id)
          }
          
          }
          val click : (View) -> Unit = Test()
      • 如果信息足够推断函数类型时, 可省略类型
        val plus = {i : Int -> i + 1}
      • 调用函数类型的方式
        • 方式一: 直接调用
          var result = plus(3)
        • 方式二: 使用 invoke
          var result = plus.invoke(3)
      • 带接受者的函数类型
        • 定义带接受者的函数类型(类似 扩展函数)
          val myConcat: String.(String) -> String = { string -> this + string } // this表示该函数的调用者
          val result = “abc”.myConcat(“efg”) // 用 “abc” 调用该函数, 等价于 myConcat(“abc”, “efg”)
        • 函数类型的接受者, 可以相互转化为其参数列表的第一个参数
          fun a(str1: String, str2: String) = str1 + str2
          val myConcat: String.(String) -> String = ::a
  2. Lambda表达式
    • 例子
      val map = mapOf(“key1” to “value1”, “key2” to “values”)
      map.forEach({key: String, value: String ->
      print("$key & $value")
      
      })
    • Lambda表达式写在 {} 中, 参数列表 与 函数体 之间用 -> 分开
    • 参数类型可省略
      map.forEach({key, value ->
      print("$key & $value")
      
      })
    • 对于没有使用的参数, 可以用 表示
      map.forEach({
      , value ->
      print(value)
      
      })
    • 如果Lambda表达式的参数列表只有一个参数, 那么可以省略参数列表 和 ->, 在函数体中如果要使用该参数, 可以用 it代替
      val list = listOf(1,2,3)
      list.forEach({ print(it) })
    • 如果Lambda表达式需要有返回值, 那么函数体中的最后一个表达式会作为其返回值
      val newList = list.map({ 2 it }) // 不要使用 return, 除非带上限定符号, 比如 return@map
      // val newList = list.map({ return@map 2
      it })
    • 如果 Lambda 是另一个函数的最后一个参数, 那么可以写在 () 之外
      val newList = list.map() { 2 * it }
    • 如果 Lambda 是另一个函数的唯一个参数, 那么 () 可省略
      val newList = list.map { 2 * it }
  3. 高阶函数: 使用函数作为 参数 或 返回值
  4. 内联函数
    • 使用高阶函数会造成额外的消耗(函数也是对象, 需要单独开辟空间, 寻址..), 使用内联函数可以解决这一问题
      inline fun lock(lock: Lock, body: () -> T): T {}
    • inline会影响函数本身和作为参数的 lambda表达式. 内联会导致生成的代码量增加, 可以通过 noinline 标识哪些参数不进行内联
      inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
    • lambda表达式中不允许使用单独的 return; 但是在内联情况下, 可以直接 return, 标识从包裹该lambda表达式最近的函数返回
    • 内联属性: inline 也可以用来修饰 没有幕后字段 的属性
      • 作用于访问器
        val foo: Foo
        inline get() = Foo()
        
        var bar: Bar
        get() = ……
        inline set(v) { …… }
        
      • 直接作用于整个属性, 表示所有访问器都标记为内联
        inline var bar: Bar
        get() = ……
        set(v) { …… }
        

其它

  1. 类型别名
    typealias NodeSet = Set // 为泛型起别名
    typealias AInner = A.Inner // 为内部类起别名
    typealias MyHandler = (Int, String, Any) -> Unit // 为函数起别名 (可以简化函数类型的书写)
    typealias Predicate = (T) -> Boolean
  2. 集合
    • 不可变集合: List . Set . Map
      val list: List = listOf(1,2,3)
      val set = setOf(‘a’,’b’)
      val map = mapOf(“key1” to 1, “key2” to 2)
    • 可变集合: MutableList . MutableSet . MutableMap
      val list: MutableList = mutableListOf(1,2,3)
      val set = mutableSetOf(‘a’,’b’)
      val map = mutableMapOf(“key1” to 1, “key2” to 2)
    • 访问 map 集合
      println(map[“key”])
      map[“key”] = value
    • 集合的复制以及可变性变化
      toList() toMutableList() …
  3. 解构
    • 对象解构
      val (name, age) = person // 对于不需要用到的变量, 可以使用 代替 val (name, ) = person
    • 在 lambda 表达式中解构
      • 示例
        map.mapValues { entry -> “${entry.value}!” }
        map.mapValues { (key, value) -> “$value!” }
      • 声明 两个参数 和 声明一个 解构来取代单个参数之间的区别:
        { a -> …… } // 一个参数
        { a, b -> …… } // 两个参数
        { (a, b) -> …… } // 一个解构
        { (a, b), c -> …… } // 一个解构对以及其他参数
      • 解构中未被使用的变量可以使用 代替
        map.mapValues { (
        , value) -> “$value!” }
      • 可以指定整个解构的参数的类型 或者 分别指定特定组件的类型:
        map.mapValues { ( , value): Map.Entry -> “$value!” }
        map.mapValues { (
        , value: String) -> “$value!” }
  4. 利用 with 调用一个对象实例的多个方法
    class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
    
    }
    with(Turtle()) {
    penDown()
    for(i in 1..4) {
    forward(100.0)
        turn(90.0)
    }
    penUp()
    
    }
  5. 泛型
    • 类泛型
      class Box(t: T) {
      var value = t
      
      }
      val box: Box = Box(1) // 如果泛型可以推断, 那么可以省略 val box = Box(1)
    • 函数泛型
      fun singletonList(item: T): List {} // 普通函数泛型
      fun T.basicToString() : String {} // 扩展函数泛型
      val l = singletonList(1) // 调用泛型函数需要在 <> 中指明类型
    • 泛型变异
      • Java中是在使用处型变
        • 协变: <? extends T> 可以使用父类型的地方, 就可以接受子类型; 只能取值, 不能写入, 称为 生产者
          public class Utils{
          public static void test(List<? extends View> list){
              // 只能从 list 中取值, 而不能写入
              View view = list.get(0);                 // 可以通过编译
              // list.add(new TextView(context));      // 不能通过编译
          }
          
          }
          List imgs = new ArrayList<>();
          Utils.test(imgs);
        • 逆变 <? super T> 可以使用子类型的地方, 就可以接受父类型; 可以写入, 取值都是 Object, 称为 消费者
          public class Utils{
          public static void test(List<? super ImageView> list){
              // 可以往 list 中写入, 但取值都是 Object
              list.add(new ImageView(context));
              Object object = list.get(0);
          }
          
          }
          List views = new ArrayList<>();
          Utils.test(views);
      • Kotlin中是在声明处型变
        • out: 类似 协变, 表示泛型只能被生产, 不能被消费(就是只能作为返回值, 不能作为参数)
          class Test{
          fun test(): T { ... }
          
          }
          val t: Test = Test() // 这是合法的, 因为泛型只能被读取, 不能写入 (安全)
        • in: 类似 逆变, 表示泛型只能被消费, 不能被生产 (就是只能作为参数, 不能作为返回值)
          class Test{
          fun test(t: T) { ... }
          
          }
          val t: Test = Test() // 这是合法的, 因为泛型只能被写入, 不能读取 (安全)
    • 泛型约束
      fun test(t: T){ } // 泛型约束为 Number子类及其子类
      test(1) // 可以通过编译
      test(“a”) // 不能通过编译
      fun test(t: T) where T : A, T: B{ } // 泛型约束为: 必须是 A的子类 也是 B的子类
  6. 注解
    • 元注解
      @Target 指定注解可作用的地方(类, 函数, 属性, 表达式)
      @Retention 指定注解被保留的时间长短(AnnotationRetention.SOURCE, ..CLASS, ..RUNTIME)
      @Repeatable 允许 在单个元素上多次使用相同的该注解
      @MustBeDocumented 指定 该注解是公有 API 的一部分,并且应该包含在 生成的 API 文档中显示的类或方法的签名中。
    • 利用元注解 自定义注解
      @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
      AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
      
      @Retention(AnnotationRetention.SOURCE)
      @MustBeDocumented
      annotation class Fancy
    • 注解可以有接受参数的构造函数
      annotation class Special(val why: String)
    • 使用注解
      @Special(“example”) class Foo {}
  7. 反射
    • 类引用
      • 通过类名获取
        val c = MyClass::class // 该引用是KClass类型, 要获得 Java 类引用, 需要在 KClass 实例上使用 .java 属性。
      • 通过对象获取
        val widget: Widget = ……
        widget::class
    • 函数的引用 (用于高阶函数, lamada 表达式也可以实现类似功能)
      • 获取函数的引用
        fun isOdd(x: Int) = x % 2 != 0
        listOf(1, 2, 3).filter(::isOdd)
      • 函数的引用可以存储到变量中
        val predicate: (Int) -> Boolean = ::isOdd
      • 在外部引用 类的成员函数(或构造函数) 需要指定接受者
        val isListEmpty: List.() -> Boolean = List::isEmpty // 函数类型一定要指明接受者, 因为无法自动推断出来
        val list = listOf(“1”)
        print(“isEmpty: ${isListEmpty(list)}”) // 可将调用者作为第一个参数传入
        print(“isEmpty: ${list.isListEmpty()}”) // 和上面是等价的
      • 引用需要接受者的函数时, 使用 类名 和 类实例 都可以
        class Test{
        fun double (p: Int) = 2 * p
        
        }
        val test = Test()
        val d1: Test.(Int) -> Int = Test::double // 使用类名限定
        val d2: (Int) -> Int = test::double // 使用类实例限定
        val result1 = d1(test, 3) // 需要将调用者作为第一个参数, 或者直接 test.d1(3)
        val result2 = d2(3) // 无需再设置调用者
      • 引用特定对象的函数
        val numberRegex = “\d+”.toRegex()
        println(numberRegex.matches(“29”)) // 输出“true”
        val isNumber = numberRegex::matches
        println(isNumber(“29”)) // 输出“true”
      • 构造函数引用
        ::类名 // 表示 零参数构造函数 的引用
    • 属性引用
      • 获取属性的引用
        var x = 1
        ::x.name // 获取属性名
        ::x.get() // 获取值
        ::x.set(3) // 对于 var 属性, 还可以赋值
      • 在外部引用类的属性
        class A(var p: Int)
        val a = A(3)
        A::p.set(a, 5)
        val value = A::p.get(a)
      • 引用类的扩展属性
        val String.lastChar: Char
        get() = this[length - 1]
        
        val str = “abc”
        val result = String::lastChar.get(str)
      • 引用特定对象的属性
        val prop = “abc”::length
        val l = prop.get()

协程

1.

  1. 通过 launch、 runBlocking 开启协程
    • 一个关于协程的小案例
      override fun onCreate(savedInstanceState: Bundle?){
      setContentView(R.layout.activity_main)
      launch(CommonPool) {                    // 在后台启动一个协程
          delay(1000L)                        // 延迟1s,  delay是一个特殊的 suspend fun, 不会锁死线程, 只会挂起协程, 只能在协程内使用
          Log.e(">>>", "World: @ ${Thread.currentThread().name}")
      }
      Log.e(">>>", "Hello: @ ${Thread.currentThread().name}")
      runBlocking{                            // 在当前线程启动协程, 会阻塞当前线程, 直到协程内部的代码运行完毕
          delay(1000L)  
      }
      
      }
    • 直接用 runBlocking 包裹整个函数 (泛型是指当前函数的返回值类型, 如果需要返回值, runBlocking 代码块的最后一个表达式会作为返回值)
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      launch(CommonPool) {
          delay(2000L)
          Log.e(">>>", "World: @ ${Thread.currentThread().name}")
      }
      Log.e(">>>", "Hello: @ ${Thread.currentThread().name}")
      delay(2100L)
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
    • 等待协程运行完毕
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val job: Job = launch(CommonPool) {                                  // 启动新的协程, 并通过变量保留其引用
          delay(2000L)
          Log.e(">>>", "World: @ ${Thread.currentThread().name}")
      }
      Log.e(">>>", "Hello: @ ${Thread.currentThread().name}")
      job.join()                                                           // 等待子协程结束后才会执行后面的代码
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
  2. 协程的取消
    • 通过 lauch返回的 job, 调用 cancel() 可以取消协程
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val job: Job = launch(CommonPool) {
          repeat(100){ i ->
              Log.e(">>>", "$i @ ${Thread.currentThread().name}")
              delay(1000L)
          }
      }
      delay(3100L)
      job.cancel()                                // 取消协程
      job.join()
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
    • 如果协程一直在运行, 那么它就不会去检查取消标志, 结构就无法取消成功. 解决方式是 在代码中主动去检查取消标志
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      var time = System.currentTimeMillis()
      val job: Job = launch(CommonPool) {
          var i = 0
          while (isActive && i < 1000){                  // isActive是协程内部的一个属性,可以检查该协程是否被取消
              if(System.currentTimeMillis() >= time){
                  Log.e(">>>", "${i++} @ ${Thread.currentThread().name}")
                  time += 1000
              }
          }
      }
      delay(3100L)
      job.cancel()
      job.join()
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
    • 协程取消时时候回抛出 CancellationException, 可以通过 try {} finally {} 处理
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val job: Job = launch(CommonPool) {
          try {
              repeat(1000){ i ->
                  Log.e(">>>", "$i @ ${Thread.currentThread().name}")
                  delay(500)
              }
          }catch (e: Exception){                        // catch不是必须的, 这里只是验证确实抛出了异常
              Log.e(">>>", e.toString())
          }fianlly{ ... }
      }
      delay(3100L)
      job.cancel()
      job.join()
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
    • 超时自动取消 (withTimeout): 在当前线程启动协程, 如果协程内的代码在指定时间内未执行完毕, 就抛出异常 TimeoutException
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val result = withTimeout(3000){                                             
          repeat(10){ i ->
              Log.e(">>>", "$i: @ ${Thread.currentThread().name}")
              delay(1000)
          }
          "最后一行表达式就是结果"
      }
      Log.e(">>>", "结果等于: $result")
      
      }
    • 安全的超时自动取消(withTimeoutOrNull): 超时后不会抛出异常, 只是返回结果是 null
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val result = withTimeoutOrNull(3000){
          repeat(1){ i ->
              Log.e(">>>", "$i: @ ${Thread.currentThread().name}")
              delay(1000)
          }
          "最后一行表达式就是结果"
      }
      Log.e(">>>", "结果等于: $result")
      
      }
  3. suspend fun : 由 suspend 修饰的方法, 和普通方法结构完全一样
    • suspend fun 只能在另一个 suspend fun 中调用
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val job: Job = launch(CommonPool) { test() }
      Log.e(">>>", "Hello: @ ${Thread.currentThread().name}")
      job.join()
      Log.e(">>>", "over: @ ${Thread.currentThread().name}")
      
      }
      suspend fun test(){
      delay(2000L)
      Log.e(">>>", "World: @ ${Thread.currentThread().name}")
      
      }
    • 多个 suspend fun 的执行顺序
      • 默认是串行执行
        suspend fun one(): Int{ // 延迟 1s 后返回 1
        delay(1000L)
        return 1
        
        }
        suspend fun two(): Int{ // 延迟 1s 后返回 2
        delay(1000L)
        return 2
        
        }
        override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val time = measureTimeMillis {                   // 在当前线程启动协程, 会阻塞当前线程, 最后会返回该协程消耗的时长
            val one = one()
            val two = two()
            Log.e(">>>", "计算结果: ${one + two}")
        }
        Log.e(">>>", "耗费时长: $time")                   // 时间差不多是 2s
        
        }
      • async & await
        • 说明:
          async 的作用是在后台启动一个协程, 并返回一个 Deferred 对象, 它是Job的子类, 表示一个承诺(promise) 会在稍后返回一个结果
          await 是 Deferred 上的一个方法, 作用是获取最终的结果 (会挂起当前协程, 直到获取到结果才会执行后面的代码)
        • 示例:
          override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
          val time = measureTimeMillis {
              val one = async(CommonPool){ one() }     // 在后台启动一个新的协程
              val two = async(CommonPool){ two() }
              Log.e(">>>", "计算结果: ${one.await() + two.await()}   @ ${Thread.currentThread().name}")
          }
          Log.e(">>>", "耗费时长: $time")               // 因为是 并行执行的, 时间差不多缩短一半
          
          }
        • async 的惰性求值: async函数可以传入一个参数 CoroutineStart.LAZY, 开启惰性求值, 表示只有调用 awaite 或 start 时开会开启协程
          override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
          val time = measureTimeMillis {
              val one = async(CommonPool, CoroutineStart.LAZY){ one() }     
              val two = async(CommonPool, CoroutineStart.LAZY){ two() }
              Log.e(">>>", "计算结果: ${one.await() + two.await()}   @ ${Thread.currentThread().name}")
          }
          Log.e(">>>", "耗费时长: $time")               // 时间 又 差不多是 2s
          
          }
        • async 风格的函数
          fun oneAsync() = async(CommonPool){ // oneAsync 就是一个普通函数, 不过调用它时会异步开启一个协程
          one()   
          
          }
          fun twoAsync() = async(CommonPool){
          two()
          
          }
          override fun onCreate(savedInstanceState: Bundle?) { // 注意这里没有了 runBlocking
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
          val one = oneAsync()                                       // 可以在协程之外调用
          val two = twoAsync()
          runBlocking {
              Log.e(">>>", "计算结果: ${one.await() + two.await()}")  // 但是 await 还是必须要在协程中调用
          }
          
          }
  4. 协程的上下文: 协程总是在 CoroutineContext 类型的上下文中执行
    • 协程调度器: 决定协程将要在 哪个(或哪些)线程中执行
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val jobs = arrayListOf<Job>()
      jobs += launch(Unconfined){           // 无限制, 会在当前线程开启协程
          Log.e(">>>", "Unconfined @ ${Thread.currentThread().name}")
      }
      jobs += launch(coroutineContext){     // 限制使用 父级上下文, 这里是 runBlocking的上下文(任何协程内都能获取 coroutineContext)
          Log.e(">>>", "coroutineContext @ ${Thread.currentThread().name}")
      }
      jobs += launch(CommonPool){           // 一个线程池 ForkJoinPool.commonPool
          Log.e(">>>", "CommonPool @ ${Thread.currentThread().name}")
      }
      jobs += launch(newSingleThreadContext("MyNewThread")){ // 开启新的线程, 实际项目中如果不再使用它, 需要通过 close 方法释放
          Log.e(">>>", "newSingleThreadContext @ ${Thread.currentThread().name}")
      }
      jobs.forEach { it.join() }
      
      }
    • 父子协程: 如果 协程B 使用了 协程A 的上下文, 那么 B 就是 A 的子协程
      • 父协程会等其所有子协程执行完毕后才会结束
      • 取消父协程, 它所有的子协程也会取消
  5. Channel: 用于在 协程间 发送 和 接收 数据
    • send & receive : 发送数据与接受 (都会将协挂起)
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val channel = Channel<Int>()
      val job = launch(CommonPool){
          for (i in 1 .. 5)
              channel.send( i * i)           // 发送数据
          channel.close()                    // 发送结束
      }
      for (value in channel)                 // 迭代获取数据 (底层通过 receive) , 直到 channel 关闭
          Log.e(">>>", "$value")
      job.join()
      Log.e(">>>", "Over")
      
      }
    • 使用 produce & consumeEach 简化 channel 的生产和消费
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val product = product()
      product.consumeEach { Log.e(">>>", "$it") }
      Log.e(">>>", "Over")
      
      }
      fun product() = produce(CommonPool){
      for (i in 1 .. 5) send( i * i)
      
      }
    • Channel缓冲区:
      • Channel 默认不开启缓冲, 它会等 send 和 receive都准备好之后再进行数据传输. 如果 send先调用, 也会挂起直到 receive被调用
      • 构建 Channel 是可以指定缓冲区大小. 设置缓冲的情况下, 允许在没有 receive的时候先发送几条数据, 直到填满缓冲区
        val channel = Channel(5)
  6. 多个协程更改共享数据
    • 数据安全问题: 多个协程同时对数据进行修改, 导致结果的未知性
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      var count = 0
      val jobs = List(1000){
          launch(CommonPool){
              count++
          }
      }
      jobs.forEach { it.join() }
      Log.e(">>>", "$count")                // 结果不可预知
      
      }
    • 解决方式
      • 方式一: 粗粒度的控制, 将所有协程 限制在 一个线程上
        override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
        super.onCreate(savedInstanceState)
        var count = 0
        val ctx = newSingleThreadContext("ctx1")
        val jobs = List(1000){
            launch(ctx){
                count++
            }
        }
        jobs.forEach { it.join() }
        Log.e(">>>", "$count")           // 结果是 1000
        
        }
      • 方式二: 细粒度的控制, 使用互斥锁 Mutex
        override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
        super.onCreate(savedInstanceState)
        var count = 0
        val mutex = Mutex()
        val jobs = List(1000){
            launch(CommonPool){
                mutex.withLock { count++ }        // 等价于  mutex.lock(); try{ count++ } finally{ mutex.unlock() }
            }
        }
        jobs.forEach { it.join() }
        Log.e(">>>", "$count")                    // 结果是 1000
        
        }
      • 方式三: 使用 Actor (就是一个协程, 其中可以保存数据, 并带有一个和其它协程通信的 channel )
        sealed class CounterMsg
        object IncCounter: CounterMsg()
        class GetCounter(val response: CompletableDeferred): CounterMsg()
        fun counterActor() = actor(CommonPool){
        var counter = 0
        for (msg in channel){                                      // 迭代收到的数据
            when(msg){
                is IncCounter -> counter++                         // 修改数据
                is GetCounter -> msg.response.complete(counter)    // 将结果返回
            }
        }
        
        }
        override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
        super.onCreate(savedInstanceState)
        val counter = counterActor()                          // 创建 Actor
        val jobs = List(1000){
            launch(CommonPool){
                counter.send(IncCounter)                      // 发送消息 通知修改数据
            }
        }
        jobs.forEach { it.join() }
        val res = CompletableDeferred<Int>()                  // 用于获取数据
        counter.send(GetCounter(res))                         // 发送消息 通知需要获取结果
        Log.e(">>>", "${res.await()}")                        // 结果是 1000
        counter.close()
        
        }
  7. 在 Android中使用协程
    • 添加依赖: implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:0.18’
    • 一个简单的案例
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      val job = launch(UI){                      // 使用 UI 上下文 开启协程
          for (i in 10 downTo 1){
              tv.text = "CountDown $i"           // 修改 TextView 的 文本
              delay(500L)                        // 延迟 500  (不会阻塞线程, 只会挂起协程)
          }
          tv.text = "Done"
      }
      bt.setOnClickListener { job.cancel() }     // 点击按钮 取消协程
      
      }
    • 通过UI上下文使用 actor
      override fun onCreate(savedInstanceState: Bundle?) = runBlocking{
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      bt.onClick {
          for (i in 10 downTo 1){
              tv.text = "Hellow: $i"
              delay(500)
          }
          tv.text = "Done!!!"
      }
      
      }
      fun View.onClick(action: suspend () -> Unit){
      val eventActor = actor<Unit>(UI){
          for (event in channel) action()
      }
      setOnClickListener{
          // offer方法会尝试给 actor传递数据, 如果此时actor正在处理其它数据, 则会根据情况做不同处理
              // 通过 actor<Unit>(UI) 创建的 actor                   ->  直接将新的数据丢弃
              // 通过 actor<Unit>(UI, Channel.CONFLATED) 创建的数据   ->  保留最新的那条数据, 等 actor空闲后再传入
              // 通过 actor<Unit>(UI, Channel.UNLIMITED) 创建的数据   ->  将所有新数据依次保存, 等 actor空闲后再依次传入
          eventActor.offer(Unit)
      }
      
      }
    • 使用协程的一些建议
      • 为了便于在页面销毁时统一释放资源, 建议将页面中的所有协程用一个 Job管理
        interface JobHolder{
        val job: Job
        
        }
        class MainActivity : AppCompatActivity(), JobHolder {
        override val job: Job = Job()                           // 每个页面都持有一个顶层 job
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            launch(job + UI){ ... }                             // 开启协程时, 这样传递上下文
        }
        override fun onDestroy() {
            super.onDestroy()
            job.cancel()                                         // 页面销毁时取消
        }
        
        }
      • 假如需要在自定义View中获取 Job, 可以通过扩展属性实现
        val View.contextJob: Job
        get() = (context as? JobHolder)?.job ?: NonCancellable