传递lambda而不是接口


69

我创建了一个界面:

interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

但只能将其用作匿名类,不能用作lambda

dataManager.createAndSubmitSendIt(title, message,
object : ProgressListener {
    override fun transferred(bytesUploaded: Long) {
        System.out.println(bytesUploaded.toString())
    }
})

我认为应该可以用lambda代替它:

dataManager.createAndSubmitSendIt(title, message, {System.out.println(it.toString())})

但是我得到了错误: Type mismatch; required - ProgressListener, found - () -> Unit?

我究竟做错了什么?

Answers:


51

正如@ zsmb13所说,仅Java接口支持SAM转换。

您可以创建一个扩展功能以使其正常运行:

// Assuming the type of dataManager is DataManager.
fun DataManager.createAndSubmitSendIt(title: String, 
                                      message: String, 
                                      progressListener: (Long) -> Unit) {
    createAndSubmitSendIt(title, message,
        object : ProgressListener {
            override fun transferred(bytesUploaded: Long) {
                progressListener(bytesUploaded)
            }
        })
}

编辑:

Kotlin 1.4将带来功能接口,该功能接口可为Kotlin中定义的接口启用SAM转换。这意味着,如果使用fun关键字定义接口,则可以使用lambda调用函数。像这样:

fun interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

29

Kotlin仅支持Java接口的SAM转换。

...请注意,此功能仅适用于Java互操作;由于Kotlin具有适当的功能类型,因此不需要将功能自动转换为Kotlin接口的实现,因此不受支持。

-官方文件

如果要在参数中使用lambda,请使函数采用函数参数而不是接口。(至少现在。支持Kotlin接口的SAM转换是一个持续的讨论,它是Kotlin 1.1实时流中将来可能的功能之一。)


7
也许还值得一提的是,如果接口的初衷是表达期望函数的语义,则可以使用typealiases或具有诸如的已命名参数的函数类型(bytesUploaded: Long) -> Unit
热键

10

晚了一点:您无需编译接口,而是让编译器通过直接使用函数而不是数据管理器中的接口来创建接口,如下所示:

fun createAndSubmitSendIt(title: String, message: String, transferred: (Long) -> Unit) {
    val answer = TODO("whatever you need to do")
    transferred(answer)
}

然后您就可以按照自己的意愿使用它!如果我没记错的话,kotlin / jvm编译器的作用与创建接口相同。

希望能帮助到你!


8

更新:2020年9月7日

正如Kotlin 1.4版本的Kotlin文档指出的那样:

在Kotlin 1.4.0之前,您只能在使用Kotlin的Java方法和Java接口时应用SAM(单一抽象方法)转换。从现在开始,您还可以将SAM转换用于Kotlin接口。为此,请使用fun修饰符将Kotlin接口明确标记为可正常使用。

fun interface Operation1 {
    operator fun invoke(x: String): String
}

fun interface Operation2 {
    fun doSomething(x: Int): String
}

val operation1 = Operation1 { "$it world!" }
val operation2 = Operation2 { "$it world!" }

fun main() {
    // Usage: First sample.
    println(operation1("Hello"))
    println(operation2.doSomething(0))
    // Usage: Second sample.
    println(Operation1 { "$it world!" }("Hello"))
    println(Operation2 { "$it!" }.doSomething(0))
}

您可以在此处阅读有关功能接口的更多信息。

先前的解决方案:

声明一个typealias,将其注入到某个地方,稍后再调用它:

internal typealias WhateverListener = (String) -> Unit

然后将这种类型别名注入我们的班级:

class Gallery constructor(private val whateverListener: WhateverListener) {
    
    ...
    
    galleryItemClickListener.invoke("hello")

    ...
}

所以我们有我们的lambda:

val gallery = Gallery { appNavigator.openVideoPlayer(it) }

归功于我的同事乔尔·佩德拉萨(Joel Pedraza),他在尝试寻找解决方案时向我展示了诀窍<3。

注意:如果您想知道何时使用功能性接口(lambda)或键入别名,请查阅Kotlin文档


这是一个不错的解决方案,您甚至不需要额外的typealias
复杂的

不支持嵌套和局部typealiases
IgorGanapolsky

5

Kotlin 1.4及更高版本

Kotlin 1.4将通过“功能接口”解决此问题

Kotlin功能界面

  • Kotlin API:完美
  • Kotlin访问:完美
  • Java访问:完美
class KotlinApi {
    fun interface Listener {
        fun onResponse(response: String)
    }

    fun demo(listener: Listener) {
        listener.onResponse("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo { response ->
        println(response)
    }
}

public static void javaConsumer(){
    new KotlinApi().demo(response -> {
        System.out.println(response);
    });
}

在Kotlin 1.4之前

如果您希望Kotlin和Java都具有最佳访问体验,那么就没有唯一的最终解决方案。

如果Kotlin开发人员认为不必为Kotlin接口进行SAM转换,那么“ Kotlin接口”方法将是最终的解决方案。

https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
另外请注意,此功能仅适用于Java互操作。由于Kotlin具有适当的功能类型,因此不需要将功能自动转换为Kotlin接口的实现,因此不受支持。

为您的用例选择最佳的解决方案。

Kotlin函数类型

  • Kotlin API:完美
  • Kotlin访问:完美
  • Java访问:
    • 自动生成的参数类型,例如Function1(对于Java 8 lambda来说不是大问题)
    • 详细return Unit.INSTANCE;而不是无效的回报。
class KotlinApi {
    fun demo(listener: (response: String) -> Unit) {
       listener("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo { response->
        println(response)
    }
}

public static void javaConsumer() {
    new KotlinApi().demo(response -> {
        System.out.println(response);
        return Unit.INSTANCE;
    });
}

Kotlin界面

  • Kotlin API:其他接口定义。
  • Kotlin访问:太冗长
  • Java访问:完美
class KotlinApi {
    interface Listener {
        fun onResponse(response: String)
    }

    fun demo(listener: Listener) {
       listener.onResponse("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo(object : KotlinApi.Listener {
        override fun onResponse(response: String) {
            println(response)
        }
    })
}

public static void javaConsumer() {
    new KotlinApi().demo(response -> {
        System.out.println(response);
    });
}

Java介面

  • Kotlin API:混合Java代码。
  • Kotlin访问:有点冗长
  • Java访问:完美
class KotlinApi {
    fun demo(listener: Listener) {
        listener.onResponse("response")
    }
}

public interface Listener {
    void onResponse(String response);
}

fun kotlinConsumer() {
    KotlinApi().demo { response ->
        println(response)
    }
}

public static void javaConsumer() {
    new KotlinApi().demo(response -> {
        System.out.println(response);
    });
}

多种方法

  • Kotlin API:多种方法实现
  • Kotlin Access:如果使用正确的方法,则是完美的。自动完成功能也建议使用详细方法。
  • Java Access:完美。由于JvmSynthetic注释,自动完成不建议使用函数类型方法
class KotlinApi {
    interface Listener {
        fun onResponse(response: String)
    }

    fun demo(listener: Listener) {
        demo { response ->
            listener.onResponse(response)
        }
    }

    @JvmSynthetic //Prevents JVM to use this method
    fun demo(listener: (String) -> Unit) {
        listener("response")
    }
}

fun kotlinConsumer() {
    KotlinApi().demo { response ->
        println(response)
    }
}

public static void javaConsumer() {
    new KotlinApi().demo(response -> {
        System.out.println(response);
    });
}

Java API

  • Kotlin API:没有Kotlin API,所有API代码都是Java
  • Kotlin访问:完美
  • Java访问:完美
public class JavaApi {
    public void demo(Listener listener) {
        listener.onResponse("response");
    }

    public interface Listener {
        void onResponse(String response);
    }

}

fun kotlinConsumer() {
    JavaApi().demo { response ->
        println(response)
    }
}

public static void javaConsumer() {
    new JavaApi().demo(response -> {
        System.out.println(response);
    });
}
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.