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

Kotlin做为Android开发的首选语言,为开发者提供了大量的语法糖和技巧,让开发者能够专一于需求开发,而将语言所带来的影响减小到最少。Java和Kotlin最大的区别,实际上在于Kotlin的函数式编程思想以及语法,特别是lambda表达式,这是Kotlin效率高于Java开发的核心武器,在以前的文章中,已经有比较详细的讲解了。git

下面我将从几个方面分别来给你们演示下Kotlin到底是如何提升开发效率的。github

语法糖

所谓语法糖,实际上就是对Java原生写法进行的封装,虽然不用也能写,可是用了绝对回不去。编程

字符串模版

"${xxxBean.type}"
复制代码

字符串模版保证了String的完整性,这也是大部分现代语言都会有的功能,有了字符串模板,就能够再也不使用+进行拼接,不但更方便,也让字符串的语义更加明确。设计模式

Raw string

val str1 = "abc"
val str2 = """value1\n
    value2
    value3
    "value4"
    """.trimMargin()
复制代码

三引号表明Raw string,即三引号内全部内容均为string,即便有须要转义的字符,也不用特殊处理。安全

使用trimMargin来去除每行开头的空格微信

强化Switch

Kotlin中的when函数,解除了Java中的switch的不少限制,而且拓展了不少方便的功能。markdown

fun getValue(a: Int) = when {
    a > 0 -> "A"
    a < 0 -> "N"
    a.hashCode() == 0x108 -> "XYS"
    else -> "ZJ"
}
复制代码

在使用上更加灵活,同时让选择分支语句更加容易理解。app

语句 Vs 表达式

kotlin中大部分关键字都是表达式。编程语言

  • 表达式有值,而且能做为另外一个表达式的一部分使用,这是函数式编程的基础
  • 语句老是包围着它的代码块中的代码元素,而且没有本身的值,例如Java中的if\else\Switch等。

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

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
复制代码

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

  • by lazy 修饰val的变量
  • lateinit 修饰var的变量,且变量是非空的类型

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的静态内部类。

在Java中调用伴生对象,可使用User.Companion.isMale(1)

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中的不一样参数的重载函数。

在Java、Kotlin混编的时候,没法避免的会混合调用,能够经过@JvmOverloads注解,给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中的第一公民,因此函数能够出如今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有趣的地方还有不少,一篇文章很难所有写完,因此后面的计划以下。

  • 集合与惰性序列
  • Kotlin DSL
  • 操做符重载
  • sealed class
  • KTX

修仙

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

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

项目地址:

github.com/xuyisheng/f…

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息