【问题标题】:Can't understand how to use SAM interface in Kotlin无法理解如何在 Kotlin 中使用 SAM 接口
【发布时间】:2021-02-23 20:32:34
【问题描述】:

我不明白为什么以下代码有效: 山姆接口:

    fun interface WebResponseHandler
{
    fun onWebResponseFinished(jsonObject:JSONObject)
}

而实现看起来像:

  val onWebResponseHandler: VolleyHandler.WebResponseHandler = VolleyHandler.WebResponseHandler()
{
    fun onWebResponseFinished(jsonObject: JSONObject) {
        Log.v("da", "dsa")
    }

    fun foo() {
        it.getString("name")
    }
}

foo 函数如何知道it 是什么?毕竟,foo 函数不是 VolleyHandler.WebResponseHandler 接口的一部分, 仅在接口作为一种方法时才起作用。Function Foo 在没有任何参数的情况下被调用,那么为什么 it 的含义在那里?

此外,如果我在尝试实现接口时从接口声明中省略关键字 fun,则会出现另一个问题:

val onWebResponseHandler: VolleyHandler.WebResponseHandler = VolleyHandler.WebResponseHandler()
{
    fun onWebResponseFinished(jsonObject: JSONObject) {
        Log.v("da", "dsa")
    }

    fun foo() {
        Log.v("something", "something")

    }
}

我收到一个编译错误提示

接口 WebResponseHandler 没有构造函数

这两种情况有什么不同?如果我删除 () 我会收到很多不同的错误,例如:

函数声明必须有名字

我希望有人能弄清楚这里出了什么问题

【问题讨论】:

  • 发生的事情非常微妙和奇怪,但是这些代码都没有按照您的想法进行。你需要实际上做你认为你正在做的事情是在=符号之后写object := object : VolleyHandler.WebResponseHandler() { ... }
  • @LouisWasserman,我可以使用它是一个 SAM 接口的事实吗?你对我在帖子中写的所有问题都有解释吗?似乎 Kotlin 无法解释所有的混乱情况
  • 看来如果我使用 () 我必须在同一行打开 {。实在看不懂这是怎么回事

标签: java kotlin lambda interface implementation


【解决方案1】:

这不是 Kotlin 的混乱,而是你使用了错误的语法来做你想做的事情。

fun interface 让你做的就是写

val onWebResponseHandler = VolleyHandler.WebResponseHandler {
    jsonObject: JSONObject -> Log.v("da", "dsa")
}

普通界面可以做的就是写

val onWebResponseHandler = object : VolleyHandler.WebResponseHandler() {
    fun onWebResponseFinished(jsonObject: JSONObject) {
        Log.v("da", "dsa")
    }

    fun foo() {
        Log.v("something", "something")

    }
}

您的代码不会做任何这些事情,并且在编译时,不会做任何事情,就像您的意思一样。你写的相当于

val onWebResponseHandler = VolleyHandler.WebResponseHandler() { 
  unusedJsonObject: JSONObject ->
    fun aFunctionWhichIsDefinedAndNeverCalled(jsonObject: JSONObject) {
        Log.v("da", "dsa")
    }

    fun anotherFunctionWhichIsDefinedAndNeverCalled() {
        unusedJsonObject.getString("name")
    }
    // doesn't actually do anything at all when executed
    // just defines functions which aren't ever invoked
}

...编译是因为,因为它是 fun interface,您可以只将 lambda 传递给构造函数,但实际上并没有做任何类似于您期望的事情。

【讨论】:

  • 函数声明可以是 lambda 表达式的一部分吗?
  • 当我写 onWebResponseHandler.aFunctionWhichIsDefinedAndNeverCalled 会发生什么?你也说过:你可以只将 lambda 传递给构造函数,但接口没有构造函数。
  • 您正在定义一个本地函数,它不能从外部调用。你可以像这里一样声明函数,但它们只能在 lambda 中调用。
  • 另外,定义一个fun interface确实为接口创建一个构造函数:一个接受单个 lambda。
  • 我从未见过这样的 lambda.. 通常 lambda 有输入参数,后跟 ->,然后是花括号内的实现。我无法理解这个 lambda :(
【解决方案2】:

这里有一种不同的解释方式可能会有所帮助。

如果要将接口的实现分配给变量或属性,通常必须使用像这样的匿名类语法。注意object: override的使用。

val onWebResponseHandler = object : VolleyHandler.WebResponseHandler {
    override fun onWebResponseFinished(jsonObject: JSONObject) {
        Log.v("da", jsonObject.toString())
    }
}

您还可以在匿名类中定义其他公共函数,但这毫无意义,因为无法访问匿名类的唯一成员,因此在此示例中您将无法调用函数 foo()

val onWebResponseHandler = object : VolleyHandler.WebResponseHandler {
    override fun onWebResponseFinished(jsonObject: JSONObject) {
        Log.v("da", jsonObject.toString())
    }

    fun foo() {
        Log.v("something", "something")
    }
}

如果您将接口设为fun interface,则可以使用 lambda 语法来定义像这样的单个函数。这就是 SAM 转换。它将您的 lambda 视为您接口的匿名实现。

val onWebResponseHandler = VolleyHandler.WebResponseHandler { jsonObject ->
    Log.v("da", jsonObject.toString())
}

另外,如果一个lambda只有一个参数,你可以省略定义参数名,直接调用it,这样就相当于上面的:

val onWebResponseHandler = VolleyHandler.WebResponseHandler {
    Log.v("da", it.toString())
}

另外,请注意,您可以在代码中的任何位置定义函数,包括在另一个函数的中间。例如:

fun bar() {
    
    fun foo() {
        Log.i("foo", "foo")
    }

    foo()
}

等价于

fun bar() {
    
    val foo: () -> Unit = {
        Log.i("foo", "foo")
    }

    foo() // or foo.invoke()
}

因此,在您的原始代码中,您将这些概念结合起来创建了一个 lambda 函数,其中定义了其他从未使用过的函数。混淆可能是因为它看起来类似于我的顶部代码块,我们在其中使用object: 语法定义了一个匿名类。最大的不同是您省略了object:override,因此您使用的是 lambda 语法。您的代码相当于:

val onWebResponseHandler = VolleyHandler.WebResponseHandler { jsonObject ->

    val onWebResponseFinished: (JSONObject) -> Unit = {
        Log.v("da", "dsa")
    }

    val foo: () -> Unit = {
        jsonObject.getString("name")
    }

}

没有 lambda 语法(SAM 转换)看起来像

val onWebResponseHandler = object : VolleyHandler.WebResponseHandler {
    override fun onWebResponseFinished(jsonObject: JSONObject) {
        val onWebResponseFinished: (JSONObject) -> Unit = {
            Log.v("da", "dsa")
        }

        val foo: () -> Unit = {
            jsonObject.getString("name")
        }
    }
}

【讨论】:

  • 首先谢谢你(一如既往)。其次,您能告诉我object:VolleyHandler.WebResponseHandlerobject的作用是什么吗?它与伴侣有某种关系吗?因为当我省略 object 时,我得到了以下错误:“Classifier 'WebResponseHandler' 没有伴随对象,因此必须在此处初始化”
  • object 是一种定义单例类的方式,它是一个没有构造函数的类,在第一次使用时自动实例化,整个应用程序只存在一个实例。有三种使用方式。
  • (1) 伴生对象是与其他类一起使用的对象,可以通过该类的名称来调用。 (2) 您可以使用object 而不是class 将整个类简单地定义为一个对象。 (3) 你可以像我上面那样使用它们来创建一个匿名类,这意味着你正在创建一个实现接口的object 或者是某个东西的子类,甚至没有给它一个名字。与 Java 中的匿名类的概念相同。
  • SAM 转换是您可以使用 lambda 语法来创建具有单一抽象方法的接口的匿名实现,而不是使用 object: 语法。
  • 非常感谢,我想我理解所有我的错误
猜你喜欢
  • 2019-05-17
  • 2022-01-08
  • 2022-11-23
  • 1970-01-01
  • 2017-01-01
  • 2020-03-24
  • 2021-10-10
  • 2020-03-08
  • 1970-01-01
相关资源
最近更新 更多