• 在Kotlin中,一个if语句是可以直接给一个变量赋值的,这就是表达式,它有返回值。

    val status = when {}
        xxx -> {}
        xxx -> {}
        else -> {}
    }

    这种方式比Java节省了太多的代码,所以Kotlin中不再需要三目表达式了,直接通过if/else即可。

    fun max(a: Int, b: Int) = if (a > b) a else b

    延迟初始化

    在Kotlin中,成员变量的值被严格区分可空和非可空,其中非可空的变量值,要么在声明的时候进行初始化,要么通过延迟加载的方式进行初始化,一般来说,有两种方式来进行延迟加载。

    lazy

    通过lazy函数,可以实现在首次使用到的时候才去实例化。

    private val xxxxFragment by lazy {
        XXXXFragment().apply {
            arguments = Bundle().apply {
            }
        }
    }

    lateinit

    通过lateinit,自己控制变量的初始化。

    private lateinit var iv: ImageView

    这两种方式各有各的使用场景:

    data class

    data class是Kotlin中一个用来生成模板代码的语法糖,在Java中,定义的实体类,通常会有很多的模板代码,大部分情况下,我们都是通过一个工具插件来生成,而在Kotlin中,则更加简单。

    第一种方式实际上是Kotlin对构造函数的优化,省略了构造函数的实体,直接通过参数声明的方式进行了创建。

    // Kotlin会为类的参数自动实现get set方法
    class User(val name: String, val age: Int, val gender: Int, var address: String)

    第二种方式则是借助data关键字,生成Kotlin中定义好的实体类。

    // 用data关键词来声明一个数据类,除了会自动实现get set,同时还会自动生成equals hashcode toString
    data class User(val name: String, val age: Int, val gender: Int, var address: String)

    object

    object在Kotlin中是一个比较难理解的概念,和Java中的Object完全不同,后面会有单独的文章来介绍object,这里先简单的看下Kotlin通过object提供的语法糖。

    object,其实可以把它理解成:定义一个类并创建该类的一个实例。

    所以object的一个功能,就是快速创建一个单例模式。

    例如在代码中经常写的:

    object ThreadUtil {

        fun onMainThread(runnable: Runnable) {
            val mainHandler = Handler(Looper.getMainLooper())
            mainHandler.post(runnable)
        }
    }

    简化下实际上就是下面的代码。

    object Singleton {

        fun xxx() {
        }
    }

    反编译后看生成代码,这就是一个典型的饿汉式单例,借助静态代码块初始化的锁,初始化单例实例,从而实现单例效果。

    public final class Singleton {
       public static final Singleton INSTANCE;

       public final void xxx() {
       }

       private Singleton() {
       }

       static {
          Singleton var0 = new Singleton();
          INSTANCE = var0;
       }
    }

    通过object代替匿名内部类

    这是object的另一个比较常用的地方,也符合了object的语义,定义一个类,并生成该类的实例,也就是需要创建的匿名内部类。

    viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
        override fun onPageScrollStateChanged(state: Int) {}

        override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}

        override fun onPageSelected(position: Int) {}
    });

    companion object

    由于Kotlin中没有静态函数,所以在Kotlin中,可以使用companion object替代Java中的static修饰。

    编译器会自动生成了一个叫做Companion的静态内部类。

    class User {

        companion object {
            const val DEFAULT_USER_AGE = 30
        }
        
        fun test(){}
    }

    // later, accessed like you would a static variable:
    user.age = User.DEFAULT_USER_AGE

    Kotlin函数

    在Kotlin的基础库中,系统提供了大量针对函数的优化,解决了很多在Java代码中写起来不太爽的地方。

    显式参数

    在Java中,当一个函数的参数值太多时,需要一个个对齐参数,虽然可以通过IDE的快捷提示等功能来展示,但始终用起来不太方便,而在Kotlin中,除了像Java中那样按顺序的传递参数外,还可以通过指定参数名的方式进行参数传递。

    fun test(name: String, age: Int) {
    }

    test(name = "xys", age = 18)

    参数含义一目了然,提高了代码的可读性。

    参数默认值

    fun test(name: String = "xys", age: Int) {
    }

    fun a() {
        test(age = 18)
    }

    通过参数默认值,可以避免Java下大量参数下的重载函数,当某个参数可以使用默认值时,就不用显示的声明了,类似Java中的不同参数的重载函数。

    拓展函数

    拓展函数可以说是Kotlin最为重要的黑魔法之一了,通过拓展函数,可以给一些系统类添加原本没有的函数,极大的提高了函数的可拓展性。

    fun Activity.toast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }

    拓展属性

    与拓展函数类似,拓展属性可以给现有属性拓展自定义的实现。

    val String.lastChar: Char
        get() = get(length - 1)

    拓展功能看上去比较神奇,但大家可以通过查看Kotlin生成的class代码,反编译成的Java代码来看它具体的实现方法。

    对于扩展函数来说,转化为Java代码的时候,其实就是生成一个静态的函数,这个静态函数的第一个参数就是该类的实例对象,所以这样把类的实例传入函数以后,函数内部就可以访问到类的公有方法。

    扩展属性也是类似,获取的扩展属性会生成为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象,设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象。函数内部可以访问公有的方法和属性。

    在了解了其实现原理后,可以发现,拓展函数一定是static的,且不能被override,也不存在运行时类型,其类型在编译时就已经确定,同时扩展函数和扩展属性内只能访问到类的公有方法和属性,私有的和protected同样是不能访问的。

    嵌套函数

    函数是Kotlin中的第一公民,所以函数可以出现在Kotlin中的任何一个地方,包括在一个函数中。

    在一个函数中定义另一个函数,可以很好的将这个函数的使用限制在当前的外层函数中,避免对外暴露不必要的接口,同时还能避免重复的模板代码,例如下面这个例子。

    class User(val id: Int, val name: String, val address: String, val email: String)

    fun check(user: User) {
        if (user.name.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty Name")
        }
        if (user.address.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty Address")
        }
        if (user.email.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: empty Email")
        }
        // ...
    }

    通过嵌套函数实现。

    fun saveUser2(user: User) {
        fun validate(value: String, fildName: String) {
            if (value.isEmpty()) {
                throw IllegalArgumentException("Can't save user ${user.id}: empty $fildName")
            }
        }

        validate(user.name, "Name")
        validate(user.address, "Address")
        validate(user.email, "Email")
        // ...
    }

    工具类函数

    由于在Kotlin中,函数可以脱离类而独立存在,所以这对于工具类函数来说,就非常方便了,不用再定义一个ToolUtil类,而可以直接写在文件中。

    作用域函数

    作用域函数在Kotlin修炼指南(一)中已经有详细介绍了。

    设计模式

    设计模式最早是在面向对象编程的基础上提出来的编程范式,但是对于函数式编程来说,有很多定义都过于教条了,所以,现代式的编程语言,通过很多语法上的定义,就已经实现了很多种设计模式。

    单例模式

    前面已经提到了,通过object class,就可以很轻松的实现一个线程安全的单例类。

    静态工厂模式

    借助运算符重载,可以很方便的实现静态工厂模式。

    interface Car {
        val brand: String

        companion object {
            operator fun invoke(type: CarType): Car {
                return when (type) {
                    CarType.AUDI -> Audi()    
                    CarType.BMW -> BMW()
                }
            }
        }
    }

    通过重载了invoke()函数,在调用Car(CarType.BMW)的时候,就创建好了对应的工厂实例。

    代理模式 策略模式

    代理模式,或者说策略模式,都可以通过Kotlin中的类委托来实现。

    interface BaseTTS {
        fun doTTS()
    }

    class BaiDuTTS : BaseTTS {
        override fun doTTS() {
            print("BaiDu")
        }
    }

    class TencentTTS : BaseTTS {
        override fun doTTS() {
            print("Tencent")
        }
    }

    class TTSCategory(tts: BaseTTS) : BaseTTS by tts

    fun doTest() {
        TTSCategory(BaiDuTTS()).doTTS()
    }

    通过类委托,将tts的实现代理出来。

    更进一步,可以通过匿名类的方式,直接创建代理类的实现。

    interface BaseTTS {
        fun doTTS()
    }

    class TTSCategory(tts: BaseTTS) : BaseTTS by tts {
        override fun doTTS() {
            print("Do tts")
        }
    }

    而当策略中只有一个函数的时候,还可以进一步简化,把策略直接封装成Lambda表达式。

    class TTSCategory(val strategy: () -> Unit) {
        fun doTTS() {
            strategy.invoke()
        }
    }

    fun test() {
        TTSCategory { print("Do tts") }.doTTS()
    }

    装饰器模式

    同样是通过类委托功能,还可以实现装饰器模式。

    装饰器模式是为了解决继承导致类行为变更的问题产生的。如果需要在使用一个类的同时,又要修改该类的一些函数的实现,这时候就可以使用装饰器模式,创建一个装饰器类,实现与原始类一样的接口并将原来的类的实例作为一个成员变量。装饰器类与原始类拥有相同行为的方法不用修改,只需要直接转发给原始类的实例,需要修改的函数,实现新的功能即可。

    但这里的问题是,当一个原始类需要实现的函数很多时,而装饰器类又只需要修改很少的函数时,就会产生大量的模板代码,所以这个时候,借助类委托,就可以极大的减少这种模板代码的产生。

    class ListDecorator<T>(val innerSet: List<T> = listOf()) : List<T> by innerSet {
        override fun contains(element: T): Boolean {
            print("Do other thing")
            return innerSet.contains(element)
        }
    }

    fun test() {
        val contains = ListDecorator(listOf("ss")).contains("s")
    }

    通过反编译代码可以发现,实际上编译器帮助我们重写了所有的未修改函数。

    后续计划

    Kotlin有趣的地方还有很多,一篇文章很难全部写完,所以后面的计划如下。

    修仙

    Flutter Dojo开源至今,受到了很多Flutter学习者和爱好者的喜爱,也有越来越多的人加入到Flutter的学习中来,所以我建了个Flutter修仙群,但是人数太多,所以分成了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指东】三个群,对Flutter感兴趣的朋友,可以添加我的微信,注明加入Flutter修仙群,或者直接关注我的微信公众号【Android群英传】。

    Kotlin修炼指南(三)——奇技淫巧-LMLPHP

    感兴趣的朋友可以加我微信【Tomcat_xu】,我拉你入群。

    项目地址:

    https://github.com/xuyisheng/flutter_dojo


    本文分享自微信公众号 - Android群英传(android_heroes)。
    如有侵权,请联系 support@oschina.cn 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    09-03 06:33