Kotlin学习笔记

Posted on By Qinggai Huang

init和constructor的不同

Kotlin中的数据初始化有好几种方式,其中调用的方式是不同的。

Initializer

构造函数外的数据初始化

  • Property initializers
    var name: String = "David"
    
  • Initializer blocks
    逻辑比较多时,可以这样初始化,用init包裹。
    init {
      /* do some setup here */
    }
    

Constructors

构造函数中的数据初始化,如下代码中constructor。

open class Parent {
    private val a = println("Parent.a")

    constructor(arg: Unit=println("Parent primary constructor default argument")) {
        println("Parent primary constructor")
    }

    constructor(arg: Int, arg2:Unit= println("Parent secondary constructor default argument")): this() {
        println("Parent secondary constructor")
    }

    init {
        println("Parent.init")
    }

    private val b = println("Parent.b")
}

执行顺序

先Initializer(从上到下)后constructor内部。如上例子,如果调用Parent(1),则输出如下:

Parent secondary constructor default argument  
Parent primary constructor default argument  
Parent.a  
Parent.init  
Parent.b   
Parent primary constructor  
Parent secondary constructor  

引用:https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546

Kotlin中的companion object

companion object可以有名字,也可以省略。如果省略,就默认Companion
有点像java里面的static,但底层实现不一样。

有用资料:https://www.programiz.com/kotlin-programming/companion-objects

by lazy

第一次调用的时候会执行里面的所有代码,然后就会记录返回值。
第二次调用就不执行代码了,直接用上次的返回值,节省重复执行时间消耗


run, with, let, also 和 apply 的区别

这篇文章写的非常好:https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
这篇文章关于用this还是ithttps://proandroiddev.com/the-difference-between-kotlins-functions-let-apply-with-run-and-else-ca51a4c696b8

以下这是对上面文章的理解总结归纳。

  • run
    这个函数可以理解为,内部的作用域和外部不同,内部可以重新定义和外部一样的变量名。此外还有一个好处,多个对象调用一个方法可以写在一处。以下函数就不用写两个show():
      run {
          if (firstTimeView) introView else normalView
      }.show()
    
  • with
    with其实和T.run()很类似。 类似的地方:
    with(webview.settings) {
      javaScriptEnabled = true
      databaseEnabled = true
    }
    //与下面代码相似
    webview.settings.run {
      javaScriptEnabled = true
      databaseEnabled = true
    }
    

    不同的地方,如果对象为空:

    //麻烦
    with(webview.settings) {
        this?.javaScriptEnabled = true
        this?.databaseEnabled = true
    }
    //简洁
    webview.settings?.run {
      javaScriptEnabled = true
      databaseEnabled = true
    }
    
  • let
    let传入的是參数要用it来使用。可以对it进行重名名。返回的可以是改造过的it,或者之前返回另一个类型的数据。
  • also
    alsolet有所不一样,返回的和传入的是一样的。also可以用于代码分段。
    如以下例子很好解释了两者的区别:
    //正确的
    original.let {
      println("The original String is $it") // "abc"
      it.reversed() // evolve it as parameter to send to next let
    }.let {
      println("The reverse String is $it") // "cba"
      it.length  // can be evolve to other type
    }.let {
      println("The length of the String is $it") // 3
    }
    //不对的
    original.also {
      println("The original String is $it") // "abc"
      it.reversed() // even if we evolve it, it is useless
    }.also {
      println("The reverse String is ${it}") // "abc"
      it.length  // even if we evolve it, it is useless
    }.also {
      println("The length of the String is ${it}") // "abc"
    }
    
    //also的正确使用
    original.also {
      println("The original String is $it") // "abc"
    }.also {
      println("The reverse String is ${it.reversed()}") // "cba"
    }.also {
      println("The length of the String is ${it.length}") // 3
    }
    
  • apply
    T.apply传入this作为参数,返回修改后的(比如添加了属性值)的this。还可以链式调用。
    fun createIntent(intentData: String, intentAction: String) =
          Intent().apply { action = intentAction }
                  .apply { data = Uri.parse(intentData) }
    

Lambda in Kotlin

Kotlin中的Lambda用到的地方很多,用好Lambda能省下不少代码。
Lambdas表达式其实是匿名函数,我们可以把它作为一个变量来处理,当成一个参数传入另一个高级函数等等。
正常的一个lambda定义是这样的:

var func: (String) -> String = {in -> out}

(String) -> String 表示传入函数有一个String类型的参数,函数返回值为String。当然这部分kotlin里面是可以省略的,也就是变成了:

var func = {in -> out}

Lambda唯一不能省的是函数体, 就是->符合后面的部分,函数体的最后一行就是返回的值。
只有一个参数时传入参数的变量名也是可以省的,系统有默认的it,所以上述定义又可以简化为:

var func = {out}

结合最近使用的RxAndroid举一个稍微复杂一点的例子
比如对RxAndroid中map功能的调用,map函数源码为:

public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) {
    ...//省略不关键代码
}

常见的类似java中不使用Lambda的调用:

map(MyFunction())

private class MyFunction : Function<WeatherModel.Result, WeatherModel.WeatherInfo> {
    override fun apply(t: WeatherModel.Result): WeatherModel.WeatherInfo {
        return t.weatherinfo
    }
}

初步使用Lambda后应该是这样的:

map(function)
val function = Function<WeatherModel.Result, WeatherModel.WeatherInfo> { 
    result -> result.weatherinfo
}

不使用变量直接嵌入map中:

map({
  result -> result.weatherinfo
})

省略传入变量名:

map({it.weatherinfo})

此外,推荐函数的最后一个Lambda参数放()外面,因为只有一个参数()也是可以省的,所以就变成了:

map {
  it.weatherinfo
}

很好玩是不是!

参考文章:https://www.baeldung.com/kotlin-lambda-expressions