如何在Kotlin中实现applyif?

12

我希望applyif的工作方式如下:

builder.applyif(<condition expression>) {
    builder.set...
}

与...相等:

builder.apply {
    if (<condition expression>) {
        builder.set...
    }
}

这可能吗?


我鼓励你学习足够的语言知识,以便自己构建这个。阅读有关扩展函数高阶函数的内容。第一个参数可以是普通布尔值。第二个参数是你的lambda函数。 - Eugen Pechanec
尽管我来晚了:你到底尝试了什么来解决它?最好遵循@EugenPechanec的建议,而不是简单地复制解决方案。 - Roland
1
个人而言,我会坚持使用标准库:builder.takeIf { condition_code }?.apply { applied_code }。任何新读者都会熟悉这些内容,不必学习和记忆你手写的扩展函数。 - Marko Topolnik
1
@MarkoTopolnik,注意表达式builder.takeIf { condition_code }?.apply { applied_code }如果条件为false则返回null,而所要求的applyif返回一个构建器对象,无论条件是true还是false,在我看来这是在构建器上链接调用时所需要的。 - Michal Borowiecki
我的理解是你已经在builder变量中拥有了构建器对象,并且直接在闭合的}上链接并不比引入自定义API更具有优势。如果你认为坚持使用标准库中熟悉的习语没有任何价值,那么你可能会有不同的看法。 - Marko Topolnik
4个回答

17

当然可以。你几乎可以编写任何东西,但是不要重复造轮子。看一下答案底部的标准 Kotlin 方法,它可能已经满足您的需求(虽然并非完全等同于applyIf)。

现在,让我们看一下如何实现一个applyIf

inline fun <T> T.applyIf(predicate: T.() -> Boolean, block: T.() -> Unit): T = apply { 
  if (predicate(this)) 
    block(this) 
}

如果使用lambda实现扩展函数,请不要忘记inline关键字。

以下是上述内容的示例用法:

// sample class
class ADemo {
  fun isTrue() = true
}

// sample usage using method references
ADemo().applyIf(ADemo::isTrue, ::println)

// or if you prefer or require it, here without
ADemo().applyIf( { isTrue() } ) {
  println(this)
}

如果你只想提供一个布尔值,可以使用以下扩展函数:

inline fun <T> T.applyIf(condition : Boolean, block : T.() -> Unit) : T = apply { 
  if(condition) block(this) 
}

然后使用以下方式调用:

val someCondition = true
ADemo().applyIf(someCondition) {
  println(this)
}

现在有一个可能更多人熟悉的Kotlin标准方式:

ADemo().takeIf(ADemo::isTrue)
       ?.apply(::println)

// or
ADemo().takeIf { it.isTrue() }
       ?.apply { println(this) }

如果他们记得的话(我实际上是在看到Marko Topolnik的评论之前才想起来的),他们应该立即知道发生了什么。 但是,如果您在调用takeIf后需要给定值(即ADemo()),则此方法可能不适合您,因为以下内容将把变量设置为null

val x = ADemo().takeIf { false }
               ?.apply { println(this) /* never called */ }
// now x = null

而以下代码将会把变量设置为ADemo实例:

val x = ADemo().applyIf(false) { println(this) /* also not called */ }
// now x contains the ADemo()-instance

链式调用构建器可能不是很好。但你也可以通过标准的Kotlin函数实现,将takeIfapplyalso结合使用(或者使用withletrun,具体取决于你是否想要返回某些内容,以及是喜欢使用it还是this):

val x = builder.apply {
  takeIf { false }
    ?.apply(::println) // not called
  takeIf { true }
    ?.apply(::println) // called
}
// x contains the builder

但是,我们几乎已经到了你在问题中所提到的地方。使用applyIf确实可以使代码变得更好:
val x = builder.applyIf(false, ::println) // not called
               .applyIf(true) { 
                 println(this) // called
               }
// x contains the builder

请添加一个使用示例(我正在寻找括号内的第一个lambda和外部的第二个)。哦,还有行内修饰符丢失了。 - Eugen Pechanec
好的...有趣的是,我一开始就想到了,但写的时候可能忘了 :-D 很快会添加示例。 - Roland
感谢您的提示,已添加。 - Roland
@Roland,这很有帮助。有没有一种方法可以在不执行T的情况下执行takeIf?在上面的示例中,`ADemo().takeIf(ADemo::isTrue)`ADemo将被创建,然后仅在谓词为真时才会被取走。是否有一种方法只在谓词为真时执行T - Nick
@Nick,这可能需要单独提出一个问题..听起来像是一个“简单”的ifwhen的工作.. - Roland

3

当然可以,你只需要一个扩展函数,这样你就可以在builder上调用它,并且你需要它接受一个Boolean参数和要执行的lambda表达式。

如果你查看apply函数本身的源代码,它将帮助你完成大部分实现:

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

基于此,applyIf 可以非常简单地实现如下:
inline fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T {
    return if (condition) this.apply(block) else this
}

使用方法如下:

builder.applyIf(x > 200) {
    setSomething()
}

0
fun <T> T.applyIf(condition: Boolean, block: T.() -> T) = if (condition) block() else this

fun main() {
    println("a".applyIf(true) { uppercase() }) // A
    println("a".applyIf(false) { uppercase() }) // a
}

0

对于不可变的值(例如Compose Modifier),apply将无法工作,因此必须显式地返回this,类似于这样

inline fun <T> T.applyIf(predicate: T.() -> Boolean, block: T.() -> T): T =
    if (predicate())
        block(this)
    else
        this
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接