有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Kotlin:如何显式指定lambda的实际类型?

如何在Kotlin中显式指定lambda的实际类型

这是必要的,因为如果我有两个函数采用相似的lambda,例如

fun use(block: Context.() -> Boolean)
@JvmName("use2") fun use(block: Context.() -> Unit)

我希望能够使use2调用正则(布尔)use,即

@JvmName("use2") fun use(block: Context.() -> Unit) = use { block(); true }

这类似于可以显式指定SAM类型的方式,例如:

Runnable { println("hello") }

如何对Kotlin lambda类型执行相同操作

这是我想出的解决问题的方法,但我想知道是否有更好/更干净的方法

@JvmName("use2") fun use(block: Context.() -> Unit) = use({ block(); true } as (Context.() -> Boolean)})

共 (3) 个答案

  1. # 1 楼答案

    您可以使用@OverloadResolutionByLambdaReturnType注释,但这是一个实验性功能,因此需要添加@OptIn(ExperimentalTypeInference::class)。此外,还应该显式指定返回类型

    fun use(block: Context.() -> Boolean): ReturnType
    
    @OptIn(ExperimentalTypeInference::class)
    @OverloadResolutionByLambdaReturnType
    @JvmName("use2")
    fun use(block: Context.() -> Unit): ReturnType = use { block(); true }
    
  2. # 2 楼答案

    我已经做到了:

    @JvmName("use2") fun use(block: Context.() -> Unit) =
        use({ foo: Context -> block(foo); true } as (Context) -> Boolean)
    

    删除强制转换或: String类型注释将使其无法编译。IntelliJ IDEA还错误地报告演员阵容是不必要的(note that this isn't the first time IntelliJ is wrong

    注意,我已经将lambda的类型从Context.() -> Boolean更改为(Context) -> Boolean,因为我认为没有明确指定接收器类型的方法。但这不会影响过载解决方案

    如果您真的想使用接收器类型为Context的东西,可以使用匿名函数:

    @JvmName("use2") fun use(block: Context.() -> Unit) =
        use(fun Context.(): Boolean { block(); return true })
    

    我认为这是一种更好的编写use2的方法,但请注意,匿名函数不同于lambda in many ways

  3. # 3 楼答案

    How can I explicitly specify the actual type of a lambda in Kotlin?

    我认为有两个选项可以让代码更加清晰:泛型和函数接口。它们也可以结合在一起

    选项1:泛型(*见下文更新)

    不要指定返回值,而是使用泛型。对于所有交互,我们只能有一个功能:

    fun <R> use(block: Context.() -> R) : Boolean = context.block()
    

    如果block不返回布尔值,您还希望返回到默认值true。我们也可以这样做:

    fun <R> use(block: Context.() -> R): Boolean {
      // use a 'when' block - it solves type safety issues
      return when (
        val result = context.block() // execute the lambda and store the output in a val
      ) { 
        !is Boolean -> true   // default to 'true' if result is not a boolean
        else        -> result // else, we can return the boolean result of 'block'
      }
    }
    

    *UPDATE实际上,泛型不是必需的,因为use(...)函数总是返回布尔值,而不是lambda的结果

    这里有一个非泛型的use(...)函数,可以解决“两个函数的jvm签名冲突”的问题:

    fun  use(block: Context.() -> Any): Boolean {
      // use a 'when' block - it solves type safety issues
      return when (
        val result = context.block() // execute the lambda and store the output in a val
      ) {
        !is Boolean -> true   // default to 'true' if result is not a boolean
        else        -> result // else, we can return the boolean result of 'block'
      }
    }
    

    选项2:功能接口

    使用functional interfaces——它使lambda的定义更加清晰,并且可以重用

    // step 1: define our 'lambdas'
    
    // equivalent: Context.() -> Boolean
    fun interface ContextPredicate {
      fun Context.execute(): Boolean
    }
    
    // equivalent: Context.() -> Unit
    fun interface ContextConsumer {
      fun Context.execute()
    }
    
    // step 2: define the 'use' functions
    class ContextService(
      private val context: Context
    ) {
    
      fun use(block: ContextPredicate): Boolean = with(block) {
        context.execute()
      }
    
      fun use(block: ContextConsumer): Boolean = with(block) {
        context.execute()
        true // default response is 'true' for 'ContextConsumer'
      }
    }
    
    // 3. dummy class for testing
    data class Context(
      val name: String
    )
    
    // 4. test our code
    fun main() {
      val service = ContextService(Context("hello"))
    
      println(
        service.use(ContextPredicate { name == "hello" })
      )
      // output: true
    
      println(
        service.use(ContextPredicate { name == "good morning" })
      )
      // output: false
    
      println(
        service.use(ContextConsumer { println("~~~$name~~~") })
      )
      // output:
      // ~~~hello~~~
      // true
    }
    

    现在关于返回类型没有什么不明确的地方了use(...)函数要么接受谓词,要么接受消费者,故事的结尾

    注意:with(block) { ... }read more about it here)是必需的,因为我们定义的功能接口有接收器

    泛型+功能接口

    我们可以将两者结合起来,以获得两个方面的最佳效果:简洁和清晰:

    // define a generic fun-interface
    fun interface ContextFunction<R> {
      // change Context to be a parameter, not a receiver 
      fun execute(context: Context): R
    }
    
    class ContextService(
      private val context: Context
    ) {
      
      // use our generic fun-interface
      fun <R> use(block: ContextFunction<R>): Boolean {
        
        return when (val result = block.execute(context)) {
          // if the result isn't a boolean, then the default response is 'true'
          !is Boolean -> true
          // else, we can use the actual result
          else        -> result
        }
      }
    }
    
    // usage is the same as 'Option 1: Generics'
    fun main() {
      val service = ContextService(Context("hello"))
    
      println(
        service.use { it.name == "hello" }
      )
      // output: true
    
      println(
        service.use { it.name == "good morning" }
      )
      // output: false
    
      println(
        service.use { println("~~~${it.name}~~~") }
      )
      // output:
      // ~~~hello~~~
      // true
    }