Bootstrap

kotlin高阶函数let、with、apply、run、also使用场景

let、with、apply、run、also这几个高阶函数非常相似,看定义也非常简单,初学kotlin时要么不记得使用它们,要么不知道选用哪个函数。

标准库之所以设计这些高阶函数,必定有需求和使用场景,本文讨论几个常见的使用场景。解决那些习惯Java语言的程序员们,初次使用kotlin造成困扰。

1. let

1.1 经常和?.操作符号联合使用,替代IF判断

var name:String? = null
var map = HashMap()

// name是可变变量,IF 条件判断不等于空,对于编译器来讲name还是可空变量,所以必须先赋值到val变量
val theName = name
if(theName !=null){
  //此时theName是不可空变量
  map[theName] = "OK"
}

// 使用?.let方法简化上面的代码
name?.let{
   map[it] = "OK"
}

2. apply

2.1 创建对象并初始化设置

val map = HashMap()
map["name1"] = 18
map["name2"] = 18

// 使用apply
val map = HashMap().apply{
  this["name1"] = 18
  this["name2"] = 18
}

// 如果对象创建后立即作为参数,那么避免一个变量名
fun processUser(user:User) 

processUser(User().apply{
  this.name = "jimi"
  this.age = 18
})

3. run

run函数有两种:一个是全局函数、一个是扩展函数

3.1 全局函数run 替代 java中 { } 代码作用域

java代码作用域有很多好处,比如避免临时变量名作用域过长,代码复制风险等,kotlin语法层面不支持代码作用域,可以通过run函数替代

fun logic(){
  run{
    val user = User()
    user.xxxx
    //....
    addUser(user)
  }

  run{
    val user = User()
    user.yyyy
    //....
    addUser(user)
  }
}

// 变量名user可以重复使用,避免user1、user2这种变量命名,容易在复制代码过程中造成错误
fun logic(){
    val user1 = User()
    user1.xxxx
    //....
    addUser(user1)
  

  
    val user2 = User()
    user1.yyyy   // 第二段代码和第一段类似,通常通过复制来修改,但是常常忘记修改user1为user2,造成bug
    //....
    addUser(user2)
  
}

当然作用域还有其他语义更为明确等其他优势。

另外run也能把最好一行作为返回值,避免专门定义一个函数

val user = run{
  val r1 = Role()
  //.....
  User(r1,...)
}

3.2 函数懒得定义返回类型

spring mvc 中Controller中对应RequestMapping的函数是定义可以省略返回类型定义,有时候比较方便

@GetMapping("/user/list")
fun list(): List{
  return service.findUsers()
}

// 简写成
@GetMapping("/user/list")
fun list() = run {
  service.findUsers()
}

4 also

5.1

Java中经常使用(line=reader.readLine())!=null这种写法,是由于赋值语句可以作为表达式才可以这么写。

但是kotlin的赋值语句不能作为表达式,所以刚开始使用kotlin会不习惯

    public void test(BufferedReader reader) throws IOException {
        String line;
        while ( (line=reader.readLine())!=null ){
            System.out.println(line);
        }
    }

在kotlin中

fun test(reader: BufferedReader) {
        var line: String?
        while (reader.readLine().also { line = it } != null) {
            println(line)
        }
    }

also 通过it访问当前变量,并且整体返回自己。所以可以模拟赋值语句当做表达式的效果。

虽然apply也能做类似的事情

while (reader.readLine().apply { line = this } != null) 

会造成语义不清晰,可读性不强,this到底指的哪个对象?

选择使用this 还是 it 访问当前对象,一个原则就是:如果使用this,那么就不要把 this. 写出来,利用this关键字可以省略的特性。

5 with

5.1 消除对同一个变量多次引用

val service = Service()
service.setName("xxx")
service.doSome()
service.ddd()
service.sddd()


// 使用with修改为
with(service){
  this.setName("xxx")
  this.doSome()
  this.ddd()
  this.sddd()
}

// 进一步省略this

with(service){
  setName("xxx")
  doSome()
  ddd()
	sddd()
}