什么时候我们应该在Kotlin

时间:2017-08-31 08:40:35

标签: kotlin

我希望每个函数run,let,apply,with

都有一个很好的例子

我已阅读this article但仍缺少示例

5 个答案:

答案 0 :(得分:70)

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

以下是一些例子:

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()

如果您想重新使用某些配置,那么它作为Builder模式的替代品特别有用。

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

val fruitBasket = ...

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

上面的代码只有在它不为空的情况下才会将苹果添加到购物篮中。另请注意,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会引用苹果,而不会引用水果篮。

注意:我无耻地采用了例子from my blog

答案 1 :(得分:30)

还有一些文章如herehere值得一看。

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

我喜欢这个简单的图表,所以我在这里链接了它。你可以从Sebastiano Gottardo写的this看到它。

enter image description here

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

概念

  

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

以上是我的想法。

概念示例

让我们在这里看到所有这些的例子

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将反映到调用者类型的对象。您可以查看this,了解run {}with {}之间差异的绝佳解释。

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

myComputer.let {
    myGrandpa.installVirusOn(it)
}

看待它的方式

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

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

其他所有内容都与this相关联。此外,run/with显然对返回对象 - 自我回归并不感兴趣。现在你可以区分所有这些。

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

答案 2 :(得分:8)

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

  1. T.run
  2. T.let
  3. T.apply
  4. T.also
  5. 使用
  6. 运行

我准备了如下视觉注释,以显示差异:

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

enter image description here

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

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

这是另一张图,用于从https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84中决定使用哪个 enter image description here

一些约定如下:

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

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 :(得分:5)

让,也,申请,takeIf,takeUnless 是Kotlin的扩展功能。

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

扩展功能:

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

  

Kotlin,类似于C#和Gosu,提供了扩展课程的能力   具有新功能而无需从类继承或使用   任何类型的设计模式,如装饰师。这是通过特殊方式完成的   声明称为扩展。 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()) }

在上面的例子中,number只有phoneNumber字符串,只与regex匹配。否则,它将是null

<强> takeUnless

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字符串。否则,它将是cvc-complex-type.2.4.a: Invalid content was found starting with element 'constructor-args'. One of '{"http:// www.springframework.org/schema/beans":import, "http://www.springframework.org/schema/beans":alias, "http://www.springframework.org/schema/beans":bean, WC[##other:"http://www.springframework.org/ schema/beans"], "http://www.springframework.org/schema/beans":beans}' is expected.

您可以在difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin

查看类似的答案

答案 4 :(得分:0)

根据我的经验,由于此类函数是内联语法糖,没有性能差异,因此,您应始终选择需要在lamda中编写最少代码的函数。
为此,首先确定您是要lambda返回其结果(选择run / let)还是对象本身(选择apply / also);那么在大多数情况下,当lambda是单个表达式时,请选择与该表达式具有相同的块函数类型的表达式,因为当它是接收方表达式时,this可以省略,而当它是参数表达式{{1 }}比it短:

this

但是,当lambda由它们的混合体组成时,则取决于您,然后选择一个更适合上下文或您更喜欢的lambda。
另外,在需要解构时,请使用带有参数块功能的参数:

val object: Type = ...

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

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
pair.run/*apply*/ { parameterFunction(this, ...) } // longer
pair.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"

以下是JetBrains关于Coursera Kotlin for Java Developers的Kotlin官方课程中所有这些功能的简要比较: Difference table Simplified implementations