刚学习kotlin的时候,会发现一些奇怪的语法,例如

var dints2=ints.map{ it*2}

File("test.txt").let { }

等等,这些感觉好奇怪的,和java区别很大的,这些到底是啥呢, 在学习这些之前,我们先大概了解一下什么是lambda表达式

lambda表达式

  • 目的是为了更贴近函数式编程,把函数作为参数的思想
  • 格式:{(输入参数)-> (运算)输出}

最外面使用{},用()定义参数列表,箭头-> 以及一个表达式或者语句块

例如

textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("hoyouly", " onClick "+v.getId());
            }
        });

简化成为 lambda表达式后就成了下面这种形式

textView.setOnClickListener(view -> {Log.d("hoyouly", " onClick "+v.getId()});
//只有一行代码的,可以把{} 也省略掉
textView.setOnClickListener(view -> Log.d("hoyouly", " onClick "+v.getId());

再如

executor.submit(
   new Runnable(){
     @Override
     public void run(){
         //todo
     }
  }
);
//简化为
executor.submit(() ->{/*todo*/})

Kotlin里提供了一些有趣的函数,包括it,let,apply,run,with,also,inline 可以成为Kotlin的标准库函数,他们都是建立在lamdba表达式的基础上的。

it

对于这种单个参数的运算式,例如

val dints=ints.map{value->value*2}

可以进一步简化,把参数声明和->都简化掉,只保留运算输出,不过这要用it来统一代替参数

val dints2=ints.map{ it*2}

简单来说,it 就是为了简化代码而存在的

let

let能把更复杂的对象赋给it

File("test.txt").let {
       println(it::class.simpleName) //  File
       println(it.absolutePath) //  /Users/hoyouly/llll/HelloKotlin/test.txt
}

打印的it 类型就是 File,

这个特性可以稍微扩展一下,比如增加?检查

getVaraiable()?.let{
    it.length    // when not null
}

这样可以先检查返回值是否为空,不为空才继续进行

默认let{}返回中是带的it 类型,但是也可以进行修改成其他有含义的名称例如

File("test.txt").let {
     file-> //把默认的it 改成了有含义的file
     println(file::class.simpleName) //File
     println(file.absolutePath) //  /Users/hoyouly/llll/HelloKotlin/test.txt
 }

let 返回的是代码块中最后一行的对象

fun testlet() {
    val original = "abc"

    original.let {
        println("The original String is $it")  //adc
        it.reversed()
    }.let {//此时it 类型还是String
        println("The reverse String is $it") //cba
        it.length
    }.let {//此时it 类型就是Int
        println("The length of the String is $it") //3
    }
}

also

和let 类似,但是 返回的还是对象本身,并不会发生变化

original.also {
    println("The original String is $it") //abc
    it.reversed()
}.also {//此时it 类型还是String
    println("The reverse String is $it") // abc
    it.length
}.let {//此时it 类型还是String
    println("The length of the String is $it") // abc
}

虽然 在 also 中执行了 it.reversed() ,可是再次输出的时候,还是abc ,

意义:

  • 1、它可以对相同的对象提供非常清晰的分离过程,即创建更小的函数部分。
  • 2、在使用之前,它可以非常强大的进行自我操作,从而实现整个链式代码的构建操作。

let 和 alse 组合

例如 创建一个文件夹,通用的写法

fun makeDir(path: String): File {
  val file=File(path)
  file.mkdirs()
  return  file
}

但是通过 also 和let 组合,就变得很简洁

fun makeDir(path: String): File {
  return path.let {
      File(it) //创建一个File对象,然后返回,
  }.also {//此时it 对象就是File,然后对it 进行 mkdirs() 的操作,
      it.mkdirs()
  } //最终return 的还是 also 的时候的it 对象,
}

apply

apply可以操作一个对象的任意函数,再结合let返回该对象,例如

var array: ArrayList<Int> = ArrayList()
println(array.size)//  0
array.apply {
    add(0, 30); //执行了ArrayList 的add方法,
}.let {
    println(it.size)//1
    println(it) // [30]
}

run

  • apply是操作一个对象,run则是操作一块儿代码
  • apply返回操作的对象,run的返回则是最后一行代码的对象,这一点和let 很相似的
array.run {
    add(20)
    var text ="abd"
    text
}.let {
    println(it::class.simpleName)  //String
    println(array.size) // 2
}

当把 text 这一行注释掉之后,返回的结果就变了

array.run {
    add(20)
    var text ="abd"
//        text
}.let {
    println(it::class.simpleName)  //Unit
    println(array.size) // 2
}

这是因为 run()返回的是最后一行代码的对象,因子最后一行代码没有对象,所以就返回了Unit

run的一个应用可以把将show()方法应用到两个View中,而不需要去调用两次show()方法

run {
  if (firstTimeView) introView else normalView
}.show()

with

with有点儿像apply,也是操作一个对象,不过它是用函数方式,把对象作为参数传入with函数,然后在代码块中操作,例如

with(array) {
    add(10)
    var text = "abd"
    text
}.let {
    println(it::class.simpleName)  //String
    println(array.size) // 3
}

同样,把text 注释掉,结果就变成了 Unit

with(array) {
    add(10)
    var text = "abd"
//        text
}.let {
    println(it::class.simpleName)  //Unit
    println(array.size) // 3
}

with 和 run 区别

run函数可以在使用它之前对可空性进行检查

// Yack!(比较丑陋的实现方式)
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
   }
}
// Nice.(比较好的实现方式)
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

inline

inline内联函数,其实相当于对代码块的一个标记,这个代码块将在编译时被放进代码的内部,相当于说,内联函数在编译后就被打散到调用它的函数里的,目的是得到一些性能上的优势。

使用标准库函数的补充

  1. 建议尽量不要使用多个标准库函数进行嵌套,不要为了简化而去做简化,否则整个代码可读性会大大降低,一会是it指代,一会又是this指代,估计隔一段时间后连你自己都不知道指代什么了。
  2. let函数和run函数之所以能够返回其他类型的值,其原理在于lambda表达式内部返回最后一行表达式的值,所以只要最后一行表达式返回不同的对象,那么它们就返回不同类型,表现上就是返回其他类型
  3. T.also和T.apply函数之所以能能返回自己本身,是因为在各自Lambda表达式内部最后一行都调用return this,返回它们自己本身,这个this能被指代调用者,是因为它们都是扩展函数特性

use

  • 实现了Closeable接口的对象可调用use函数
  • use函数会自动关闭调用者(无论中间是否出现异常)
  • Kotlin的File对象和IO流操作变得行云流水
File("/home/test.txt").readLines()
        .forEach { println(it) }

readLines()内部间接使用了use函数,这样就省去了捕获异常和关闭流的烦恼


搬运地址:

Kotlin Jetpack 实战|00. 写给 Java 开发者的 Kotlin 入坑指南

写给Android开发者的Kotlin入门

Kotlin 教程

Kotlin use函数的魔法

[译]掌握Kotlin中的标准库函数: run、with、let、also和apply