kotlin入门到熟悉上手使用

基础内容

  1. 定义一个常量,val a 这种写法 是一个只可读的变量,他并不是一个常量,而是 const val a = 123 才是定义一个常量
    并且, 不能够放在 函数内部 去定义 一个常量 const val a = 123 这样的定义,定义一个常量只能是 放在外面
    并且需要 在 companion object 内部
class MainActivity : AppCompatActivity() {


    companion object {
        const val a = 123
    }
    

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        const val a = 123 //错误不能在函数内部定义常量

    }
}
  1. kotlin 并不像java 一样有 基本类型 int ,和引用类型 Int ,他只有 一种引用类型 Int ,所以定义一个 Int 是这样的
    var a :Int = 1 ; // 这里看起来是用了引用类型Int, 但是实际上,编译器在编译成字节码的时候,会把他变成 java 的 int a = 1;
    所以并不需要考虑都是引用类型耗费性能

  2. 区间 0…100 , 0… 200 都是代表一个一个区间,而有专门的类类型去定义着这一个个区间

 var rang: IntRange  = 0..100

in 字符 可判断 前面的变量是否在 后面的区间内,他会返回一个Boolean 值

var a = 50 in  range

如果想取反, 判断 前面的变量 是否不在后面的区间

var b = 50  !in  range

通过 in 不止可以判断 前面的变量是否在 后面的区间内, 也可以判断 前面的变量是否在 后面的listof()内中其中一个,可以返回 Boolean
而listof内容又是泛型,所以就很好用了

    var isContain = "zjs" in listOf("zjs","liming","xxxx")
    Log.d("zjs", "onCreate: isContain $isContain")

    var isContain = 100 in listOf( 2233,45665,4500)
    Log.d("zjs", "onCreate: isContain $isContain")
  1. when 表达式,表达式和语句是不一样的, 在java 中, if 和switch 都是语句,而 在kotlin 中 if 和 when 是表达式,
    表达式和语句的区别是,表达式可以有返回值,也就是 这个 用这个 if 和 when 可以 返回一个值
  var a  = 5
  var b = when(a){
            1 -> "1"
            2 -> 2
            3 -> true
            4 -> println("哈哈哈")
            else -> {   //这里一定要写else 因为上面定义了 var b 去获取
                println("哈哈哈")
                3
            }
        }

when 的作用是 判断 括号内部的东西 是否 等于 下面的 东西,如果是 就做 后面的事情,并且,因为 when 是有 返回值,
所以会把最后一行当作返回值,比如else 这里 ,做了 println(“哈哈哈”) ,还把 3 当作返回值。

when的举例 如果 定义了有个 变量要获取返回值 判断最后 都需要 有个 else 做为 最后条件的结尾 ,不能只有上面的 1 2 3 4 的条件 而没有else 。

而如果没有定义 一个变量获取返回值就不需要一定有个 else

    when(a){
            1 -> "1"
            2 -> 2
            3 -> true
            4 -> println("哈哈哈")
        }

这里的4 的情况, println(“哈哈哈”) 其实也是有返回的,他返回的是 Unit 的类型 ,在java 中,没有返回的函数是用void,他是一个关键字,而在kotlin 中,没有返回是用Unit,并且他是一个类类型

public object Unit {
    override fun toString() = "kotlin.Unit"
}

也就是说,定义没有返回的Unit,他的返回值就是一个unit的一个类对象,
而在这里 的when ,当等于 4 的时候,输出了 println(“哈哈哈”)这句话,并且把 unit 对象给到 b 也就是
var b = unit ,也就是 b 是一个unit对象,那么当打印b 的时候,也就是打印 unit 的toString 函数 也就是会输出 kotlin.Unit

kotlin 这么定义把没有返回的操作的返回值 也定义成一个类对象,并且打印它会输出 kotlin.Unit 的好处是,它相比java,java没有返回就
直接是个关键字void,而kotlin,它可以拿到这个对象,然后你可以打印它,也可以处理判断它,比如 是否 字符串是 kotlin.Unit,是的话做出你的逻辑,不是的话做出你的逻辑。

  1. 日志打印 是可以这么操作的 ,用${} ,{} 里面可以写变量,也可以写 一些表达式操作 ,如果只是单纯的变量这个 {}可以省略
        var a  = 5
        
        Log.d(TAG, "onCreate:a : $a  还可以这样操作  ${ if (a>5) a++ else a--}")

        Log.d(TAG, "onCreate:a : ${a} 还可以这样操作  ${ if (a>5) "a是大于5" else "a是大于5"}")
  1. 函数 TODO 并不是个注释,他是个函数,使用它会自动抛出一个异常崩溃,参数是理由,返回类型是 Nothing
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")

在使用接口的时候,比如
定义一个接口

interface Interface {
    fun show()
}

定义一个接口实现类

class TextInterface :Interface {
    override fun show() {
        TODO("Not yet implemented")
    }
}

你会发现Androidstudio 会自动帮你写上了这个 TODO(“Not yet implemented”) ,代表如果在这里不写点代码,就会抛异常崩溃,
所以你应该删掉这一行 TODO(“Not yet implemented”) ,然后写点自己的代码实现。

  1. 反引号 它的作用在kotlin 中有几种,

第一 ,可以命名一些瞎几把中文名字,比如说 为了测试定义一个中文命名的函数

  fun `测试要的用的函数`(){

    }

调用 一样是  

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        测试要的用的函数()
}

第二 ,还有一种是比如有一个java 写的 函数


public class JavaClassTest {
    
    public static void is(){
        
    }

    public static void in(){

    }
}

然后你在kotlin 中想要去调用这个java 写的函数 is 和 in

 JavaClassTest.`is`()

而由于kotlin 中已经有了 is 和 in 函数,所以调用就需要加上``

  1. Kotlin里的函数中的参数 是 val的 ,不能通过去改变他的值,只能读和使用
    比如如下操作是做不到的。
  //多个参数
    fun  test(a: Int,b:String):String{
         a += 1 // 不能够这么做,参数是不能写的
         var c = a +1  //只能够读和使用
        return b
    }
  1. Java的 顶层类 是object,对应 kotlin 是 Any 类,Any是所有不可null对象的顶层类,也就是Any是不可以null的,
    而Any?才是所有可null对象的顶层类。

  2. Nothing不是代表返回空,Unit才是返回空,Nothing是没有返回,没有返回值,不会返回

  3. kotlin 提供了一些函数方便我们在项目中使用,

         var name :String? = null
        //这两个函数给我们判断,当前对象是否为null 的,如果为null 这两个函数都会抛出异常崩溃
        checkNotNull(name)
        requireNotNull(name)
        var isRight :Boolean = false
         //这个函数是如果变量是false 就会抛出异常崩溃
        require(isRight)
  1. kotlin 的 == 与 ===

== 和 equals() 是一样的,他们都是比较的是 值也就是内容

=== 比较的是两者的引用是否相同

        var name :String = "zjs"

        var name1 :String? = "zjs"


        //都是比较值也就是内容
        name == name1
        name.equals(name1)


        //比较的是两者的引用
        name === name1
  1. _ 符使用
    _ 这个符号 代表不接受任何变量,这样写可以节约一点点性能,可以理解为单纯的变量占位
    它的使用常见使用场景如下
        var list = listOf("1","2","3")
        var(value1,value2,value3) = list
         var(_,_,value3) =  list

//当 匿名函数 -> 左边的变量 在 -> 右边 函数要做的事情并没有用到这些变量,但是这里又需要两个变量的写法,那么可以直接用_ 代替 
代表这里不接受变量。
   declareFun = { _ ,_  ->
             "zjs"
        }

   //函数是可以写在类外面,函数的两种写法:
    //一种叫做函数体写法 ,这种写法不能省略 函数返回类型也就是这里标明的Int  以及return语句
    fun test() :Int {
        return  1
    }
    
    //一种是表达式体写法
    //直接用= 写函数内容,这种写法可以直接省略函数的返回类型,以及 return 语句
    fun test1() = 1
  1. var /val 在声明变量用,在类的主构造函数用,但是在函数参数,类的次构造函数不能用

  2. 在kt中,除了for,do,dowhile 这种循环是语句,其他基本都是表达式,比如when,if,
    表达式区别语句是,表达式默认有返回值,可以 = 表达式 把表达式的值给其他这种操作,

fun test1() = if (a>b) a else b
  1. 在一个类里定义一个方法,方法内可以访问类的私有属性和方法,但是如果为一个类添加一个扩展函数,这个函数不可以访问类的私有属性和方法,

  2. 学习一种写法,对于一个ArrayList 的类,你可以继承它,并且添加一些额外的需要的字段
    比如说如下写法:

   class ChartDataList : ArrayList<Float>() {
        var minValue = 0f
        var maxValue = 0f
        var total = 1
        var position = ArrayList<Int>()
    }

ChartDataList 继续了 ArrayList,并且他还额外增加了

4个字段 minValue maxValue total ,position = ArrayList

也就是说,一个 ChartDataList的类实例对象,他不仅可以在原来自己的list功能上去添加一些float值,
它还可以,为它的几个属性赋值。

                val pos = ArrayList<Int>()

                val list = ChartDataList()
                list.apply {
                    //list他本身自带的功能就是可以添加add数据进行
                     add(66.0f)
                     add(55.0f)
                   
                   //而下面是这个类实例他自带的属性赋值
                    maxValue = 100
                    minValue = 10
                    position = pos
                    total = pos.last()
                    
                }

这种写法可以用于,当你一个数据结构,你有一个list,list下又想再添加几个属性,其中属性包括又一个list
就可以直接写一个子类继承list,然后为子类添加你需要的额外的数据属性,形成一个多样化的类数据结构

  1. :: 操作符
    :: 操作符被称为成员引用操作符,也就是你 可以通过它获取 类、对象或接口的某个成员(如方法或属性),而不需要直接调用它

比如说

获取一个类的属性引用

class Person(var name: String) 
val nameProperty:  = Person::name

获取一个类的方法引用

fun isPositive(x: Int) = x > 0
val positivesReference = numbers.filter(::isPositive) 

获取一个类的构造方法

class Person(val name: String)

 val personConstructor: (String) -> Person = ::Person  
val person = personConstructor("zjs") 

使用 ::class 获取 Kotlin 类的类型对象引用(KClass 实例)

val stringClass: KClass<String> = String::class  
println(stringClass.simpleName) // 输出 "String"
  1. 关键字 reified
    reified 主要用于修饰泛型类型, 在不加reified 的情况下 ,
    泛型信息在编译后会被擦除,这意味着在运行时无法获取到泛型的具体类型是什么类型,
    reified 就是为了 防止泛型擦除,支持类型检查和转换,由于 reified 允许在编译时和运行时都保留泛型类型信息,可以在方法内部直接访问这个泛型的具体类型
    比如说:
inline fun <reified T> getValue(value: T): String {  
    return "value 的值为 $value; value 的类型为 ${T::class.java}"  
}  
  
fun main() {  
    println(getValue("Hello, World!")) // 输出: value 的值为 Hello, World!; value 的类型为 class java.lang.String  
}

在这个示例中,getValue 函数使用了 reified 关键字来修饰泛型 T,这样它就可以在方法内部访问到 T 的具体类型信息,并打印出来。

集合 数组 Map

kt的集合是分为两种,一种是可变的,一种是不可变的,也就是一个只读,而一个可读可写

  1. list集合
  • 创建 一个 不可变的只读 集合 listof()

listof 只读集合是不可以也没有 这些 add remove 方法

        //正常来说定义一个 List<T> 类型是这么做,通过 listof(里面写数据)
        var list :List<String> = listOf("1","2","3")

        //但是kt可以直接简化类型不写
        var list = listOf("1","2","3")

        //正常来说,list 获取每个元素 是通过get
        var a = list.get(0)
        var b = list.get(1)
        var c = list.get(2)
        //因为kt 内部有对这个[]运算符进去重载函数,所以可以直接像数组的写法一样获取值
        var a = list[0]
        var b = list[1]
        var c = list[2]


        //而由于数组或者list获取元素的时候会出现下标越界的情况所以
        //最好采用以下的方式去获取元素

        //getOrElse 代表获取list指定下标的元素,如果是null的情况也就是越界了就返回匿名函数最后一行的当作默认值
       var a =  list.getOrElse(0){
            //it是 就是这里的下标位置,也就是这里的 0
            //这个匿名函数作用是写默认值
            "1"
        }

        //getOrNull 代表获取list指定下标的元素,如果是null的情况的话会返回 null, 
        // 经常是搭配 getOrNull + ?: 代表如果是null 就做后面的
        var b =  list.getOrNull(0) ?: "1"


      listof 只读集合是不可以也没有 这些  add  remove 方法  

  • 创建一个可变的集合 mutableListOf()

//arrayListof也是可变的集合

  //正常来说定义一个 MutableList<T> 类型是这么做,通过 mutableListOf(里面写数据)
        var mutableList:MutableList<String> = mutableListOf()<String>("1,"2","3")
        //但是kt可以直接简化
        var mutableList = mutableListOf("1","2","3")

        mutableList.add(0,"1")
        mutableList.removeAt(0)

  mutableList.removeIf {
            //这里it 是个String 类型,代表  list 下的每个元素
            //这里匿名函数最后一个一定要返回true /false
            it.contains("1")
        }

可变的集合和不可变集合是可以相互转换的

        var listof =  mutableList.toList()
        var mutableList = list.toMutableList()

遍历list的方式

    //i 不需要先去外面定义变量,直接这里写
        for(i in list){

        }
     
        list.forEach {
            //这里的it就是list 的每一个元素
        }

        //forEachIndexed 不止有元素,还有对应的下标 ,匿名函数的参数变量就代表,list的每个元素的下标和对应的每一个元素
        list.forEachIndexed { index, item ->

        }
  1. Set集合

Set集合一样分为可变集合和不可变集合

  //正常定义一个set  集合 类型是  Set<String> ,使用是 setOf
    var setof: Set<String> = setOf<String>("1", "2", "3")
    //但是kt可以直接省略为
    var setof = setOf("1", "2", "3")

    //set 集合获取元素没有像list 一样可以直接用[] setof[] 而是需要
     var a = setof.elementAt(0)

    //并且set集成也会越界的情况所以也有跟list对应的高阶函数
    var a setof.elementAtOrElse(0){"1"}
    var b  setof.elementAtOrNull(0)?:"1"


    //可变set  mutableSet
    var mutableSet  = mutableSetOf("1","2","3")
    mutableSet+="1"
    mutableSet-="2"
    mutableSet.remove("1")


    //list集合和set集合是可以相互转换的
    var list = setof.toList()
    var set = list.toSet()
  1. 数组
   //kt 中对各种类型都有对应的数组类型
        // 比如 Int 对应 IntArray 使用是 intArrayof
        //     double 对应 DoubleArray 使用是 doubleArray of
        var a :IntArray = intArrayOf(1,2,3)
         //但是对应那种对象类型的比如 String ,File 这种 它对应的数组类型是用 Array<T> ,使用是 arrayof()
        var a: Array<String> = arrayOf("1", "2")
        var a : Array<File> = arrayOf(File("123"), File("345"))
        //由于kt 可以直接省略为
        var a = intArrayOf(1,2,3)
        var a  = arrayOf("1", "2")
        var a  = arrayOf(File("123"), File("345"))

        //获取数组的元素依旧是用[]
        a[0]

        //并且数组也会越界的情况所以也有对象函数
        var  b  = a.elementAtOrNull(0)?:"1"
        var  b  = a.elementAtOrElse(0){"1"}
  1. Map
    May 也是分为可变Map 和不可变Map
  • 不可变Map mapOf
  //正常定义是一个键值对的 Map<T,T> 类型的,使用是用的mapOf
        var map :Map<String,Int> = mapOf<String,Int>("123" to 123 ,"456" to 456)

        //由于kt 类型推导可直接写
        var map = mapOf("123" to 123 ,"456" to 456)

        //或者另外一种写法
        var map = mapOf(Pair("123",123),Pair("456",456))

        //map获取的方式
        //正常来说跟Java一样根据key获取键值
       var value =  map.get("123")
        //但是在kt中可以直接通过[]获取值,这种方式跟get 是一样的
       var value = map["123"]

        //并且如果 通过[] 或者get 获取的键值没有的话会返回 null 不会崩溃
        var value =  map.get("789")
        var value =  map["789"]

        //但是如果你用  getValue 去获取值 获取不到就会直接崩溃 而不是返回null
        var value =  map.getValue("789")

        //map 依旧有像list那样有获取不到就返回默认值的写法
        var value =map.getOrDefault("123",123) //获取不到就返回 后面的默认值
        var value = map.getOrElse("123"){123}


        //遍历map的方式
        //map 是由一对对键值对也就是entry 组成的 ,相当于 ( "123" to 123 ) 就是一个 entry
        //所以这里的 entry 类型就是 Map.Entry<T,T>
        for(entry in map){
            entry.key
            entry.value
        }


        map.forEach {
            //这里的it 其实就 每一个键值对   Map.Entry<T,T>
            it.key
            it.value
        }


        map.forEach {k,v ->
            k //key
            v //value

        }
  • 可变map mutableMap
  var mutableMap = mutableMapOf("123" to 123,"456" to 456)
        mutableMap += "789" to 789
        mutableMap -= "789"
        mutableMap["789"] =789
        mutableMap.put("789",789)

        // getOrPut 的意思是 获取这个key的对应的值,如果没有就 会先 put进去 ,"789" to 789 进去然后在获取出来
        mutableMap.getOrPut("789"){789}

函数, 匿名函数,lambda,高阶函数 知识

  1. 什么是匿名函数,其实匿名的意思就是,这个函数没有名字。

每一个由花括号{} 组成的 函数实现体 就是一个匿名函数 。

匿名函数 默认最后一样就是返回值

  1. 函数声明和定义

我们常见的定义一个函数常见写法 是这么写的


    //无参数无返回
    fun  test(){
    }


    //无参数有返回
    fun  test():String{
        return "zjs"
    }


    //1个参数
    fun  test(a:Int):String{
        return "zjs"
    }


    //多个参数
    fun  test(a: Int,b:String):String{
         a+1
        return b
    }

而另外的写法是这样,它分为两部分 , 它 一个 函数声明,只是声明, 另外一个就是函数实现体,也就是 匿名函数

函数声明写法如下:

 //无参无返回
var declareFun: () ->Unit
 // 无参有返回
var declareFun: () ->String
   //一个参数
var declareFun:(Int) ->String
//多个参数
var declareFun:(Int,String) ->String

中括号()-> 类型 就是一个个 函数声明, -> 左边 中括号内是 参数类型(), -> 右边 是 返回类型

匿名函数的写法如下:

         //无参数无返回 ,直接写函数要做的事情
        declareFun = {
            "zjs" 
        }

          //无参数,直接写函数要做的事情
        declareFun = {
            "zjs" //匿名函数不能写return 关键字,因为它默认就是最有一行是return(如果需要),如果 这个函数是无返回的,那就肯定没有返回
        }


        //当只有一个参数的时候 这个参数就是默认是名字叫做 it, -> 左边是变量  ,-> 右边是函数要做的事情
        declareFun = { it ->
            "it $it"
        }
       而由于只有一个参数,所以可以直接把 -> 左边的去掉 ,直接写 函数要做的事情,it 就是那一个 参数变量

    declareFun = { 
            "it $it"
        }

      所以从上面可以看出,一个匿名函数,如果只有一个或者没参数,可以直接写 函数要做的事情。


         //当有多个参数的时候,  -> **左边是变量**  ,-> 右边是函数要做的事情
        declareFun = { a ,b  ->
             a +1
             b
        }

 注意,这里的 a,b 它是个变量,并不是值,
你不能这里写 

   declareFun = { 1 ,"zjs" ->
            
        }
        
 并且 不需要 提前
         var a
         var  b  
       去先定义出来,你直接在这里写上你需要的变量名字就行



//当 匿名函数 -> 左边的变量 在 -> 右边 函数要做的事情并没有用到这些变量,但是这里又需要两个变量的写法,那么可以直接用_ 代替 
代表这里不接受变量。

   declareFun = { _ ,_  ->
             "zjs"
        }



**每一个花括号{ }是 匿名函数 , {}里是 函数体 , {}里 -> 左边是参数变量的名字, -> 右边是 具体要做的事情 **

所以一个函数由 2个部分组成,一个是 函数声明,一个匿名函数 ,他们的各自写法如上

两部分也可以结合起来写就是:

        //无参无返回
        var declareFun: () -> Unit = {

        }
        // 无参有返回
        var declareFun: () -> String = {
            "zjs"
        }
        //一个参数
        var declareFun: (Int) -> String = {
            "it $it"
        }


        //多个参数
       var declareFun:(Int,String) ->String = { a,b ->
            a +1
            b

    }

上面的函数写法是,既有函数声明,又有匿名函数
由于kotlin有自动类型推断功能 ,所以 把上面 两者 省略 写成 这样

        //无参无返回
        var declareFun  = {
          //直接写函数要的事情
        }

        // 无参有返回
        var declareFun = {
          //直接写函数要的事情,最后一行做为返回值
            "zjs"
        }


        //一个参数,这里不能因为 只有一个参数直接把 it 给 -> 左边给省略掉,因为it 都不知道是什么类型,
        //前面可以省略,是因为它已经有了函数声明,已经申明了参数类型。
        var declareFun = { it : Int ->
            "it $it"
        }


        //多个参数
       var declareFun =  { a :Int,b :String ->
            a +1
            b
       }

其实就是 把函数声明去掉,而匿名函数{} 里, -> 左边的参数变量 要写上类型 。

注意下,上面的省略写法, 即 这种 -> 左边的参数变量要写上类型 ,也是 属于 匿名函数的一种写法。

 { a :Int,b :String ->
            a +1
            b
       }

前面已经说了,一个{}就是一个匿名函数。反正记住,函数由2 部分组成,一个是函数声明,一个 匿名函数。
如果已经定义了 函数申明,那么 匿名函数的 -> 左边的变量参数 不需要定义类型
如果没有定义 函数申明, 那么 匿名函数的 -> 左边的变量参数 需要定义类型
如果没有参数,那么直接写 函数要做的事情。
如果只有一个参数,有函数申明,那么可以直接写 函数要做的事情,it 就是那个参数,
如果只有一个参数 ,没有函数申明,那么还得 -> 左边的参数 就得定义 是什么类型

  1. lambda 就是 匿名函数。他们是一样的,写法也是一样

  2. 在函数内部定义一个函数,也就是高阶函数

   fun gaojiehanshu(a:Int, b:String, name: (Int, String) -> String){

    }

name 是个函数,是属于 gaojiehanshu 的一个 参数。你会发现,函数 的参数是个函数, 这个参数的 函数写法 其实就是 函数申明

那么 前面说了,一个函数是由两部分组成,一个是函数申明,他是对这个函数的类型的申明,它一个是函数的是实现体,也就是 匿名函数。
那么也就是我们可以写一个匿名函数当作 参数 去传给这个 gaojiehanshu 。

//这里  -> 左边的参数变量并不需要写类型,因为,已经有了函数申明
gaojiehanshu(1,"zjs",{ c, d ->  "zjs" } )
  1. 匿名函数当作参数的 写法演变。

如果一个高阶函数的参数只有一个匿名函数,那么演变过程可以是这样的。

  // 参数只有是匿名函数
   fun gaojiehanshu(name: (Int, String) -> String){

    }

常规写法是这样的

  gaojiehanshu({ c: Int, d: String ->  "zjs" }) 

当 匿名函数是函数参数的最后一个,那么可以把 匿名函数放在后面

 gaojiehanshu(){
     c: Int, d:String -> "zjs"
  }

当参数只有一个匿名函数可以把括号给省略掉

 gaojiehanshu{
     c: Int, d:String -> "zjs"
  }

而至于如果匿名函数没有参数或者只有一个参数,那么可以直接写函数要做的事情,那是匿名函数自己内部的写法了

  fun gaojiehanshu(name: (Int) -> String){

    }

 gaojiehanshu{
            "zjs"
        }

所以说当你看到 一个命名然后直接后面带着一个花括号,那么要下意识知道
这个命名是个高阶函数,参数里面有个函数申明
花括号是个匿名函数,是这个高阶函数的参数
或者换句话说,当你在调用一个高阶函数的时候,如果这个高阶函数只有一个匿名函数参数,那么
直接写 命名 +匿名函数{},而匿名函数内部要怎么写法,就看这个匿名函数参数的情况了。

  1. 所谓内敛 inline ,就是把匿名函数内函数要做的事情给搬到函数被调用的地方,

当一个函数的参数有 函数申明的时候, 并且这个函数申明在这个 高阶函数中直接被调用,
那么这个高阶函数使用内敛是最好的,也就是如果一个函数是高阶函数,并且它的参数函数在高阶函数内部直接调用,那么这高阶函数定义内敛。

  inline  fun test1(a:() -> Unit){
     a()  //这个函数申明参数在高阶函数内被直接调用,而不是被传给别人的时候 这个高阶函数用inline
    }

如果一个函数的参数有多个函数申明,并且希望其中的某个函数申明不要内敛,那么用noinline

  inline  fun test1(a:() -> Unit,noinline  b:() -> Unit){


    }
  1. 函数定义默认就是 public 所以不需要写public。

  2. 函数也是一种类类型 ,跟 Int String 一个道理,它也是属于Any 的子类, 用 :: 符号去 把一个函数 变成一个 函数类类型的变量对象
    那么 这里的a 就是 函数类类型的引用 ,并且后续可以把 函数类型对象引用a 传给当作参数传给 参数为函数的 高阶函数。

  var a = ::test
  
  fun test(){
    }

   fun gaojiehanshu( name: () -> Unit){

    }

  gaojiehanshu(a)


像这种 把一个函数给引用出来然后函数有具体的名字比如这里的a ,就叫做 具名函数,
而如果是这种没有专门把一个函数给抽出来有个具体的名字 直接是一个{} 当作高阶函的参数,就是匿名函数

  gaojiehanshu { 
            
        }

正因为函数也是一种类型,所以一个函数的返回类型也可以一个函数

     var a = test()

    fun test() :(Int,String)->String{
       
        return {a,b -> b}

    }

 a(1,":zjs")

test 函数的返回值是一个函数申明也就是 它表示要返回一个函数,
而return 返回的就是个匿名函数,那么 a 就是这个匿名函数
所以使用调用 a 就是 使用这个匿名函数一样的写法

匿名对象表达式,Java接口对象简略写法

  1. 什么叫做匿名对象表达和 具名对象表达式区别

这里有一个正常的类Student ,类不管是不是接口,它的方法 不管是不是抽象都行

open class Student(){
   
    open fun play() {

    }

    open fun eat(){

    }
    

}

如果你想要简单创建这个一个Student 对象很简单就是

  var a = Student()

但是如果你想要创建一个 这个对象的同时,又想要重写他里面的方法写上自己的对应实现呢

想要重写方法正常的操作是不是得这样,弄个类去继承或者实现这个 Student,然后重写它里面的方法

class StudentImpl :Student() {
    override fun play() {
        super.play()
        //写上自己的逻辑
    }

    override fun eat() {
        super.eat()
       //写上自己的逻辑
    }
}

然后再创建这个对象 var a = StudentImpl()

这样的操作就叫做 具名表达式

而匿名表达式是这样的 ,直接一步到位 。
也就是用 object : 直接类名然后{ 里面重写对应的函数即可},这种就叫做匿名对象表达式

  var a =  object : Student{
            override fun play() {
                super.play()
            }

            override fun eat() {
                super.eat()
            }
        }

这个一个使用场景好处就是:

//当你写了一个接口回调,来使用的时候

interface DataCallback {

    fun responseSuccess(data: Data)

    fun responseFail(errorMsg:String)
}

//你不需要去制造个实现 DataCallback 的实现类,或者让Activity去实现它,可以直接用object:创造一个Callback实例传过去

    getData(object :DataCallback{
        override fun responseSuccess(data: Data) {
             //在callback这里接收到获取的数据
        }

        override fun responseFail(errorMsg: String) {
            //在callback 这里接收到失败的信息
        }

    })




fun getData(dataCallback: DataCallback){
    //获取到数据成功的时候调用callback
    dataCallback.responseSuccess(),把数据传给callback
    //获取数据失败的时候调用callback
    dataCallback.responseFail(),把失败的信息传给callback

}
  1. 函数式接口 创造 接口对象的 简略写法

Java的一个接口仅有一个方法的 这个接口就叫做,函数式接口

当一个接口它只有一个方法。


public interface Runnable {
    public abstract void run();
}

这个时候想要创建一个接口对象,并重写方法,依旧可以采用上面的匿名表达式写法

  var runable =  object :Runnable{
            override fun run() {
            }
        }

除了这种写法 ,还有一种,可以直接 ** 接口名+ 花括号** 花括号{里面就方法要做的事情}

   var runnable = Runnable{
            //run方法
        }

而如果是一个有参数的方法的话 比如

public interface JavaCallback {
    void run(String info );
}

那么就是

var a=   JavaCallback{it:String

        }

这里的it 其实就是 run参数里面的 info 。

所以总结就是

匿名表达式写法不需要在意是不是java写的接口还是kt,类是接口还是普通类 ,接口里的函数一个还是多个,
只要你想创建一个对象并且重写该类的方法就可以用匿名表达式写

而 一个接口是java写的,并且接口只有一个方法 的时候 才 可以 直接 接口名+ 花括号 去创建接口对象

  1. 当参数是仅需一个(Java接口对象并且接口函数只有一个)的写法简略

当参数是需要 一个Java接口对象,并且接口的函数只有一个 可以简单写法 : {里面是接口函数内要做的事情}

比如

//这个方法的参数就是需要一个java接口对象,并且接口的函数只有一个  ,那么可以直接 setOnClickListener{里面是接口函数内要做的事情}
setOnClickListener()

 public interface OnClickListener {
        void onClick(View var1);
    }

它的演变过程是这样的 :

       通过上面我们知道可以直接用简单表达式创建一个java接口类对象并且实现里面的函数
        leftbutton.setOnClickListener(View.OnClickListener{
    
        })

      //  不过 当 参数列表中仅需一个Java 单抽象方法接口参数,我们还可以将 接口名进行省略
        //这里的 Java方法 setOnClickListener的参数 就是 有且仅有一个Java 单抽象方法接口参数 ,那么可以把接口命省略
        leftbutton.setOnClickListener({

        })

       //而由于这里的 {}它是最后一个参数,也只有这个参数,所以可以直接
        leftbutton.setOnClickListener{
        }

不只是上面的 我们常见的

public interface Runnable {
void run(); }

也可以这么玩

简化过程是这样的:

Thread(object : Runnable { override fun run() {
println("Thread is running") }
}).start()


Thread(Runnable { println("Thread is running")
}).start()


Thread({
println("Thread is running")
}).start()


Thread {
println("Thread is running")
}.start()

kotlin 的可空性

  1. 在Java中,当一个引用对象 被申明定义的时候,它可以不写 这个引用默认初始值,因为java 会为它们 提供一个默认初始值
    比如说 在Java中,
public class JavaClassTest {

    
    // t 初始值可以不写,java 会为他们提供 默认的null
    String t ;

    // a 初始值可以不写,java 会为他们提供 默认的0
    int a;

}

但是在kotlin 中不可以,定义一个对象引用出来,就必须给他一个初始值,不可以不写,kotlin 不会自动帮他们提供默认值

只定义不写默认值 是会报错的,必须写上默认值是什么

class MainActivity : AppCompatActivity() {

    var name :String  //错误必须自己申明初始值是啥
    
    var a :Int   // 错误必须自己申明初始值是啥
}

除非你指明了懒加载 ,告诉编译器,我等下会自己去 初始化它,你不用担心,但是你这么做一定要记得根据生命周期去初始化它,
不然就会造成人为的失误。

class MainActivity : AppCompatActivity() {

  private lateinit var name :String

     var a :Int = 0 

lateinit 关键字能用与这些 类型对象,不能用在这里的Int ,所以还是得 提前写上初始化值
lateinit的变量只能是var ,不能是val

  1. 在kotlin 中,当一个对象被申明出来那一刻,它就必须指定它到底 是可null 类型还是 不可null 类型。
    也就是必须在类型后面 后面加上或不加上?
class MainActivity : AppCompatActivity() {

    var name :String = "zjs"  //不可null

    var name1 :String? = null //可null

并且后续这个属性 是不可以 更改的。

也就是后面这个 不可null 的 name 去设置 name =null 是会报错的不允许的。

  1. 当一个对象是可null 的,那么调用它的任何属性和方法函数,都必须加上 ?. 比如 name1?.length
    代表如果不为空就做xxx操作

  2. 高阶函数let

 name1?.let { 
            it.length
            it.toString()
            it.equals("zjs")
        }

前面我们说,当一个命名后直接+{}匿名函数,证明这个命名是个高阶函数,并且它的参数只有唯一参数就是这个匿名函数,
而{} 这个匿名函数就这个let函数的参数。而至于匿名函数{} 内部 直接用it ,代表 这个匿名函数 只有一个参数。

前面我们说了,当一个对象是可null的 那么调用它的任何属性和方法函数,都必须加上 ?
那么必然会造成 任何一个可null 对象在对用函数和属性的是每一次都要加上?

        name1?.length
        name1?.toString()
        name?.equals("zjs")

而利用let ,就会把前面的可null 对象 name1 在 匿名函数内部 转给一个 不可null 对象 ,而这不可null 对象就是匿名函数的it
也就是 it 就是这个 name1 ,只不过it 是不可null 属性
所以就可以调用it 的任何方法,不需要在加上?

 name1?.let { 
            it.length
            it.toString()
            it.equals("zjs")
        }

注意name1 依旧是可null 对象

  1. 当一个可null 对象,你百分百确定它不是null的,那么你可以设置非空断言,这样就不需要设置?.
        name1!!.length
        name1!!.toString()
        name1!!.equals("zjs")
  1. if 显示判空写法

前面如果一个对象是可null的,那么你调用它的函数方法需要?.代表是不null的是时候做xxx,
但是如果你用if显示去做这一步判断是否为null了,你就不需要在 写? 了

 if(name1!=null){
            name1.toString()
            name1.equals("zjs")
        }
  1. ?. ?: 如果为null 做啥,如果不为null 做啥 。有了这些操作符 就可以不需要手动去些那些
    if xx==null 做xxx else if xx!=null 做 xxx
 var length =   name1?.length?:name.length
  1. 在kt 中,当一个对象不为null的时候,也有可能是空值,所以有需要的话 你也需要对空值对象一个判断 用 isBlank判断是否是空值
var name :String? = null //null
var name :String? = ""  //空值


fun   getName ():String{
 return  name?.let {
        if (it.isBlank()){
            "空值"
        }else{
            it
        }
    }?:"等于null"
}

kotlin的内置常用高阶函数

  1. apply 使用
        //前面我们说匿名函数默认当做最后一行为返回值,但是apply的匿名函数比较特殊
        //调用 apply 函数会返回改对象本身也就是这里的 调用apply 会返回自身的name 对象
        //sameName 跟name 是一个对象

      var sameName = name?.apply {

           //一般匿名函数里的参数是it,但是在apply 这里的参数是this,也就是调用apply 对象本身
           //正式因为匿名函数里面的环境是this,所以在这里可以直接调用对象的属性和方法,
           //虽然name是可null 对象,按道理调用他的任何方法属性都得?.
           //但是因为在调用apply的时候已经调用了? 所以这里this 默认就不可能为null 也就不需要?
           length
           equals("zjs")
           toString()

       }

        //apply一般用于 对一个对象的多次调用使用
        比如正常是

        name1?.length
        name?.equals("zjs")
        name?.toString()

        //而用了 apply就是
        name1?.apply {
            //直接些属性和方法,因为当前环境就是this本身
            length
            toString()
            equals("zjs")
        }
  1. run 的使用
 //run 跟 apply一样 匿名函数 的参数 是this,也就是调用apply 对象本身
        // 他的使用跟 apply 一样,区别在于, apply函数的返回值是调用对象本身
        //而run函数的返回值是遵循 匿名函数的最后一行
        name1?.run {
            length
            toString()
            equals("zjs")
            
        }

  1. with 的使用
  //with跟run是一样的,唯一区别就是 写法不一样,run是 name1?.run 
        //with是 带个括号() 把对象放进去,run是调用对象的run 方法
        //而且因为带个括号() 把对象放进去。所以如果是可null 对象,那么还需要进行判断处理
        //而run 的话因为已经name1?.run ,所以run的匿名函数内不会为null的情况
        var b = with(name1){
            this?.length
            this?.equals("zjs")
            this?.toString()
        }

  1. let 的使用
    //let的匿名函数 参数就是个it,it代表的是调用者本身这个对象
        //let的作用就是把前面的可null 对象转给一个相同的对象只不过他是不可null的属性
        //let 函数同样是返回匿名函数的最后一行,也就是a = it.toString()
        var a = name1?.let { 
            it.length
            it.equals("zjs")
            it.toString()
        }
  1. also 的使用
 //also返回类型跟apply 一样也是调用者本身而不是匿名函数最后一行
        //alse 匿名函数内部是it 
        name1?.also {
            it.length
            it.equals("zjs")
            it.toString()
        }

前面可以总结为 apply run with 都是内部是 this ,apply 返回对象本身,run 返回最后一行,with 返回最后一行写法不同
let ,also 内部是 it . ,let 返回最后一行,also,返回对象本身

  1. takeif 使用
   //如果takeif 匿名函数返回true 那么 整体返回 对用这本身,也就是这里 c 就是 name1
        //如果匿名函数返回false,那么整体 返回 null 也就是这里 d 会是null 
        name1?.takeIf { true/false }

       var c =  name1?.takeIf { booleanTest(1) }

        var d =  name1?.takeIf { booleanTest(2) }

     
        // takeif 一般会跟 ?: 配合使用,代表如果前面takeif 整体是 null ,那么xxx
         var e=   name1?.takeIf { booleanTest(2) } ?: 2


  private fun booleanTest(a:Int): Boolean {
        return a ==1
    }



  //takeif 里面的匿名参数也是it 代表对象本身,并且也是匿名函数最后一行返回,但是由于takeif的 功能
        //最后一行只能返回true 或者false,不能像其他匿名函数一行随便什么类型都可以
        name1?.takeIf {
            it.length
            it.equals("zjs")
            it.toString()
            true//最后一行一定要返回true或者false
        }

  1. takeUnless
 //如果takeUnless的使用跟 takeif 一摸一样,只不过 ,有个功能是反者来的
        // takeif 是 匿名函数返回true 那么 整体返回 调用对象本身, 匿名函数返回false,那么整体 返回 null 
        // 而 takeUnless 匿名函数返回true ,那么整体 返回 null  , 匿名函数返回false,整体返回 调用对象本身
        name1?.takeUnless {
            it.length
            it.equals("zjs")
            it.toString()
            true//最后一行一定要返回true或者false
        }


     // takeUnless 一般跟  it.isNullOrBlank()配合使用来判断对象是否有初始化
        //像这里如果name 有初始化     it.isNullOrBlank() 会返回 false ,那么 takeUnless 整体会返回 name,最后setName 会是 name
        //而如果 name 没有初始化, it.isNullOrBlank() 会返回  true ,name takeUnless 整理会返回 null ,如果是null ?:  最后setName 会是没有初始化
        var setName = name1?.takeUnless {
           it.isNullOrBlank()
        }?:"没有初始化"
  1. map ,flatMap,filter
   //map是转换的意思,是对每个元素进行转换一个新的值,包括转换类型
    //这里把每个元素转成了123 Int 类型了
    //map返回的是一个新元素组成的一个新集合 List<>
    var list2 =  list.map {
        //it 代表每个元素
         123
    }



    //flatMap 是把每个元素,转成一个新的集合,所以所有的元素组所有的集合就组成一个新的集合List<List<String>>
    var list3  = list.flatMap {
        //it 依旧是每个元素
        //这里是把元素又组成了一个新的集合
        //也就是形成了 listof("123aaa","123bbb","123ccc")
        //那么最后就会返回多个集合的嵌套list ,listof(listof("123aaa","123bbb","123ccc"),listof("456aaa","456bbb","456ccc"),listof("789aaa","789bbb","789ccc"))
        listOf(it+"aaa",it+"bbb",it+"ccc")

    }


    //filter 过滤操作,满足条件是true的就会把这个元素合进来,不满足的就过滤掉,最终形成一个新的list
    var list4 = list.filter {
        //判断每个元素包含123的就合进来
        it.contains("123")

    }



    //zip合并操作,合并并不是直接添加,而是每个元素形成Pair 对的集合
    //像合并后形成的list3 就是一个  List<Pair<String,Int>>
    //它的合并结果是这样的 [("123",123),("456",456),("789",789)]
    var list1 = listOf("123","456","789")
    var list2 = listOf(123,456,789)

    var list3 : List<Pair<String,Int>> = list1.zip(list2)

  1. 类的属性
class Student {

    //当定义一个属性的时候
    var name ="zjs"
      //下面的代码都是默认会帮你把写的,
      //类的每个成员默认都有set /get 方法 而  set/get方法 都会持有 field ,这里的field就相当于当前的变量name
      //而当成员不初始化时候,set/get 是不持有 field的。也就是 如果name 不一开始初始化一个值“zjs"。set/get方法是不能够用field的
    get()=field
    set(value) {
        field = value
    }
        
        //所以说你想重写实现自己的逻辑就是去重写对应的set 或者get 方法
    get()=field+"1"
    set(value) {
        field = value+"1"
    }


         var student = Student()
        //kt 都不需要调用get 和set 以这种方式默认会帮你调用
        //调用set
        student.name ="zjs"
        //调用get
        var name = student.name

}
  1. val 变量定义的属性,只有get方法,并且,如果get方法没有用到field,那么属性不可以定义初始化值
//val 定义的属性是没有set方法,只有get方法
val count
get() = "123"

//并且如果你这里需要重写了get方法,并且get 方法没有用到 field ,那么val 的变量不能初始化一个值,不然会报错,而在上面的var就不会可以这么写
val count ="123"  //不能这么写初始化,因为你用get 重新赋予一个 新值了,这里就冲突了
    get() = "456"

//除非你get里面用的还是有用到field
val count ="123"  //可以这么写初始化 ,这里的get 用到了field ,那么上面的count有作用当然就可以,在count做初始化
    get() =field+ "456" 



  1. 类的定义和创建 与 主构造函数
//定义一个类
class Student {
}

//其实实际是 ,只是这个主构造函数如果没有参数,那么kt 可以省略这个括号
class Student() {
}

创建一个 对象,
  var student = Student()

  1. 主构造函数里面放参数写法意义
//kt 规范,构造函数的参数 用下划线 _xxx  的写法
class Student(_name:String,_age:Int){

}

这里的参数 _name,_age ,他们只是传过来的 临时参数,不是 Student 的属性,只有定义成

class Student(val _name:String,var_age:Int){

}
参数 _name,_age 才会是 Student 的属性。

或者你也可以手动写成这种写法

class Student(_name:String,_age:Int){
 
    //手动定义两个属性,然后拿传过来的参数赋值初始化。这跟在上面构造函数里面写 var,val 是等价的
    val name = _name
    
    var age = _age


}


反正你就记住,构造函数里面的不写val,var的都只是值的传递的意义而已,只有定义var val 才是 为此类添加属性

  1. 次构造函数

类对象创建是通过主构造,而一个类只有一个主构造,但是可以有多个次构造,只是次构造方法一定要调用主构造
所以说也可以通过次构造间接的创造对象

调用的方式写法就是如下:

class Student(val _name:String){

    //另外再次说明一点,这里整个Student 只有 一个属性_name
    constructor(_name: String,_age:Int):this(_name){

    }

    constructor(_name: String,_age:Int,_sex:String):this(_name){

    }


}

var student = Student("zjs")

var student1 = Student("zjs",123) //通过次构造创建对象

var student1 =Student("zjs",123,"nan")
  1. 主构造被调用的会调用的地方 init{}

前面说了类可以有很多次构造去创建对象,并且,当被创建的时候,你也是可以感知到的也就是在
constructor的方法你可以监听这个对象通过哪个次构造被调用了,但是主构造呢?

  constructor():this(){

    }

我们说当一个对象被创建是通过主构造,而当主构造被创建的时候,必然会最开始先走 init{} 方法,相当于一个类对象的创建的初始化
,然后再走那些此构造的方法。

class Student(val _name:String){


    init {
        println("一个对象被创建的时刻,主构造被调用必然最开始先走这里,然后再走那些次构造方法")
    }

    
    //另外再次说明一点,这里整个Student 只有 一个属性_name
    constructor(_name: String,_age:Int):this(_name){

    }

    constructor(_name: String,_age:Int,_sex:String):this(_name){

    }

}
  1. 前面说的在主构造函数不定义 var /val 的参数都是临时的不能用的。只有在 init{} 和属性下才可以用。也就是:
class Student(_name:String){

    
    //这里临时变量只有在这两种场景才可以用
    var a = _name
    init {
        println("一个对象被创建的时刻,主构造被调用必然最开始先走这里,然后再走那些次构造方法")
        var a = _name
    }

    //而其他函数下是不能用这个临时的
    fun show(){
        var a = _name
    }
}
  1. 类创建的顺序 。
class Student(_name:String){


    //第一步 先初始化这里的类成员a  //这里 类成员的初始化和init 函数内是看谁先写再前面就谁先执行
    var a = _name

    //第二 初始话 init 里面的
    init {
        println("一个对象被创建的时刻,主构造被调用必然最开始先走这里,然后再走那些次构造方法")
    }
    
    // 第三,再初始化这里的类成员b 
    var b ="zjs"


    //次构造肯定是先调用主构造再回来,最后一步
    constructor(_name: String,_age:Int):this(_name){

    }
   
}

正是因为这种顺序,所以不能写出这样的代码

class Student(_name:String){
   //先辅助,再初始化
    init {
        age = 100
    }
    
    var age =1
}
  1. 类的每一个成员一旦定义出来来就必须定义是可null 还是不可null ,并且必须为它定义初始值 。如果想等待再定义初始值,就必须定义懒加载,也就是延迟加载,延迟初始化。

延迟加载有两种

一种是 要自己手动调用初始化
也就是用 **lateinit var **
注意这里的 var 只能是var 而不能是val ,因为,你这里先定义出来 ,你后面要自己再去设值的,如果你设用val 后面还怎么设值

class Student(_name:String){

   // lateinit 只用于 对象类型的
    lateinit var age :String

    //而不用于这种数据类型的
    lateinit var age1 :Int


    //需要手动自己初始化
    fun init(){
        age = "zjs"
    }

    //可通过这种写法判断这个对象是否初始化,不然很容易因为忘了初始化而崩溃
    注意这里一定要写:: ,也就是对于一个变量是用 lateinit 的你要判断是否已经初始化
    需要不能单纯 age.isInitialized ,而是要加上:: ,不然就会报错
    fun use(){
        if(::age.isInitialized){
            
        }else{
            init()
        }
    }
}

另外一种是 by lazy {里面写初始化值 }

class Student(_name:String){

//   by lazy 的方式只能用于val ,而不能是var 
// { 里面写初始化值 } 
val  age by lazy { "zjs" }

//{也可以在里面调用个函数的写法去获取值}
val  age by lazy { getString() }

fun getString() :String{
    return "zjs"
}


//  用 by lazy 延迟加载初始化属性的最大好处是,它能够再这个对象的这个属性被用到的时候才被初始化
// 而不是对象一被创建就被初始化
val  age by lazy { "zjs" }


//比如说 如果直接写
val  age = "zjs"
//然后当对象被创建时,这个age属性以及被初始化了
var student = Student("zjs")

//而如果用懒加载 by lazy

 val  age by lazy { "zjs" }

//然后当对象被创建时,这个age还是未初始化的
 var student = Student("zjs")
//只有当调用 的时候才会被初始化,这个好处就是提高性能,有些对象很大,只需要用到它的时候才初始化
student.age

}

  1. 继承与重载

a. kt 中所有的类默认都是final 的,也就是不能被继承,如果想被继承需要用open修饰

open class Father {
}


class Son : Father() {
}

b.父类的主构造要的参数,子类的主构造函数也必须有要,才能传给父类

open class Father(val _name:String) {
}

//父类的主构造要的参数,子类的主构造函数也必须有要,才能传给父类
//而你子类要不要这个参数,就看你自己要不要定义val/var
class Son(_name:String) :Father(_name) {
}

c. kt 中父类的函数默认都是final的,所有子类想要重写父类的函数,这个父类的函数必须是open的。

open class Father(val _name:String) {
    
   open fun play(){
        
    }
}


class Son(_name:String) :Father(_name) {

    override fun play() {
        super.play()
    }
}
  1. is + as 类型转换
    var father:Father =Son("zjs")

    //这里调用的是子类Son的play
    father.play()

    // 这里会打印true
    if(father is Father){

    }
    //这里也会打印true
    if(father is Son){

    }

    //可以通过 as 进行类型转换
   var son =  father as Son
    
    
    //一般会采用 is +as 的搭配
    if(father is Son){
        (father as Son).play()
    }
      
    //当这个引用被is 判断过,实际上 它已经是属于转换类型了,可以直接使用了,不需要再被as 转才能使用
        if(father is Son){
          father.play()//这里father已经是Son了,可以直接调用Son的属性了,不需要再as 转换
    }

as? 如果是这个类型就会帮你转换成对应的类型,如果不是就会返回null
如果 as 不加?不是这个类型就会崩溃,而不是返回null

  1. kt 还有一种智能推断类型,当 上面的as 被转过一次后,后面使用就无需再转,它就已经知道是什么类型了,就可以直接用了
open class Father(val _name:String) {

   open fun play(){

    }

}


class Son(_name:String) :Father(_name) {

    override fun play() {
        super.play()
    }

    //这里有个只有子类才有的方法,父类没有
    fun sonPlay(){

    }
}
 var father:Father =Son("zjs")


        //这里的father 转了一次son 后,
        (father as Son).sonPlay()

         // 后续这个father就是Son类了,所以它才可以直接调用 子类专属的 sonPlay方法
        father.sonPlay()
     
        
        //而如果你不先转一次的话,
        father.sonPlay() //这句话就肯定会报错
  1. 单例类 直接写类名
//单例类是不能写主构造的,也就是这里不能写个括号(),直接写类名就行
object Once {

    //单例类只有被创建一次,也就是,初始化这里会被调用一次
    init {
        
    }
    
    fun show(){
        
    }
}


//当调用 第一次 Once.show() 单例类的 init{}就不会被调用,因为类对象第一次创建
//并且直接写单例类名就行,不能写Once()
Once.show()
  1. 类的 companion object 伴生对象
open class Student(){


    //伴生对象只有一个,只会初始化一次
    companion object{
    
        var a ="zjs"

        fun show(){
        }
        
    }

    open fun play() {

    }

    open fun eat(){

    }
}


//不管创建多少个对象,他们都是共享一个伴生对象 伴生对象只有一个,只会初始化一次,
//正因为如此,伴生对象里面的属性a 也只 会初始化一次,并且只有一个a  让他们共享
//在 kt 中没有 static 的关键字,而是用伴生代替。
// 可以理解为伴生对象就是静态 ,里面是属性和方法也是静态的。
// 所以可以直接调用   Student.a      Student.show()
var student =Student()
var student1 =Student()
var student2 =Student()
  1. 内部类和嵌套类
open class Student(){


  var name  ="zjs"

    //外部的类也可以访问内部类
    fun getAge(){
        Test().test()
        Test().age
    }


    //kt 中内部类可以访问外部类的成员。但是只有定义inner的才叫内部类
    //外面的类可以访问内部的类成员
   inner class Test{

        var age = 1

        fun test(){
            name.length
        }

    }
    
    
    //而不加inner修饰的内部是叫做嵌套类。 
     //嵌套类的特点是 /外部的类也可以访问内部类,但是内部的不能访问外部的
     class Test1{
         
    }


}
  1. data 类
//data 数据bean 类,直接就是在 普通class上加 data 修饰

//data 类的好处是相比普通类有,不止有 ,set get ,toString ,equals 重写了,方便人使用
// data类的主构造参数必须申明是var/val,不能什么也不写 
//data 类不能被 open,abstract,sealed ,inner 修饰
data class Persion(val name:String,var age:Int)




//注意, == 跟equals 都是判断内容值是否一样。 但是
//当判断的是对象的时候,它判断的是引用是否一样 比如  一个不加 data 的普通类 
class  Son(val name:String,var age:Int)
var son = Son("zjs",24)     
var son1 = Son("zjs",24)     

son == son1  //这里判断的是引用是否一样 也就是会返回 false



//但是如果这个类是data类,由于data 有自己重写的equals方法 所以的==判断的是内容
data class Persion(val name:String,var age:Int)
var Persion = Persion("zjs",24)
var Persion1 = Persion("zjs",24)

Persion == Persion1 //判断的是内容 打印true

//特别注意下, data类的 toString,equals.copy 函数 它只对 主构造的变量生效,也就是说, 打个比方,这里的toString 只有
变量 name,age 这两个主构造的变量,而sex变量是不会有的。

再比如,copy 函数,

//这里a 是有3个变量的,2个是主构造的。 
var a = Persion("zjs")

//变量b 是会丢失 sex 显示是 "" 而不会显示 "boy"
var b = a.copy("zjs",24)

     

data class Persion(var name:String,var age: Int){

      var sex:String = ""

    constructor(name:String) : this(name,99){
        sex = "boy"

    }
  
}

并且 data 类默认持有解构声明。
对与一个普通类来说,如果想要使用解构声明需要如下

open class Student(var name:String,var age: Int){


    //通过运算重载符  operator ,并且 函数命名一定要是  componentXX 按顺序1,2,3...这样子
    
    operator  fun component1() = name

    operator  fun component2() = name


    //当上面的操作后就可以,写法如下
    var(name1,age1) = Student("zjs", "24")


}

而如果是data 类,就不需要自己写上面的 operator fun component 。直接

data class Student(var name:String,var age: Int){

}

 var(name1,age1) = com.example.myapplication.Student("zjs", "24")
  1. 接口
//接口默认就是 public 且是open 的直接就可以被继承
//接口是不能有主构造或者次构造的,也就是这里的Anim 不能加括号Anim()
interface Anim {

    //接口的变量也不能设置初始化值
    var head: String

    //接口的变量也不能设置初始化值
    var hand :String

    //接口的成员包括函数都是 public 且是 open 的,所以被继承的时候不需要加open
    fun eat(){

    }
}


//在kt 中,实习类不仅要重写接口的函数,也必须要重写接口的成员
class Dog(override var head: String, override var hand: String) :Anim{

    override fun eat() {
        super.eat()
    }
}

//重写成员的赋值
class Dog(override var head: String="123", override var hand: String = "123") :Anim{

    override fun eat() {
        super.eat()
    }
}


//因为接口不能有构造,所以这里的Anim 不想普通类需要写Anim()
class  Cat :Anim{

    //重写成员有两种写法
    override var head: String ="1"
     //这里每个变量的set 和get 都会持有 field, 而这个 field 就是 head
    // 但是 如果你不对 head 进行初始化一个值的, 直接 写  override var head: String   这个 set 和get持有的filed 就不存在,也就不能在set 或者get 里写 field
        get() = field
        set(value) { field =value }

    override var hand: String ="2"
        get() =field
        set(value) {
            field = value
        }

}
  1. by委托

接口委托

//当一个类实现了一个接口的时候,它可以不重写接口的方法,而是通过by 委托给 相同实现接口的 一个类对象
//by 关键字只能用于接口,抽象类不可以
class women (val person: Person):Person by person {



}

women(man())


//其实by 委托 本质上只是一个代理罢了,上面的代码就是下面的效果
class women (val person: Person):Person  {
    override fun show() {
        //只是把方法的实现调用委托对象的实现罢了
        person.show()
    }

    override fun eat() {
        person.eat()
    }

}

所以当你 一个类继续的接口的实现是 需要让一个对象去做相同的方法,那么就可以使用 by 对象

属性委托

  //这种就是属性委托,通过 by :: +属性
  // 加上 ::就是使用官方代码写的委托,不加上就得自己写委托实现
    //属性委托的本地也是代理。
    var otherName by ::name
 

    //我们知道属性的操作就是set 和get 方法
    //而属性的委托就是下面的代码效果
    //也就是调用 otherName的set方法其实就是调用了name 的set方法
    //调用 otherName的get方法其实就是调用了name的get方法
    //也就是说 你调用 otherName获取他的值,其实是获取name的值
    //你设置 otherName的值,其实是设置了name的值
    var otherName
        set(value) {
            name.set(value)
        }
        get() {
            name.get
        }

自定义属性委托

class Owner {

    //可以自定义委托
    //也就是把一个属性委托到一个类对象
    //上面我们知道属性的委屈其实就是属性的set get 方法会变成 委托对象的set 和 get 方法
    var name :String by Test1()

    
}


class Test1 (){

    var realName ="zjs"



  //所以就需要对类 重写一个 set /get 方法
    //重写的模板如下
    // 第一个参数要是被委托属性所存在的类,比如这里的 Owner
  // 第二个参数反射对象。
  // 第三个是 被委托属性的类型,比如 name 是 String 的,所以这里的 value 写String
 operator  fun set(owner: Owner,property: KProperty<*>,value:String){

 }

    // 第一个参数要是被委托属性所存在的类,比如这里的 Owner
    // 第二个参数反射对象。
    // 第三个是 返回类型是string
    operator  fun get(owner: Owner,property: KProperty<*>) :String{
        return realName

    }
}


//kt 还有一个写法封装类
//它可以帮你写重写的 set /get函数 。用法就是继承  ReadWriteProperty<T,V> 然后 T 写 被委托属性所存在的类,比如这里的 Owner V 写被委托的类型比如这里是String
class Test2 : ReadWriteProperty<Owner,String>{


    override fun getValue(thisRef: Owner, property: KProperty<*>): String {
        TODO("Not yet implemented")
    }

    override fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        TODO("Not yet implemented")
    }

}

泛型

  1. 泛型的意义,类泛型和函数泛型申明

泛型T的意思不代表就是Any类型,泛型 它真的作用和意义是,它是个未知类型?
当我 主动为一个类,为一个函数,多添加定义申明了一个泛型 T,这个T一开始就是未知类型?
未知类型也是一种类型,它跟Int,String,都是同一个概念 也就是,你可以把T当作一种类型,但是它不是Any类型
而当T被赋予一个指定类型后,相当于被激活了,后续这个T就是指定类类型,它跟Any类不是一回事

//类泛型的申明是在类后面加<T>
//其实就是为当前类定义了一个未知类型T,这个T的作用域可以在整个类当中使用
//如果你不申明这个<T> 是不可以 class Father(name:T)
//这里就是新定义申明了一个泛型<T>,并且后续才可以使用它,这里使用它做为主构造函数的函数是泛型T
 class Father<T>(name:T) {



    //函数的泛型申明 是在fun  <A>
    //其实就是为了这个函数定义了一个未知类型A,这个未知类型A的作用域在这个函数可以使用
    //这里是使用它作为参数的类类型是A,并且返回值的类型也是A
     fun <A>test(a:A):A {
         return a
     }




泛型的含义,并不是Any

//如果是说泛型A就是代表Any的意思,那比如下面的函数 test1 返回类型指定是 A, 但是传给它是  string 却会报错
fun <A>test1():A {
    return "zjs"
}

而你这么写,才是泛型的真正意义与用法

 fun <A>test(a:A):A {
     return a
 }

只有已经明确定义a是A类型,才可以返回A类型,不是任意类型都是可以当作A类型去返回

所以说 泛型的玩法是,类或者函数为自己领域内新添加定义一个任意的泛型 类 A,B,C…T , 他们的类型都是未知的 ?,它更多是用在当作一个参数 类型,等待别人任意类型去调用赋值它一种类型
但不代表任意类型可以当作他们

  1. 申明的泛型T,它的范围是包含null的也就是 ,你可以传一个null 给T,换个角度就是会有嫌疑T是null
    但是 正是因为他包含 null ,后续使用它的时候要?.
test(null)

  
 fun <A>test(a:A):A {

    a?.let {  }

     return a
 }

如果你申明了T,然后有传null的情况,但是你却写法上不去判null,就会很坑, 所以最好的写法是如果有null的嫌疑,就老老实实写T?
这样才不会忘了去判null,去?. 不然就是个坑

  1. 泛型类的继承:

//当一个父类定义了一个泛型T,那么继承的父类的子类们也必须定义一个泛型,也就是,子类必须把一个泛型传给父类,
所以子类也需要新定义一个泛型。

 open class Father<T>() {

}

//而子类定义的泛型不一定得写T,可以是A,.
// 因为这里的意义是,子类定义的泛型A,它的范围是任意未知类型,它跟父类定义T,的未知范围都是一样的,所以可以把A,传给T
//这里相当于,子类把A当作什么类型,父类也就跟子类一样把T当做什么类型
class Son<A>() :Father<A>() {

}
  1. 泛型的约束
//原来是为这个类多添加定义了一个任意未知类型T.,但是写成这样T:Father 代表,这个T 不能再是任意未知类型,而是继承Father的未知类型
class Test<T:Father>{

}

class Father {


}



//如果这个T有多个约束,那么用where写法
 fun<T>test(t:T)where T:CharSequence,T:Appendable{

    }

继承lambda的T

有一种写法是这样

//lamda本身就是一个函数类类型,那么这里定义的T就代表T也是一个函数类类型,并且限定T的参数是无参,无返回的函数
//那么item就是一个函数类类型的变量,所以调用invoke方法就是执行这个item的函数
fun<T:()->Unit>test(item:T){
    item.invoke()
}
        //那么使用就是把一个函数的引用对象给传过去
       test(::show)

      fun show(){
        }

定义的泛型继承的父类Father不能也定义着泛型 ,不能够写出这样的代码:


//这么写代表着,定义的C必须是Father的子类,也就是必须是一个 Son<A>类,而C又是Father的T,Father的T跟Son的A一样
//也就是 C== Son<A>, C==T==A == Son<A>,也就是 A 是一个Son<A>
//那么你这个时候像就会形成 Son<Son<Son<...>>>的死循环里去了
class Test<C:Father<C>>(obj: C){
}

 open class Father<T>() {


}


class Son<A>(a:A) :Father<A>() {


}

    fun show(){

    }

  1. out协变,in 逆变,以及不变

前面说当我们为一个类或者一个函数新定义一个泛型T,那么这个T类型就可以在定义的领域内使用,
并且 不写 in,out 的T类型对象它是可读可写的,也就是不变



//而定义 out T,就代表 在这个领域内,类型是T的对象是只能读不能写
//正式因为类型是T的对象是只能读不能写,所以 对象它只有get 方法,没有set 方法
//正是因为  对象它只有get 方法,没有set 方法,所以在这里的name,它只能定义为val,不能是var,或者不写,因为不写本身就是val
class Father<out T>(val name :T) {


    fun show():T{
     return  name
    }

}



//而 定义 in T,就代表 在这个领域内,类型是T的对象是只能写不能读
//正式因为类型是T的对象是只能写不能读,所以 对象它只有set 方法,没有get 方法
//正是因为  对象它只有set 方法,没有get 方法,所以在这里的name,它不能为val,也不能是var,不写也没意义,相当于没有里面是空的
class Son<in T>() {


 fun set(value:T){

 }



}
  1. out 协变,in逆变,不变 的继承关系。

在不管是java ,还是kt中 ,不变的情况 是不能够这么写的


//当泛型是不变的情况下,是不能够
var a :Test<Father> = Test<Son>() 子类充当父类用
var b :Test<Son> = Test<Father>() 父类 充当子类



class Test<T>(){
}

open  class Father{

}


class Son:Father() {

}

只有申明是out T.才可以

var a :Test<Father> = Test<Son>() 子类充当父类用

class Test<out T>(){
}

其实 out 在java 中就是 ? extends ,也就是这里就相当于

  Test<? extends Father> a = new Test<Son>();

只有申明是In T,才可以

var b :Test<Son> = Test<Father>() 父类 充当子类
class Test<in T>(){
}

其实 In在java 中就是 ? super,也就是这里就相当于

  Test<? super Son> b = new Test<Father>();

所以,in, out的知识只需要关注两点。
1 .定义 out/in 代表这个T类型的对象是可读还是可写
2. 定义 out/in 代表他们的泛型对象能够充当父类或者充当子类

  1. 在类或者函数定义泛型T,默认这个T,是可以传入null的,尽管他没有写成T? 但是T依旧是一个可以传入null的对象
    也就是你定义的T是默认 T:Any? 的
    所以,在使用的时候一定要加上?,不然会他是null而崩溃
fun<T>test(t:T){
    t?.show()
    
}

如果你希望你定义的T是不可null的,那么你手动写成 T:Any,这样这个T就不再为null,不在需要?

  1. 星号投影
//  MutableList<*> 跟MutableList<T> 是不一样的,
//  定义星号代表的这里并不关心它具体里面会是什么类型,也不会知道 她是什么类型
//而 MutableList<T>  的意义是 这个T肯定会是Any?下的一个类型

fun test(list: MutableList<*>){
    //正是因为  List<*> 不会知道 她是什么类型,所以他不能够去添加一个指定类型,因为都不知道是什么类型如何添加对应的类型就会报错
    list.add(1)
    //也就是 写*投影的 MutableList<*> 只能读不能写
    list.get(0)
    //当你写的泛型你完全不在意他会是什么类型,你只想读取他的值 ,而不想在里面添加任意类型的值的时候,就可以用星号投影

}

不要为使用星号投影后不能使用某些方法而吃惊,比如这里的add 就不能用,如果你需要调用这些方法,你要用的是常规的T,而不是 星号投影

扩展函数

  1. 可以在任何场所为一个类添加任意一个扩展函数,并且这个扩展函数可以直接用类的成员属性。
    它背后的逻辑其实是把这些新定义的扩展函数当作静态函数写在类下,所以你才可以使用
class Father(var name :String,var age:Int){
}


class Test{

    //可以在任何地方为类添加一个扩展函数,并且扩展函数可以直接用类的成员属性
    fun Father.show(){
        Log.d("zjs", "show: name $name age $age")
    }


}

原理:

public class Father {


    //背后代码是把外面定义的扩展函数给当作静态函数搬到类内部,所以才可以使用
    public static show(){
        Log.d("zjs", "show: name $name age $age")
    }
}
  1. 为泛型添加一个扩展函数
 //为泛型添加一个扩展函数,那么任意一个未知类型的都有这个函数
    fun<T>T.show(){
        //这里的this就是,哪个对象调用,this就是哪个对象
        this
    }

          123.show()
          "zjs".show()
  1. this范围和it 的区别
    //T.() 意思为为任何类型本身他自己再添加一个扩展函数,这个函数 叫做 ()->R
    //那么在()->R 函数的实现体内就是本身自己要做的事情 所以就是this.
   // {
   // }
    
    fun<T,R>myWith(input:T,mm:T.() ->R):R{
        return input.mm()
    }

 //而这里传进入的T 他是个 String ,也就是为String添加了一个函数()->R  所以这里的实现体内部本身就是他自己,就可以直接写
        //toString  length


        myWith(name){
            toString()
            length
        }



所以说为T对象类型 添加的扩展函数,this 就是哪个对象调用,this就是哪个对象  



    //而这里是一个高级函数他参数是T,那么肯定实现体内部,就是这个参数
    //T.xxxx

    fun<T,R>myWith2(input:T,mm:(T) ->R):R{
        return mm(input)
    }

     myWith2(name){
            it.toString()
            it.length

        }
  1. 因为前面可以为泛型类添加一个扩展函数,在工作中,常常会利用此写一些工具函数,在工作中会把,所以的工具函数放在一个文件里,这个文件不需要是一个类文件,而是单单一个文件,

在这里插入图片描述

Kt注解

  1. 在kt中定义一个注解是这样的
annotation class TestKt()



//而如果这个注解有属性的话就是
annotation class TestKt(val name :String)

而在java中定义注解是这样的 用   @interface
public @interface TestKt {
  String name;
}
  1. kt 定义的注解依旧可以使用元注解,含义跟java 的注解一样这里就不再赘述
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
annotation class TestKt(val name :String)
  1. 可以通过注解,跟换一个kt文件的名字,这样后续的就换为更换的名字了
//这里的注解一定要卸载包前面,这样子这个文件的名字就换成了ZjsUtil
@file:JvmName("ZjsUtil")


package com.example.myapplication


fun<T>T.show(){
}
  1. 在kt 中为属性 添加注解 @JvmField 的作用
class Test{

    //当我们在Kt代码中为写一个属性的时候
    val name = "zjs"

}

//转成java是这样的
public  class Test{

    //name 字段是私有的,别人要调用需要调用的是 getName
    private final  String name = "zjs"

    public String getName(){
        return this.name
    }

}

所以在java 的代码中 kt 文件的代码就得这么写

new Test().getName 才能拿到属性。

但是你在kt 中为这个属性加注解 @JvmField

class Test{

    //当我们在Kt代码中为一个属性写注解时候
    @JvmField
    val name = "zjs"



}

//转成java是这样的
public  class Test{

    //name 字段是public,
    public final  String name = "zjs"

}

这样在java代码就就可以直接拿到name属性了

new Test().name 
  1. 在函数上添加 @JvmOverloads的作用
class Test {

    //kt可以不传参数默认有值
    show()
}

//在kt 中有参数默认值机制
fun show(name :String="zjs",age:Int=14){

}

但是在java类中,它想要调用这个show() 也不传参数进去,是不允许的,如果java也想要有这个效果就需要在kt 中加个注解 @JvmOverloads

@JvmOverloads
fun show(name :String="zjs",age:Int=14){

}
  1. 注解 @JvmStatic
class Test {


    //在kt中的派生对象里写属性和方法
    companion object{
    
        val name = "zjs"
        
        fun show(name :String="zjs",age:Int=14){
        }
    }
//在kt中,可以直接这么写
Test.name
Test.show
//但是在java类中想要调用就不可以
//需要写成
Test.Companion.getName()
Test.Companion.show()
但是如果加上对应的注解就可以像kt中直接写
    Test.name
    Test.show

class Test {


    //在kt中的派生对象里写属性和方法
    companion object{

        @JvmField
        val name = "zjs"


        @JvmStatic
        fun show(name :String="zjs",age:Int=14){
        }
    }

}

kotlin知识其他

  1. 运算符重载 operator

比如 + 号运算符 对应的 函数是叫做 plus(),那么如果你在某个类里重写这个函数,或者使用扩展函数为某个类重写 plus()
这个类的对象就可以直接用写法 + 去写 代码,这两种方式的plus 函数 都需要用 operator 修饰

在Student 里添加一个运算符函数重载

data class Student(var name:String,var age: Int){


    //为Student添加 + 运输符函数,使用 operator 操作符
   operator fun plus(student: Student):Int{
      return age+student.age

   }


}

//后续每个Student的对象就可以直接调用 + 函数,而+ 后面的就是 函数plus的参数
var all =  Student("zjs",24) + Student("xiaoming",26)

或者直接为Student 添加扩展函数plus,而不是在Student类里面去写 ,都是一样的需要加上operator 操作符

 //为Student添加 + 运输符函数,使用 operator 操作符
    operator fun Student.plus(student: Student):Int{
        return age+student.age
    }
    
//后续每个Student的对象就可以直接调用 + 函数,而+ 后面的就是 函数plus的参数
var all =  Student("zjs",24) + Student("xiaoming",26)
  1. 枚举类

在kt 中 枚举也是一个 类

enum class Week { 
    星期一,
    星期二,
    星期三
       ;  //需要用分号结束枚举的值
}

var one  = Week.星期一
var two  = Week.星期二

//这里打印出来显示的是    Week.星期一, Week.星期二,而不是 1,2
Log.d(TAG, "one:  $one ,two $two")

//枚举的值等于枚举类本身
var a  = one is Week
//打印true
Log.d(TAG, "one:  $one") 

枚举的值等于枚举对象本身,所以值也需要用写主构造参数

enum class Week(val  number:Int) {
    星期一(1),  //所以值也需要用写主构造参数
    星期二(2),
    星期三(3)
    ;  //需要用分号结束枚举的值

    fun showNumber():Int{
        return number
    }
}

使用如下

var a = Week.星期一.showNumber()

使用枚举的when

  //由于这里的test 函数参数是week ,已经明确是是week,所有它只有  星期一 星期二 星期三,这3 种可能。不用不需要写 else 也不会报错
    fun test(week: Week){
        var a = when(week){
            Week.星期一 -> 1
            Week.星期二 -> 2
            Week.星期三 -> 3
        }
    }
  1. sealed 密封 类
//一个类添加 sealed 修饰 是代表这 这个类是密封类, 它只有 它的成员 其中一个的可能,绝对不会是其他的可能
//使用场景大多数是 判断 是否为指定 子类的一种
sealed class People{

    object Boy :People()  //object 代表这个子类成员 Boy 是单例类
    class Girl(var age:Int) :People() // 单纯是 class 就 girl 不是单例类,一般如果 主构造 有参数的话 不应该申明 是  object  是单例类

}




  //由于这里的people 是密封类,所以除了这两种情况不会有其他的,所以也就不需要else
    fun test(people: People){
        var a = when(people){

            is People.Girl -> 1
            is People.Boy-> 2


        }
    }

    test(People.Boy)
    test(People.Girl(18))

还有另外的显示如下

在这里插入图片描述

枚举 和 密封类 区别 看 这篇文章
https://juejin.cn/post/7044708611544596516
大概如下:
枚举类的每个枚举值的是 值,是对象,而密封类是每个类类型
枚举类的每个枚举值 不能有属于自己的唯一属性,每个枚举值都是要一致的,
而密封类 的每个类都可以有属于自己的属性,
从性能上来说
枚举不会被垃圾收集,它们会在您的应用程序的整个生命周期中保留在内存中,
密封类的子类型的对象像常规类的对象一样被垃圾收集

反射

在Java中使用Class很常见的就是,xxx类.class,比如我们在startActivity的时候

startActivity(new Intent(this, OtherActivity.class)); 

这里接收的就是CLass<?> cls参数

而在kt中。这里就应该写成 OtherActivity::class.java

startActivity( Intent(this, OtherActivity::class.java)); 

在java 中获取Class的方法有

1、Class c = person.getClass(); //对象获取
2、Class cc =Person.class;//类获取

而kotlin写法有多种

//对象获取
person.javaClass// javaClass
person::class.java // javaClass
//类获取
Person::class// kClass
 person.javaClass.kotlin// kClass
(Person::class as Any).javaClass// javaClass
Person::class.java // javaClass

其实他们的区别是,
person.javaClass == person::class.java == Person::class.java 他们是一样的,都是属于 java的Class
而 person::class == Person::class 他们是一样的, 是属于,kotlin自己的Class叫做KClass 。

所以说 person::class 是KClass , 接着从KClass 在转为java 的class 就是 person::class.java

也就是带java 的是java class,不带是KClass

协程

协程依赖配置

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'

**

关于协程很好的文章

协程很好的文章

协程的开启

开启一个协程,用的api 基本调用的是 launch 和 aync ,
区别在于 launch 返回 job对象 , aync 返回 Deferred对象
Deferred 是 job 的子类。
job 不能获取返回结果
Deferred 调用awit能获取返回结果

launch

launch 是一个非阻塞的调用,它会 启动一个新的协程去做协程内的事情 然后 立即返回,也就是, 调用 launch 不会等待launch{}内的携程做完,而是直接返回,直接调用代码下面的逻辑

fun main() {
   

    runBlocking {

        //在这里调用开启一个协程,然后就立即返回 立即 跑到下面的 的launch在开另外一个去了
        //不会等到协程执行完才返回去调用下面的launch
        launch {

            for (i in 1..100){
                delay(100)
                println("main:第一个协程 $i")
            }

        }
        
        
        launch {

            for (i in 1..100){
                delay(100)
                println("main:第二个协程 $i")
            }

        }
    }
}

所以也就造成了上面两个协程在一起并行做事情。

aync
aync 跟 launch 一样是个 非阻塞的调用,一样是 启动一个新的协程 然后 立即返回

fun main() {


    runBlocking {

       println("调用第一个 async")
       var deferred =  async {
            var  result = 0
            for (i in 1..100){
                println("第一个 async 执行  $i ")
                delay(100)
                result +=i
            }
            //最后一行作为async 内 耗时操作最后的返回值
             result 
        }

        println("调用第二个 async")

        var deferre2 = async {
            var result  = 0
            for (i in 1..100){
                println("第二个 async 执行  $i ")
                delay(100)
                result +=i
            }
           //最后一行作为 async  内 耗时操作最后的返回值
           result 
        }
}

上面也一样,一旦调用,两个协程会并行执行
日志输出调用顺序执行为:
在这里插入图片描述
上面的 async 会返回一个 deferre对象, 并且最后一行就是返回结果 ,而想要这个返回结果
就得调用 deferre.await 方法 获取返回结果,而 await 就是个 挂起函数

在说 await 之前我们说话 ,什么是挂起函数
挂起函数就是,他会阻塞当前协程,等待挂起函数的内容做完,再做挂起函数后面的事情,
但是挂起函数所在的协程被阻塞了,协程外的协程或者线程是不会被阻塞的。这就是挂起函数

fun main() {


     GlobalScope.launch{

       println("调用第一个 async")
       var deferred =  async {
            var result = 0
            for (i in 1..100){
                println("第一个 async 执行  $i ")
                delay(100)
                result+=i
            }

           result
        }

        println("调用第二个 async")

        var deferre2 = async {
            var result = 0
            for (i in 1..100){
                println("第二个 async 执行  $i ")
                delay(100)
                result+=i
            }

            result
        }


        println("调用第一个 await  ")
        //在这里调用了 await 会挂起当前协程,也就是得等到 await 方法做完,获取到结果,下面的代码才会执行
        //但是,携程外的线程不阻塞继续运行
        var result = deferred.await()


        println("上面的挂起函数 await 1 执行完了 得到结果了 我才调用")
        println("上面的挂起函数 await 1 执行完了 得到结果了 结果是  $result")

    
        println("第二个 await  ")
        //在这里调用了 await 会挂起当前协程,也就是得等到 await 方法做完,获取到结果,下面的代码才会执行
        //但是,携程外的线程不阻塞继续运行
        var resul2 = deferre2.await()


        println("上面的挂起函数 await 2 执行完了 得到结果了 我才调用")
        println("上面的挂起函数 await 2  执行完了 得到结果了 结果是  $resul2")

    
    }

    for (i in 1..1000){
        Thread.sleep(100)
        println("上面的协程在挂起也不影响我协程外的线程做事 $i")
    }

}

在这里插入图片描述
在这里插入图片描述

协程作用域

前面我们说了启动个携程 用的是launch 或者async 。
但是启动他们的前提是 是在一个携程作用域下的才可以使用 launch 或者async

CoroutineScope 它就是代表了一个协程的作用域,你可以创建自定义的CoroutineScope

比如 说指定一个job,指定一个协程名字,
指定一个启动模式
指定 上下文CoroutineContext,
其中 CoroutineContext上下文就包括

调度器 CoroutineDispatcher
协程拦截器ContinuationInterceptor,
协程的异常捕获器 CoroutineExceptionHandler

而多个指定 用 + 去添加

var job = Job()
//自定义一个 coroutineScope,多个 指定 用 +
var coroutineScope :CoroutineScope =   CoroutineScope(job +  CoroutineName("协程名称")
        + CoroutineStart.LAZY
        + Dispatchers.IO)


coroutineScope.launch {
    
}

coroutineScope.launch {
    
}

//取消全部协程
job .cancel()

有时候指定的job 会是 SupervisorJob

SupervisorJob 是一个特殊的 Job 类型,可以叫做父协程,当一个 SupervisorJob 父协程 被取消时,它的子协程不会立即被取消,而是会收到一个取消信号,并且它们可以选择如何处理这个取消信号,
经常就是 进行finally 的清理操作,之后再去取消 ,也就是说当 SupervisorJob 调用取消的时候,它子协程也会被取消,只是子协程会多做一步清理操作,好比下面的代码实例, 父亲取消协程后,子协程也会取消没有做完,但是会调用 finally 做清理操作。

而如果父协程只是单单个 Job,而不是 SupervisorJob ,父协程取消后, 子协程会直接取消不会做清理操作

fun main() {

   runBlocking {
    // 创建一个 父协程
    val supervisorJob = SupervisorJob()

    // 使用这个  父协程 SupervisorJob 创建一个协程作用域
    val scope = CoroutineScope(supervisorJob + Dispatchers.Default)

    // 在这个作用域中启动一个子协程
    val childJob = scope.launch {
        try {
            // 子协程做2s耗时任务
            delay(2000)
            println("子协程做完啦")
        } finally {
            // 清理操作
            println("子协程做完清理操作")
        }
    }

    // 等待一段时间后取消 SupervisorJob
    delay(1000)
    println("取消父协程")
    supervisorJob.cancel() // 取消 SupervisorJob

  }

}

其他协程作用域

  1. runBlocking , runBlocking 会阻塞当前线程,也就是一定得等 runBlocking {}内的东西 ,下面的才会执行
fun main() {


    runBlocking { 
        
        launch { 
            delay(100000)
        }
        
    }


    println("只有等上面的 runBlocking{}内的东西做完,下面的代码才会执行 $i")
    for (i in 1..1000){
        Thread.sleep(100)
        println("协程外的线程做事 $i")
    }

}

当然你可以切换到io线程,就算阻塞了没什么关系

  runBlocking (Dispatchers.IO){  }
  1. GlobalScope
    GlobalScope 开启协程环境,他不会 像上面的 runBlocking 阻塞当前线程
    也就是不需要 等到 GlobalScope {} 内的东西做完,下面的才会执行
    而是, GlobalScope {} 内东西一边执行 ,GlobalScope {} 外的东西也会继续一边执行
    但是要注意,协程是依赖线程开启的,除了上面runBlocking 比较特殊
    CoroutineScope 和 GlobalScope 都是以一样的

如果 CoroutineScope GlobalScope {} 外的东西 比 GlobalScope {} 内东西更快执行结束,那么不管 CoroutineScope GlobalScope {}内的代码如何,直接自动结束 GlobalScope {}内的代码

fun main() {


    GlobalScope.launch {

        for (i in 1..100){
            delay(200)
            println("协程内的做事 $i")
        }

    }

    println("不需要等上的  GlobalScope{}内的东西做完,下面的代码依旧会执行 ")
    for (i in 1..100){
        Thread.sleep(100)
        println("协程外的线程做事 $i")
    }
    println("协程外的线程做事结束")

}

在这里插入图片描述
在这里插入图片描述

协程的进阶

开启一个协程也就是 launch 函数参数 需要三样东西,分别是 上下文 CoroutineContext 、启动模式CoroutineStart 、协程体也就是协程内要做的事情

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

正是因为 launch函数参数有默认值,所以我们可以直接写

launch{
//携程体
}

启动模式,我们最常用的其实是 DEFAULT 和 LAZY 。

默认参数就是 DEFAULT。他代表着一旦申明这个携程,他就可以等待执行。
而不需要像 线程一样需要 申明后启动的时候再去 myThread.start() ,也就是 不需要 job.start()

另外启动模式就是 LAZY,就需要自己去申明这种模式

launch(start = CoroutineStart.LAZY){
//携程体
}

这种模式就跟上面的 DEFAULT 不一样了,申明之后,就需要调用 像线程一样 需要调用 job.start() 或者 ,job.join() 才会等待执行。

不过 线程的 myThread.join() 他是需要在 myThread.start() 开启后执行 myThread.join() 才有效果,不然没啥作用

而 协程不需要,不先调用 job.start() 开启,直接调用 job.join() 他也会启动携程,

join的作用一样是需要等待线程或者协程内的东西做完,才继续做 join后面的代码逻辑。比如

log(1)
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
    log(2)
}
log(3)
job.join()
log(4)

打印的结果就是

14:47:45:963 [main] 1
14:47:46:054 [main] 3
14:47:46:069 [DefaultDispatcher-worker-1] 2
14:47:46:090 [main] 4

一定是先打印2,再打印4,因为 job.join()的意思就是,一定要等到 job里面的东西做完,才做 job.join()后面的东西

协程上下文 CoroutineContext

CoroutineContext 其实就是一个list 集合,他每个元素叫做 Element ,而每个 Element 在 这个list的 索引叫做key 。也就是 你可以根据这个key 从list 都找到对应的 Element。

并且 list 下的每个元素 Element 又是一个 CoroutineContext ,也就是又是一个list 。 相当于n 层list 。

通常我们见到的上下文的类型是 CombinedContext 或者 EmptyCoroutineContext,一个表示上下文的组合,另一个表示什么都没有。

协程上下文 CoroutineContext 包括几种,
其中就有 调度器 CoroutineDispatcher ,协程拦截器ContinuationInterceptor ,
协程的异常捕获器 CoroutineExceptionHandler

  1. 调度器 CoroutineDispatcher

调度器 CoroutineDispatcher 他主要是属于拦截器的一种,他的作用就是指定,你协程里面内容是要放在哪个线程。 具体就是在 dispatch 方法里面进行的指定操作。

这样的话 你就可以创建个线程,然后利用 CoroutineDispatcher 指定携程里面的东西放在 该线程下

suspend fun main() {
    val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
    GlobalScope.launch(myDispatcher) {
        log(1)
    }.join()
    log(2)
}

输出的信息就表明协程运行在我们自己的线程上。

不过 一般 android下为我们提供好了常见的 分发器,里面就表明了指定线程,比如

Dispatchers.Default、Dispatchers.IO和 Dispatchers.Main

Dispatchers.Default表示会使用一种默认低并发的线程策略,当 你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率,此 时就可以使用Dispatchers.Default。Dispatchers.IO表示会使用一种较高并发的线程策 略,当你要执行的代码大多数时间是在阻塞和等待中,比如说执行网络请求时,为了能够支持 更高的并发数量,此时就可以使用Dispatchers.IO。Dispatchers.Main则表示不会开启 子线程,而是在Android 主线程中执行代码。

  1. 协程拦截器ContinuationInterceptor

我们说协程的本质就是回调 + “黑魔法” ,而这个回调就是 Continuation,
也就是最终的数据会给到 Continuation,再由 Continuation 去调用resumeWith方法返回数据
而 协程拦截器 就是对Continuation 的拦截,所以叫做 ContinuationInterceptor。 他好比 OkHttp 的拦截器
我们可以自己定义一个拦截器放到我们的协程上下文中,看看会发生什么


class MyContinuation<T>(val continuation: Continuation<T>): Continuation<T> {
    override val context = continuation.context
    override fun resumeWith(result: Result<T>) {
        log("<MyContinuation> $result" )
        continuation.resumeWith(result)
    }
}


class MyContinuationInterceptor: ContinuationInterceptor{
    override val key = ContinuationInterceptor
    override fun <T> interceptContinuation(continuation: Continuation<T>) = MyContinuation(continuation)
}


在这里我们先自定义一个 回调 MyContinuation , 在 resumeWith 方法中 添加打印,
然后自定义 回调的拦截器 MyContinuationInterceptor ,
回调拦截器 MyContinuationInterceptor里面用的是我们的自定义的 MyContinuation。

接着我们 在开启协程的时候指定我们自定义的拦截器

  var deferred= async  (MyContinuationInterceptor()){

      }

      deferred.await()

这样当调用 deferred.await() 这个 await 挂起函数的时候,获取结果前就会先执行我们的 回调的拦截器 MyContinuationInterceptor 里面的 MyContinuation 里面的 打印日志代码。

  1. 协程的异常捕获器 CoroutineExceptionHandler

我们可以创建个 CoroutineExceptionHandler 然后在 launch 的指定协成内的异常由这个 异常捕获器捕获



    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        log("Throws an exception with message: ${throwable.message}")
    }

    
    GlobalScope.launch(exceptionHandler) {
        throw ArithmeticException("Hey!")
    }.join()
 

但是这种 CoroutineExceptionHandler 他只能用于launch启动的携程,对于async 启动的携程抛出的异常,他是不会捕获的,
还是得用try catch 的方式,
也就是
CoroutineExceptionHandler 用于 launch
而 try catch launch 和 async 都可以

异常传播

coroutineScope 是继承外部 Job 的上下文创建作用域,在其内部的取消操作是双向传播的,子协程未捕获的异常也会向上传递给父协程。它更适合一系列对等的协程并发的完成一项工作,任何一个子协程异常退出,那么整体都将退出,简单来说就是”一损俱损“。这也是协程内部再启动子协程的默认作用域,也就是默认规则就是 coroutineScope

GlobeScope 启动的协程“自成一派” 但是, GlobeScope下 启动的子协程a 内部再启动一个子协程b ,那么a b 之间的规则就是默认作用域规则,也就是 coroutineScope 的规则。

   GlobalScope.launch {

         Log.d("zjs", "GlobalScope All  ")

             GlobalScope.launch {

                 Log.d("zjs", "GlobalScope A  ")

                 launch {

                     Log.d("zjs", "launch 1: ")

                     launch {

                         Log.d("zjs", "launch 2: ")


                     }


                 }
                 
             }



             GlobalScope.launch {

                 Log.d("zjs", "GlobalScope B  ")

             }


         }

launch 2 崩了肯定的导致 launch 1 崩溃,而 launch 1崩溃会导致 GlobalScope A 崩溃,但是
GlobalScope A 崩溃不会导致他上面的 GlobalScope All,因为 GlobeScope 启动的协程“自成一派“
不会影响到他的父作用域。

supervisorScope 同样继承外部作用域的上下文,但其内部的取消操作是单向传播的,父协程向子协程传播,反过来则不然,这意味着子协程出了异常并不会影响父协程以及其他兄弟协程。它更适合一些独立不相干的任务,简单来说就是”自作自受“,需要注意的是,supervisorScope 内部启动的子协程内部再启动子协程,如无明确指出,则遵守默认作用域规则,也即 supervisorScope 只作用域其直接子协程。也就是说

 supervisorScope.launch {

                 Log.d("zjs", "supervisorScope A  ")

                 launch {

                     Log.d("zjs", "launch 1: ")

                     launch {

                         Log.d("zjs", "launch 2: ")


                     }


                 }

             }

launch 2 崩了肯定的导致 launch 1 崩溃,而 launch 1崩溃不会导致 supervisorScope A 崩溃。

挂起函数

  • 挂起函数 withCotext

    fun main() {

    var job =Job()
    var coroutineScope = CoroutineScope(job)
 
       //withContext
        coroutineScope .launch {

            //withContext也是个挂起函数 ,也就是把withContext里面的东西完全完成,才继续协程后续的流程
            //一般是用它来切换其他线程,在线程执行完毕后自动切回原来线程
            //表示{}内的要在指定线程完成,在继续下面的事情
            withContext(Dispatchers.IO){

            }
        }
 
  • 挂起函数 delay

delay 也是个挂起函数,会阻塞当前所在的协程,但是不阻塞当前所在的线程

  • 挂起函数 suspend

当一个函数被定义成 suspend 的时候这个函数就是一个 挂起函数,也就是
会阻塞当前所在的协程,但是不阻塞当前所在的线程

在挂起函数里面可以做耗时操作 ,并不会阻塞当前线程执行,

为什么说挂起函数做耗时操作不会阻塞当前线程就算是UI主线程调用也没事

 suspend fun guaqihans():String{
        //做耗时操作 获取结果
        return  "result"
    }
   
  var result = guaqihans()
        

你可以理解为 当一个函数被定义为挂起函数,内部默认创造了 一个 Continuation 实例,这个Continuation实例的作用其实相当于Callback回调接口, 借助这个 Continuation 回调 把耗时操作的result 回调给 挂起函数,成为挂起函数的返回值
好比

public interface Callback<T> {
  void onResponse(Call<T> call, Response<T> response);
  void onFailure(Call<T> call, Throwable t);
}

他就是

@SinceKotlin("1.1")
public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resume(value: T)
    public fun resumeWithException(exception: Throwable)
}

函数体里的耗时任务其实还是在其他线程里面做的,而当其他线程做完耗时操作得到的结果不管是异常还是正确结果 都会给到 Continuation这个实例,

当获取结果不是异常就会调用 Continuation.resume(耗时操作结果数据)
当获取结果是异常 就会调用 Continuation.resumeWithException(异常)

 //注意以下并不是真实的实现,仅供大家理解协程使用
    fun guaqihans(continuation: Continuation<String>): String{
        ... // 切到非 UI 线程中执行
        try {
            val result = 做函数体的内容得到结果
            //   切换回原来调用的线程, Continuation调用 resume方法把 耗时操作的结果给到挂起函数的返回值
            handler.post{ continuation.resume(result) }
        } catch(e: Exception) {
            handler.post{ continuation.resumeWithException(e) }
        }
    }

所以说看到现在,定义一个挂起函数其实就是,内部 搞了个其他线程去做函数体的内容,然后把结果给到 Continuation , 并且切回原来的线程 ,Continuation 调用对应的方法, 把结果当做挂起函数的返回值。

当一个函数被定义 suspend挂起函数,上面的操作都是内部逻辑的,默认实现的,也就是 continuation默认会调用resume把参数当做函数的返回值值等这些操作,那如果我们想自己操作呢

如何想拿到 suspend 函数里面的 这个 continuation呢。

其实就是加上 suspendCoroutine《返回值类型T》 {}

    suspend fun  guaqihans() = suspendCoroutine<String> { continuation ->

        doHaoshicaozuo{
            continuation.resume(it)
        }

    }

    fun doHaoshicaozuo(callback:(String) ->Unit){
        //做耗时操作做完把结果回调回去
        var  result = "result"
        callback(result)
    }

在 suspendCoroutine {}闭包里面我们就可以拿到这个 continuation ,当耗时任务做完把结果回调出来 ,这个结果就it ,那么continuation调用 continuation.resume(it) 就可以把结果当做 挂起函数的返回值。 var result = guaqihans() 拿到这个 结果。

suspend 的颜色规则

当一个函数用了suspend函数,那么它底下一层一层的函数也需要用suspend

class Repository {

    //获取数据用关键字 suspend
    suspend fun getData():Data{

        //  Repository 的获取数据有两类,一类是网络,一类是数据库
        if(isRoom){
            return  RoomData().getData()
        }else{
            return  NetworkData().getData()
        }

    }
}

class RoomData {
    //进行数据库获取数据
   suspend fun getData(){

    }
}

suspendCancellableCoroutine

前面说了我们的可以通过 suspendCoroutine 拿到挂起函数的 continuation 对象,
而使用 suspendCancellableCoroutine 也是可以拿到 挂起函数的 continuation 对象 ,只不过加了一层封装,包装成了一个 CancellableContinuation。他依旧是有 continuation的功能,额外的功能是
通过调用它的 invokeOnCancellation 方法可以设置一个取消事件的回调,
作用就是,当这个协程被取消了,那么 invokeOnCancellation这个方法里面的回调内容就会被调用

打个比方,我们从上面的携程也知道,我们都是开一个携程,然后在里面调用 挂起函数,然后挂起函数里面 做异步操作, 当这个携程被 job.cancel 取消,那么异步任务是不是也要跟着取消,所以利用 suspendCancellableCoroutine 的作用就是 当 携程被 job.cancel 取消,通过 CancellableContinuation 的invokeOnCancellation 方法,相当于监听 携程被 job.cancel 取消,就把异步操作也给取消了。

suspend fun getUserCoroutine() = suspendCancellableCoroutine<User> { continuation ->
    val call = OkHttpClient().newCall(...)

// 携程被取消监听,异步任务call 也要跟着取消
    continuation.invokeOnCancellation { 
        log("invokeOnCancellation: cancel the request.")
        call.cancel()
    }

    call.enqueue(object : okhttp3.Callback {
        override fun onFailure(call: Call, e: IOException) {
            log("onFailure: $e")
            continuation.resumeWithException(e)
        }

        override fun onResponse(call: Call, response: Response) {
            log("onResponse: ${response.code()}")
            response.body()?.let {
                try {
                    continuation.resume(User.from(it.string()))
                } catch (e: Exception) {
                    continuation.resumeWithException(e)
                }
            } ?: continuation.resumeWithException(NullPointerException("ResponseBody is null."))
        }
    })
}


val job = launch { 
    val user = getUserCoroutine()
}

job.cancel()

Android 领域的协程

在android中,我们常见的场景需求就是,在非主线程执行耗时操作,然后把结果返回主线程,然后再到主线程 拿返回的结果 进行 ui操作

在以前的写法就是利用handle 比如

typealias Callback = (User) -> Unit

fun getUser(callback: Callback){
    ...
}



getUserBtn.setOnClickListener { 
      Thread{
            getUser { user ->
                handler.post {
                    userNameView.text = user.name
                }
            }

        }.start()
}

开一个线程去进行耗时操作  getUser ,然后得到结果后 ,利用 handler 切换到主线程 ,把结果给到主线程
 

现在有了协程 我们就可以直接

suspend fun getUser() 


getUserBtn.setOnClickListener {
    GlobalScope.launch(Dispatchers.Main) {
        userNameView.text = getUser()
    }
}

也就是直接对耗时操作的getUser 定义 suspend ,正因为 对getUser 定义 suspend,,所以需要开启 一个协程作用域。
而这个作用域是 主线程,这样子才可以做在 作用域内做 ui操作 。 就是这么简单, 只要 开一个 上下文是主线程的协程,然后对耗时操作做定义 suspend 即可,而 以前那些老套的 需要开个线程做操作,然后开完线程还得回切主线程等操作。所以说协程的魅力就在于此,用同步的代码写出异步的效果

通过上面你也知道原理是啥, 或者你如果说你想得到这个 continuation 。也可以写成如下的形式:

suspend fun getUserCoroutine() = suspendCoroutine<User> {
    continuation ->
    getUser {
        continuation.resume(it)
    }
}

getUserBtn.setOnClickListener {
    GlobalScope.launch(Dispatchers.Main) {
        userNameView.text = getUserCoroutine().name
    }
}

开协程 launch 的时候表明的是主线程 ,但是 getUserCoroutine 内其实是个挂起函数,他会在内部开另外线程去做耗时操作 getUser
然后把结果给到 continuation 。 continuation 就可以调用 resume 把 结果 当做 挂起函数 的返回值。
当调用 userNameView.text = getUserCoroutine().name 的时候,因为launch(Dispatchers.Main) 已经切到了主线程了。

使用 MainScope
上面的操作我们用的作用域是 GlobalScope,而Android为我们提供了一个更好的 作用域就是
MainScope,他其实就是 CoroutineScope ,只不过表明指定了上下文而已,它的异常传播是自上而下的,这一点与 supervisorScope 的行为一致,

public fun MainScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

所以我们可以直接用

suspend fun getUserCoroutine() = suspendCoroutine<User> {
    continuation ->
    getUser {
        continuation.resume(it)
    }
}


       val mainScope = MainScope()
       mainScope .launch {
        userNameView.text = getUserCoroutine().name
 
}

所以说 比如 如何开个协程 里面做耗时 getAllStudent() 然后 获取到 getAllStudent的结果

class MainActivity : AppCompatActivity() {

    private lateinit var result :List<String>


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        val mainScope = MainScope()
        mainScope.launch(Dispatchers.Main) {
             result = getAllStudent()
        }
    }



    suspend fun getAllStudent(): List<String> {
        delay(200)
        return listOf("学生1", "学生2", "学生3")
    }

jetpack + 协程

  1. 每个 ViewModel 都 已经定义了 ViewModelScope。
    所以在 ViewModel 内 携程作用域可以直接 使用 viewModelScope 开协程
    ,使用他的好处是,当 ViewModel 被销毁的时候,他所作用域开启的协程都会被自动取消

下面就是典型的jetpack + MVVM + 协程

@HiltViewModel
class MainViewModule @Inject constructor( application: Application):AndroidViewModel(application){

    @Inject
    lateinit var repository:MyRepository

     var liveDataStudent: MutableLiveData<List<Student>>? = MutableLiveData<List<Student>>()
    
    
    fun getData(){
        viewModelScope.launch(Dispatchers.Main) {
            liveDataStudent.setValue(getAllStudent())
  
        }
    }

    // getAllStudent 是耗时操作
    suspend fun getAllStudent() :List<Student>{
        return   repository.getAllStudent()
    }
  1. 每个 Lifecycle 对象内 都有个 LifecycleScope 实例对象 ,可以通过他开启协程
    在 Lifecycle 被销毁时取消,通过 LifecycleScope 开启携程 也会被自动取消
    也就是 处于 生命周期是 DESTROYED 状态时自动取消

可以通过 lifecycle.coroutineScopelifecycleOwner.lifecycleScope
获取 LifecycleScope 实例对象 来开启携程,而由于activity 就是个 lifecycleOwner ,所以可以直接调用 lifecycleScope 去开启携程 ,或者 调用 lifecycle.coroutineScope 开启,一样的

class MainActivity : AppCompatActivity() {

   
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        getData()
    }


    fun getData(){
        lifecycleScope.launch {
           
        }
        
        lifecycle.coroutineScope.launch{
            
        }
    }

LifecycleOwner 的协程开启方式与生命周期

  1. lifecycleScope.launch{}

LifecycleOwner 被观察者 一般是 acitivity service ,
每个 LifecycleOwner 他都有个 lifecycleScope

LifecycleOwner.lifecycleScope: LifecycleCoroutineScope

很多时候都会拿 acitivity service 的 lifecycleScope 来开协程

lifecycleScope.launch{}

lifecycleScope.launch{} 开启的协程,会一直存在,只有当 LifecycleOwner 被销毁的时候,也就是 ,当前 Activity 或 Fragment 销毁时 才会 销毁清理 此协程,lifecycleScope.launch{} 开启的协程 协程内容结束了,结束并不意味着它会被立即清理;它只是不再继续执行任何代码
只有当 LifecycleOwner 的生命周期状态销毁或手动取消才会 进行清理

好处:避免泄露:当 lifecycleOwner 进入 DESTROYED 时, lifecycleScope 结束协程

  1. lifecycleScope.launchWhenXXX {}
    比如说:
    通过 lifecycleScope.launchWhenStarted 开启的协程,

当 LifecycleOwner 是第一次处于 STARTED 状态下,会启动一个协程,而后续,处于其他状态下,他不会取消这个协程,只是单纯的挂起等待协程,当LifecycleOwner 从 STARTED 状态变为其他状态(如 RESUMED 或 PAUSED) 后面 再回到 STARTED 状态时,那么他还会重新恢复执行,而并非重新再创建一个新的协程,只有当 LifecycleOwner 被销毁的时候,也就是 ,当前 Activity 或 Fragment 销毁时 才会 销毁清理 此协程,并且后续 Activity 再次开启,他才会再次创建一次。

好处:
避免泄露:当 lifecycleOwner 进入 DESTROYED 时, lifecycleScope 结束协程
节省资源:当 lifecycleOwner 重新进入或者退出某个生命周期的时候,不会重新创建销毁协程,只是单纯的恢复和挂起,
每次 当lifecycleOwner 重新进入或者退出某个生命周期 创建销毁协程是会浪费资源的。

坏处:
不要用 launchWhenXXX 开启协程在协程中,collect 订阅冷流

lifecycleScope.launchWhenStarted {
    flow<Int> {
        delay(3000)
        emit(1)
    }
        .collect {
            Log.d("zjs", "collect:   ")

        }
}

对于 launchWhenXXX 来说, 当 lifecycleOwner 离开 XXX 状态时,协程只是挂起协程而非销毁,如果用这个协程来订阅 Flow,就意味着虽然 Flow 的收集暂停了,但是上游的处理仍在继续,资源浪费的问题解决地不够彻底。

  1. LifecycleOwner.repeatOnLifecycle()

LifecycleOwner类有个 suspend方法叫做 repeatOnLifecycle

LifecycleOwner.repeatOnLifecycle()不是用来开启协程的,而是在协程内部关联生命周期的

比如:

    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {

        }
    }

这个写意味着,当 LifecycleOwner 每次 处于 STARTED 会重新开启一个新的协程,重新开始运行协程内部的代码
而处于其他状态下,会直接取消协程,而不是像上面launchWhenXXX 是恢复和挂起

好处:
与Flow搭配不会生命周期是i其他状态导致上游资源还在进行

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        flow<Int> {
            delay(3000)
            emit(1)
        }
            .collect {
                Log.d("zjs", "collect:   ")

            }

    }
}

坏处:
浪费资源:每次 当lifecycleOwner 重新进入或者退出某个生命周期 创建销毁协程是会浪费资源的。

总结:
如果你希望在

如果你只需要在lifecycleOwner 在启动时 协程里面的代码 只 执行一次,不管生命周期是什么状态,只要销毁的时候,自动销毁就行
那么直接用 lifecycleScope.launch{}

如果你只需要在lifecycleOwner 在启动时 协程里面的代码 只 执行一次,并且这一次代码会根据 当前生命周期来等待和恢复继续执行,销毁的时候,自动销毁 那么用 lifecycleScope.launchWhenStarted {}

如果你需要在lifecycleOwner 在每次某个生命周期的时候,协程里面的代码就执行一次,那么用 repeatOnLifecycle

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值