kotlin高级特性

Kotlin语法的高级特性异常强大,代码异常简洁,如果你在项目中能熟练使用各种kotlin高级特性后,你会发现,你之前这些年写的代码都是在浪费生命。

标准函数

kotlin的标准函数,指的是Standard.kt文件中定义的函数,包括let、also、with、run、apply函数。

  • let函数

let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

适用场景
场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。

        //没有let函数,需要每次判空,代码不够优雅
         data?.toString()
        data?.toString()
        data?.toString()

data?.let {
            //在函数域中,保证data对象不为,不用多次去判空
            it.toString()
            it.toString()
            it.toString()
        }

场景二: 然后就是需要去明确一个变量所处特定的作用域范围内可以使用

data.let {
            //在函数体内使用it替代该data对象使用
            it.toString()
        }
also函数

also函数使用的一般结构

object.also{

}

适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。

        user?.also {
            it.age = 18
            it.name = "小明"
        }.age
with函数

with函数使用的一般结构

with(object){
   //todo
 }

它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。

适用场景
需要设置某个对象多个属性到UI上时,需要不断调用对象,使用with函数能避免对象的重复书写。

        data class User(var name: String, var age: Int)

        with(user){
            Log.i("TAG", "age=$age")
            Log.i("TAG", "name=$name")
        }

该age和name变量就是该with参数中的user对象的属性值。

run函数

run函数使用的一般结构

object.run{
    
}

适用场景
适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。

        user?.run {
            Log.i("TAG", "age=$age")
            Log.i("TAG", "name=$name")
        }
apply函数

apply函数使用的一般结构

object.apply{

}

从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply可以任意调用该对象的任意方法,并返回该对象。

适用场景
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。

场景一:apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。

data class User(var name: String, var age: Int)

ArrayList().apply {
            add(User("小明", 18))
            add(User("小王", 16))

        }.run {
            get(0)
        }       

创建User对象并添加入集合,添加完成后取出第一个对象。

场景二:多层级判空问题

    data class Response(var code: Int, var data: Data)

    data class Data(var icon: String, var title: String)

        response?.apply {
            //response不为空时可操作response
            
        }.data?.apply {
            //data不为空时可操作data
            
        }.title?.apply {
            //title不为空时可操作title
            
        }
let,with,run,apply,also函数总结:
函数名 函数体内使用的对象 返回值 适用场景
let it指代当前对象 返回任意类型R 适用于处理多处判空的场景
also it指代当前对象 返回this 适用于let函数的任何场景,一般可用于多个扩展函数链式调用
with this指代当前对象或者省略 返回任意类型R 适用于调用同一个类的多个方法、属性时,省去每次书写类名
run this指代当前对象或者省略 返回任意类型R 适用于let,with函数任何场景
apply this指代当前对象或者省略 返回this 适用于run函数任何场景,一般可用于多个扩展函数链式调用
takeIf,takeUnless函数
        user.name.takeIf {
            TextUtils.isEmpty(it)
        }.let {
            print(it)
        }

        user.name.takeUnless {
            !TextUtils.isEmpty(it)
        }.let {
            print(it)
        }

takeIf的闭包返回一个判断结果,如果为false时takeIf函数返回null;takeUnless与takeIf相反,为true时takeUnless函数返回null。

使用场景:只需要单个if分支语句的时候
优点:
可以配合其他作用域函数返回的结果,做出单向判断,保持链式调用
简化写法,逻辑清晰,减少代码量,代码更优雅

Kotlin委托

委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。 比如调用A类的methodA方法,其实背后是B类的methodB去执行。

Kotlin将委托功能分为了两种:类委托和委托属性。

  • 类委托:即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
interface User {
    fun login()
}

class UserImpl(val name: String) : User{
    override fun login() {
        println(name)
    }
}

class VipUser(user: User) : User by user

fun main() {
    VipUser(UserImpl("1号用户")).login()
}

可以看到委托类并没有实现User接口,而是通过关键字by,将实现委托给了user。

  • 属性委托:委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
class MyClass {
  var p by Delegate()
}

class Delegate {
  var propValue: Any? = null
  
  operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
    return propValue
  }
  
  operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
    propValue = value
  }   
}

这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已,返回的实例可以作为实现延迟属性的委托。

val lazyProp: String by lazy {
    println("Hello,第一次调用才会执行我!")
    "Hello"
}
// 打印lazyProp 3次,查看结果
fun main() {
    println(lazyProp)
    println(lazyProp)
    println(lazyProp)
}

打印结果如下:

Hello,第一次调用才会执行我!
Hello
Hello
Hello

可以看到,只有第一次调用,才会执行lambda表达式中的逻辑,后面调用只会返回lambda表达式的最终值。

参考:
https://blog.csdn.net/xingyu19911016/article/details/126414296
https://mp.weixin.qq.com/s/QQy2zPcmtUL3SlRHMNWBEQ

扩展函数

扩展函数表示即使在不修改某个类的源码的情况下,我们仍然可以对某个类添加方法,进行扩展。例如我们对User类增加登录的功能,实现在类外面添加方法。

    fun User.login() {
        Log.i("TAG","去登录")
    }

 data class User(var name: String, var age: Int)

扩展了login方法,就可以使用user.login()。可以看到,扩展函数的主要写法就是在定义方法名的时候,通过Class.直接声明是在哪个类中。

集合操作符

Kotlin中可以通过集合操作符直接对集合进行操作,从而得到想要的结果。

map:对集合中的数据做改变,可以改变数据的类型。
filter:得到所有符合Lambda闭包中操作的数据。
find:得到符合Lambda闭包中操作的第一个数据。
findLast:得到符合Lambda闭包中操作的最后一个数据。
reduce:含有两个参数(集合中相邻的两个参数,用于遍历整个集合),对这两个参数进行操作,返回一个新参数,要求类型与集合中的参数类型相同。

协变与逆变

我们约定,在一个泛型类或者泛型接口的方法中,它的参数列表是接收数据的地方,就称为in位置,他的返回值是输出数据的地方,就称为out位置。

fun test(param: T):T {
    return param
}

如上test方法中,传入泛型T的位置为in位置,返回类型T的位置为out位置。

泛型的协变:假如定义了一个MyClass的泛型类,其中A是B的子类型,同时MyClass又是MyClass的子类型,我们就可以称MyClass在T这个泛型是协变的。如果泛型都是只读的(泛型加上out关键字),就能实现MyClass是MyClass的子类型
泛型的逆变:假如定义了一个MyClass的泛型类,其中A是B的子类型,同时MyClass又是MyClass
的子类型,我们就可以称MyClass在T这个泛型是逆变的。如果泛型都是只写的(泛型加上in关键字),就能实现MyClass是MyClass的子类型

协程

见我的Kotlin协程使用

DSL(domain specific language)

即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。

通用编程语言 vs DSL
通用编程语言(如 Java、Kotlin、Android等),往往提供了全面的库来帮助开发者开发完整的应用程序,而 DSL 只专注于某个领域,比如 SQL 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 SQL 或者正则表达式来开发一个完整的应用。

参考

Kotlin系列之let、with、run、apply、also函数的使用
Kotlin协程
Kotlin之美——DSL篇
一篇文章搞定kotlin

你可能感兴趣的:(kotlin高级特性)