我们何时应该在Kotlin上使用run,let,apply和with的示例

UmA*_*orn 72 kotlin

我希望为每个函数运行,let,apply和一个很好的例子

我读过这篇文章但仍缺乏一个例子

Lov*_*vis 86

所有这些函数都用于切换当前函数/变量的范围.它们用于将属于一起的东西保存在一个地方(主要是初始化).

这里有些例子:

run - 返回您想要的任何内容,并重新调整其使用的变量范围 this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }
Run Code Online (Sandbox Code Playgroud)

密码生成器现在rescoped的this,因此,我们可以设置seed,hash而且hashRepetitions不使用变量. generate()将返回一个实例Password.

apply是类似的,但它会返回this:

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()
Run Code Online (Sandbox Code Playgroud)

这对于Builder模式的替代特别有用,并且如果您想重新使用某些配置.

let- 主要用于避免空检查,但也可以用作替代run.区别在于,它this仍然与以前相同,您可以使用it以下方法访问重新定义的变量:

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}
Run Code Online (Sandbox Code Playgroud)

上面的代码只有在它不为空时才会将苹果添加到购物篮中.另请注意,it现在它不再可选的,因此您不会遇到NullPointerException(也就是说.您不需要使用它?.来访问其属性)

also- 当你想使用时使用它apply,但不想暗影this

class FruitBasket {
    private var weight = 0

    fun addFrom(appleTree: AppleTree) {
        val apple = appleTree.pick().also { apple ->
            this.weight += apple.weight
            add(apple)
        }
        ...
    }
    ...
    fun add(fruit: Fruit) = ...
}
Run Code Online (Sandbox Code Playgroud)

apply在这里使用阴影this,所以这this.weight将指的是苹果,而不是水果篮.


注意:我无耻地从我的博客中获取了示例

  • 对于像我这样被第一个代码吓到的人来说,lambda 的最后一行在 Kotlin 中被视为返回值。 (12认同)

hax*_*por 44

还有一些像这里的文章,这里值得一看.

我认为这取决于你需要更短,更简洁的几行,并避免分支或条件语句检查(如果不是null,那么这样做).

我喜欢这个简单的图表,所以我把它链接在这里.你可以看到是写的塞巴斯圣哥达.

在此输入图像描述

另请查看下面我的解释附带的图表.

概念

当你调用那些函数时,我认为它是你的代码块中的一个角色扮演方式+你是想要自己回来(链接调用函数,还是设置为结果变量等).

以上是我的想法.

概念示例

我们在这里看一下所有这些例子

1.)myComputer.apply { }意味着你想扮演一个主要角色(你想要认为你是计算机),你想让自己回来(计算机)所以你可以做

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()
Run Code Online (Sandbox Code Playgroud)

是的,你自己只是安装应用程序,自己崩溃,并保存自己作为参考,以允许其他人看到和做一些事情.

2.)myComputer.also {}意味着你完全确定你不是计算机,你是一个想要用它做某事的局外人,并且还希望计算机作为返回的结果.

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()
Run Code Online (Sandbox Code Playgroud)

3.)with(myComputer) { }意味着你是主要演员(计算机),你希望自己回来.

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}
Run Code Online (Sandbox Code Playgroud)

4.)myComputer.run { }意味着你是主要演员(计算机),你希望自己回来.

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}
Run Code Online (Sandbox Code Playgroud)

但它与with { }非常微妙的意义不同,你可以run { }像下面这样链接电话

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}
Run Code Online (Sandbox Code Playgroud)

这是由于run {}扩展功能,但with { }不是.因此,您调用run { }this在代码块内部将反映到调用者类型的对象.您可以看到这一点,以获得run {}和之间区别的出色解释with {}.

5.)myComputer.let { }意味着你是一个看着电脑的外人,想要对它做些什么,而不用担心计算机实例会再次返回给你.

myComputer.let {
    myGrandpa.installVirusOn(it)
}
Run Code Online (Sandbox Code Playgroud)

看待它的方式

我倾向于看alsolet为一些东西,是外部的,外部的.每当你说出这两个词时,就像你试图采取行动一样.let在此计算机上安装病毒,并also使其崩溃.所以这就说明了你是不是演员.

对于结果部分,它显然在那里.also表达它也是另一回事,所以你仍然保留对象本身的可用性.因此它返回它作为结果.

其他一切都与之相关this.另外run/with显然对返回对象 - 自我回归不感兴趣.现在你可以区分所有这些.

我认为有时当我们离开100%基于编程/逻辑的例子时,我们就能更好地概念化事物.但这取决于正确的:)


oiy*_*yio 18

有 6 种不同的作用域函数:

  1. 运行
  2. T.let
  3. 申请

我准备了一个视觉笔记,如下所示,以显示差异:

data class Citizen(var name: String, var age: Int, var residence: String)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

决定取决于您的需求。不同功能的用例重叠,因此您可以根据您的项目或团队中使用的特定约定来选择功能。

尽管作用域函数是使代码更简洁的一种方式,但请避免过度使用它们:它会降低代码的可读性并导致错误。避免嵌套作用域函数并在链接它们时小心:很容易混淆当前上下文对象和 this 或 it 的值。

这是从https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84决定使用哪个的另一个图表 在此处输入图片说明

一些约定如下:

使用适用于不改变对象的其他操作,如记录或打印的调试信息。

val numbers = mutableListOf("one", "two", "three")
 numbers
 .also { println("The list elements before adding new one: $it") }
 .add("four")
Run Code Online (Sandbox Code Playgroud)

apply的常见情况是对象配置。

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)
Run Code Online (Sandbox Code Playgroud)

如果您需要阴影,请使用运行

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}
Run Code Online (Sandbox Code Playgroud)

如果您需要返回接收对象本身,使用的应用


Den*_*nis 10

我必须承认,乍一看差异并不那么明显,除其他外,因为这 5 个功能通常是可以互换的。这是我的理解:

APPLY -> 使用这些属性初始化一个对象并等待该对象

val paint = Paint().apply {
    this.style = Paint.Style.FILL
    this.color = Color.WHITE
}
Run Code Online (Sandbox Code Playgroud)

LET -> 隔离一段代码并等待结果

val result = let {
    val b = 3
    val c = 2
    b + c
}
Run Code Online (Sandbox Code Playgroud)

或者

val a = 1
val result = a.let {
    val b = 3
    val c = 2
    it + b + c
}
Run Code Online (Sandbox Code Playgroud)

或者

val paint: Paint? = Paint()
paint?.let {
    // here, paint is always NOT NULL
    // paint is "Paint", not "Paint?"
}
Run Code Online (Sandbox Code Playgroud)

ALSO -> 同时执行2个操作并等待结果

var a = 1
var b = 3
a = b.also { b = a }
Run Code Online (Sandbox Code Playgroud)

WITH -> 使用此变量/对象执行某些操作,并且不等待结果(不允许链接)

with(canvas) {
    this.draw(x)
    this.draw(y)
}
Run Code Online (Sandbox Code Playgroud)

RUN -> 使用此变量/对象执行某些操作,并且不等待结果(允许链接)

canvas.run {
    this.draw(x)
    this.draw(y)
}
Run Code Online (Sandbox Code Playgroud)

或者

canvas.run {this.draw(x)}.run {this.draw(x)}
Run Code Online (Sandbox Code Playgroud)


Bhu*_* BS 6

让,也应用,takeIf,takeUnlow是Kotlin中的扩展功能。

要了解这些功能,您必须了解Kotlin中的扩展功能Lambda函数

扩展功能:

通过使用扩展功能,我们可以在不继承类的情况下为类创建函数。

与C#和Gosu相似,Kotlin提供了使用新功能扩展类的功能,而不必继承该类或使用任何类型的设计模式(例如Decorator)。这是通过称为扩展的特殊声明完成的。Kotlin支持扩展功能和扩展属性。

因此,要查找中是否只有数字String,您可以创建如下所示的方法而无需继承String类。

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
Run Code Online (Sandbox Code Playgroud)

您可以使用上述扩展功能

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
Run Code Online (Sandbox Code Playgroud)

这是版画true

Lambda函数:

Lambda函数就像Java中的Interface。但是在Kotlin中,lambda函数可以作为参数传递给函数。

例:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}
Run Code Online (Sandbox Code Playgroud)

您可以看到,该块是lambda函数,它作为参数传递。您可以像这样使用上面的功能,

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })
Run Code Online (Sandbox Code Playgroud)

上面的功能将这样打印,

Block executed
true
Run Code Online (Sandbox Code Playgroud)

我希望,现在您对扩展函数和Lambda函数有了一个了解。现在我们可以一一进入扩展功能。

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Run Code Online (Sandbox Code Playgroud)

以上功能中使用了两种类型的T和R。

T.let
Run Code Online (Sandbox Code Playgroud)

T可以是String类之类的任何对象。因此您可以对任何对象调用此函数。

block: (T) -> R
Run Code Online (Sandbox Code Playgroud)

在let的参数中,您可以看到上面的lambda函数。同样,调用对象作为函数的参数传递。因此,您可以在函数内部使用调用类对象。然后它返回R(另一个对象)。

例:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,让String作为其lambda函数的参数,然后返回Pair

同样,其他扩展功能也可以工作。

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
Run Code Online (Sandbox Code Playgroud)

扩展函数also将调用类作为lambda函数参数,但不返回任何内容。

例:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }
Run Code Online (Sandbox Code Playgroud)

应用

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Run Code Online (Sandbox Code Playgroud)

与函数相同,但传递的是相同的调用对象,因此您可以使用函数和其他属性,而无需调用它或参数名称。

例:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }
Run Code Online (Sandbox Code Playgroud)

您可以在上面的示例中看到在lambda函数内部直接调用的String类的功能。

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Run Code Online (Sandbox Code Playgroud)

例:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
Run Code Online (Sandbox Code Playgroud)

在上述示例中numberphoneNumber只有一个字符串与匹配regex。否则,它将为null

除非

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
Run Code Online (Sandbox Code Playgroud)

与takeIf相反。

例:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
Run Code Online (Sandbox Code Playgroud)

numberphoneNumber如果不匹配,将只有一个字符串regex。否则,它将为null

您可以查看类似的答案,这在Kotlin之间的区别也很有用,在Kotlin中应用,使用,使用,takeIf和takeUnless


Shr*_* Ye 5

根据我的经验,由于此类函数是内联语法糖,没有性能差异,因此您应该始终选择需要在 lamda 中编写最少代码的函数。

为此,首先确定您希望 lambda 返回其结果(选择run/ let)还是对象本身(选择apply/ also);那么在大多数情况下,当 lambda 是单个表达式时,请选择与该表达式具有相同块函数类型的表达式,因为当它是接收者表达式时,this可以省略,当它是参数表达式时,it短于this

val a: Type = ...

fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
Run Code Online (Sandbox Code Playgroud)

然而,当 lambda 由它们的组合组成时,您就可以选择更适合上下文或您感觉更舒服的那个。

另外,需要解构时,请使用带参数块功能的:

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Run Code Online (Sandbox Code Playgroud)

以下是 JetBrains 在 Coursera Kotlin for Java Developers上的官方 Kotlin 课程中对所有这些函数的简要比较: 差异表 简化的实施