• Kotlin进阶学习1


    写在前面

    在学习了Kotlin基础学习1Kotlin基础学习2Kotlin基础学习3之后,我们对Koltin的基础有了一定的了解。但就这样还是不够的,Kotlin里还有更多的特性等着我们去学习。这阶段可能会出现很多错误,希望看出来的老哥能指点一下。这次学习Kotlin中的标准函数、静态方法、延迟初始化与密封类。

    Kotlin中的标准函数

    介绍

    Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。虽然标准函数并不多,但要一次性全部学完自然是不可能的。这里就介绍几个最常用的标准函数。之前我们在Kotlin基础学习3中学习了let函数的用法,主要用于配合?.操作符进行辅助判空,这里就不再多言了。

    with函数

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

    val result = with(obj){
        // 这里是obj的上下文
        "value" // with函数的返回值
    }
    

    那么这个函数有什么用呢?它可以让我们在连续调用同一个对象的多个方法时让代码变得精简,比如:

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

    这段代码应该很好理解,首先我们给with函数传入了一个StringBuilder对象,之后Lambda表达式的就是这个StringBuilder对象了,那么我们调用append方法自然就不需要加上前面的实例了。最后,Lambda表达式的最后一行会作为返回值返回,然后赋值给了result,把result输出。

    run函数

    run函数的用法和使用场景与with函数也差不太多,只是在语法上有一些区别。首先run函数是不能直接调用的,一定要调用某个对象的run函数才行。其次,run函数只接收一个Lambda参数,并在Lambda表达式中提供调用对象的上下文,其他方面和with函数就没什么区别了,如下:

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

    总得来说,变化很小,只是换了一下写法而已。

    apply函数

    apply函数与上面学习的run函数也十分的类似,但差别在于apply函数无法指定返回值,只能自动返回调用对象本身,如下:

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

    因为apply函数无法指定返回值,只能返回调用对象本身,所以这里的result就是一个StringBuilder对象,我们在最后输出的时候自然还需要调用他的toString方法才行。

    总结

    总的来说,这几个标准函数用法和使用场景都十分相似,在实际编程中我们挑选合适的使用就可以了。

    静态方法

    介绍

    静态方法,一直在用java的我们一定不会陌生,在方法上声明一个static关键字就可以了。但非常奇怪的是,Kotlin极度弱化了静态方法这个概念,想在Koltin中定义一个静态方法反而不是那么简单。为什么会这样呢?因为Koltin提供了比静态方法更好的语法特性——单例类。像工具类这种功能,Koltin就推荐我们使用单例类,在使用上也十分方便。但单例类中每个方法都变成类似静态方法的调用方式了,那如果我们有一些方法不想这么搞怎么办呢?我们可以先把单例类变成普通类,然后使用companion object:

    class Util{
        fun doAction1(){}
        companion object{
            fun doAction2(){}
        }
    }
    

    现在,doAction1()和doAction2()方法就完全不一样了,想使用doAction1()就必须先实例化,但doAction2()就不需要。不过doAction2()仍然不是一个静态方法,companion object这个关键字实际上会在类中创建一个伴生类,而doAction2()就是定义在伴生类中的方法,不过Kotlin会保证每一个Util类始终只会存在一个伴生类对象。

    总的来说,在开发里,上述的特性已经足够了。可如果我比较固执,就是想用静态方法怎么办?

    使用注解

    如果想真的定义的一个静态方法,只需要在方法上加上@JvmStatic注解就好了:

    class Util{
        fun doAction1(){}
        companion object{
            @JvmStatic
            fun doAction2(){}
        }
    }
    

    但要注意,@JvmStatic注解只能加在单例类或companion object 中的方法上。这次我们的doAction2()是一个真正的静态方法了。

    顶层方法

    顶层方法指的是那些没有定义在任何类中的方法,比如我们的main()方法。Koltin会将所有的顶层方法全部编译成静态方法,因此只要你定义一个顶层方法,那么他就一定是静态方法。

    比如,我们创建一个kotlin文件,起名叫Helper.kt,里面输入我们想要的方法:

    fun doSomething(){}
    

    那么要怎么使用这个静态方法呢?十分简单,在项目的任何地方敲就是了。但如果想在java中调用,就必须要加上类名了,比如我们刚才定义的kotlin文件叫Helper.Kt,那么就可以使用HeplerKt.doSomething()来使用。这里的HelperKt是Kotlin编译器自动生成的,基本就是文件名+Kt。

    变量延迟初始化

    引入

    Koltin语言的许多特性我们已经学习了,但有时候很多特性会给我们的编程造成不方便,比如,如果你的类中存在很多全局变量实例,尽管你清楚的知道他们不会为空,但为了保证他们能够满足Kotlin的空指针检查语法标准,你也不得不做许多的非空判断保护才行。这就十分的让人烦了,比如下面的例子:

    class MainActivity:AppCompatActivity(),View.OnClickListener{
        private var adapter:MsgAdapter? = null
        override fun onCreate(savedInstanceState : Bundle?){
            ...
            adapter = MsgAdapter(msgList)
            ...
        }
        override fun onClick(v:View?){
            ...
            adapter?.notifyItemInserted(msgList.size - 1)
        }
    }
    

    这里我们将adapter设置成了全局变量,但他的初始化工作是在onCreate()方法执行的,因此我们不得不把他设置为空,同时声明为MsgAdapter?。虽然我们会确保自己已经初始化了,且能确保onClick()必然会在onCreate之后执行,但我们必须在onClick()方法里对adapter进行判空才行。

    当我们代码中的全局变量实例越来越多时,就会显得越来越烦人,因为很多代码都只是为了满足编译器的要求。

    有没有什么办法解决呢,十分简单。使用lateinit即可。

    使用

    延迟初始化使用的是lateinit关键字,它就是在告诉Kotlin编译器,这个变量我们会在之后初始化,就不需要提前赋值为null了。那么我们上面的代码就可以改写:

    class MainActivity:AppCompatActivity(),View.OnClickListener{
        private lateinit var adapter:MsgAdapter
        override fun onCreate(savedInstanceState : Bundle?){
            ...
            adapter = MsgAdapter(msgList)
            ...
        }
        override fun onClick(v:View?){
            ...
            adapter.notifyItemInserted(msgList.size - 1)
        }
    }
    

    当然,使用lateinit方法不是没有任何风险的,如果一个变量还没初始化的时候就使用这个变量,程序自然会崩溃,抛出异常。所以,当我们对全局变量使用lateinit关键字的时候,要先确保已经初始化了。

    当然,我们也可以通过代码来判断是否完成了初始化:

    if(!::adapter.isInitialized){
        adapter = MsgAdapter(msgList)
    }
    

    虽然语法看起来很奇怪,但这是固定写法。::adapter.isInitialized可以判断adapter变量是否已经初始化。再取反,就表示如果没有初始化时去立即初始化。

    密封类

    介绍

    一般来讲,密封类会结合RecyclerView里的ViewHolder一起使用。当然,它的使用场景不仅限于此。有了它,可以帮助我们写出更加规范和安全的代码。

    使用

    下面,我们先来看一个例子:

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

    我们定义了一个Result接口,然后定义了两个类去实现Result接口,Success表示成功时的结果,Failure表示失败时的结果。接下来,再定义一个getResultMsg()方法,用来获取执行结果:

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

    getResultMsg方法接收一个Result参数,我们通过when语句判断是哪种类型,是成功就返回成功的消息,是错误就返回错误的消息。但这里的else条件是毫无意义的,这个else是根本不可能走到的,但为了满足编译器的需求,我们只能加上这个else分支。

    不过,要解决这种情况也是十分简单的,使用密封类即可:

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

    可以看到,差别不是很大,我们只是把interface关键字变成了sealed class。此外,由于密封类是一个可以继承的类,所以要加上括号。那么刚才的when判断里的else分支就不需要了:

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

    这是为什么呢?因为当when中传入一个密封类变量作为条件时,编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类的条件都处理了。这样的话,就可以保证不写else也可以不漏掉任何分支了。

    总结

    总的来说,掌握了标准函数的用法,且对Koltin中的静态函数进行了一定程度的了解。也对一些由于编译器的要求所出现的不方便的情况有了解决的能力。收获还是蛮多的,希望能在实战中加深理解吧。

  • 相关阅读:
    00-03.kaliLinux-vi粘贴复制功能配置
    00-02.kaliLinux-配置SSH服务
    00-01.Kali Linux 2020.1修改root用户密码
    Web设计色彩(转载)
    Inno Setup区段之Language篇
    Inno Setup区段之Setup篇
    IE无法获取到input框的值
    Inno Setup之常量篇
    Inno Setup之概念篇
    网络不通排查
  • 原文地址:https://www.cnblogs.com/wushenjiang/p/13455980.html
Copyright © 2020-2023  润新知