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


100

我希望每个运行的函数都有一个很好的例子,让我们应用

我已经阅读了这篇文章,但仍然缺乏示例

Answers:


121

所有这些功能都用于切换当前功能/变量的范围。它们用于将属于在一起的事物放在一个位置(大多数情况下是初始化)。

这里有些例子:

run -返回您想要的任何内容,并重新作用域范围内的变量 this

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

       generate()
   }

现在,密码生成器的作用域为this,因此我们可以设置seedhashhashRepetitions无需使用变量。 generate()将返回的实例Password

apply是类似的,但它将返回this

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

这对于替换Bu​​ilder模式特别有用,并且如果您想重用某些配置。

let-主要用于避免空检查,但也可以替代run。不同之处在于,它this仍然与之前相同,您可以使用来访问重新作用域变量it

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

上面的代码仅在苹果不为null时才将其添加到购物篮中。还要注意,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) = ...
}

使用apply此处将产生阴影this,因此this.weight将指向苹果,而不是水果篮。


注意:我毫不客气地从博客中获取了示例


2
对于像我这样被第一个代码惊吓的人,lambda的最后一行在Kotlin中被视为返回值。
周杰伦·李

62

有一些更多的文章喜欢这里,并在这里,值得去看一看。

我认为这取决于何时需要在短短几行内更短,更简洁,并避免分支或条件语句检查(例如,如果不为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()

是的,您自己只是安装应用程序,使其崩溃,并将自己保存为参考,以允许其他人查看和使用它。

2.)myComputer.also {}表示您完全确定自己不是计算机,是局外人,希望对此做某事,还希望它作为计算机返回结果。

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

3.)with(myComputer) { }表示您是主要演员(计算机),并且希望自己退缩。

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.)myComputer.run { }表示您是主要演员(计算机),并且希望自己因此而退缩。

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

但从with { }非常细微的意义上来说,您可以run { }像下面这样链接呼叫

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

这是由于run {}具有扩展功能,而with { }不是。因此,您的调用run { }this代码块内部将反映给调用者类型的对象。您可以看到,以很好地解释run {}和之间的区别with {}

5.)myComputer.let { }表示您是查看计算机的局外人,并且想要对计算机执行某些操作,而无需担心计算机实例会再次退还给您。

myComputer.let {
    myGrandpa.installVirusOn(it)
}

看它的方式

我倾向于看alsolet为一些东西,是外部的,外部的。每当您说出这两个词时,就好像您尝试对某事采取行动。let在这台计算机上安装病毒,然后also使其崩溃。因此,这确定了您是否是演员的部分。

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

其他所有与关联this。另外,run/with显然不希望返回对象自身。现在您可以区分所有这些。

我认为有时候,当我们脱离100%基于编程/基于逻辑的示例时,那么我们处于更好的概念化位置。但这取决于权利:)


1
该图说明了一切。迄今为止最好的。
Shukant Pal

这应该是公认的且投票最多的答案
Segun Wahaab

8

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

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

扩展功能:

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

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

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

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

您可以使用上述扩展功能

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

这是版画true

Lambda函数:

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

例:

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

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

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

上面的功能将这样打印,

Block executed
true

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

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

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

T.let

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

block: (T) -> R

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

例:

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

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

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

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

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

例:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

应用

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

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

例:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

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

takeIf

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

例:

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

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

除非

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

与takeIf相反。

例:

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

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

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


您在上一个示例中有一个错别字,可能phoneNumber. takeUnless{}不是phoneNumber. takeIf{}
瑞安·阿玛拉

1
已更正。感谢@Ryan Amaral
Bhuvanesh BS

5

有6种不同的作用域功能:

  1. 奔跑
  2. 出租
  3. 申请

我准备了一个可视注释,如下所示以显示差异:

data class Citizen(var name: String, var age: Int, var residence: String)

在此处输入图片说明

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

尽管范围函数是使代码更简洁的一种方法,但是请避免过度使用它们:这会降低代码的可读性并导致错误。避免嵌套作用域函数,并在链接它们时要格外小心:很容易对当前上下文对象及其值感到困惑。

这是用于从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")

适用的常见情况是对象配置。

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)

如果需要阴影,请使用run

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

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

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


3

根据我的经验,由于这些函数是内联语法糖,没有性能差异,因此,您应始终选择需要在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"

但是,当lambda由它们的混合物组成时,则取决于您,然后选择一个更适合上下文或您感觉更舒适的lambda。

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

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

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

这是JetBrains针对Java开发人员的Coursera Kotlin官方Kotlin课程中所有这些功能的简要比较: 差异表 简化的实现

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.