Kotlin - 函数的调用运算符重载

5

我目前正在学习Kotlin-运算符重载
我正在尝试通过一个示例来理解如何为函数的invoke()函数进行运算符重载。

预测试

  • Kotlin's Extension Functions

    fun exampleOfExtensionFunction() {
        fun Int.randomize(): Int {
            return Random(this.toLong()).nextInt()
        }
    
        val randomizedFive = 5.randomize()
        println("$randomizedFive")
    }
    

    Prints :

    -1157408321

  • In Kotlin, functions can be declared as variables with types

    fun exampleOfFunctionType() {
        val printNumber: (number: Int) -> Unit
        printNumber = { number ->
            println("[$number = ${number.toString(16).toUpperCase()} = ${number.toString(2)}]")
        }
    
        printNumber(1023)
    }
    

    Prints :

    [1023 = 3FF = 1111111111]

  • Kotlin allows operator overloading with both extension and member functions

    fun exampleOfOperatorOverloadingUsingExtensionFunction() {
        class MyType() {
            val strings: ArrayList<String> = ArrayList<String>()
            override fun toString(): String {
                val joiner: StringJoiner = StringJoiner(" , ", "{ ", " }")
                for (string in strings) {
                    joiner.add("\"$string\"")
                }
                return joiner.toString()
            }
        }
    
        operator fun MyType.contains(stringToCheck: String): Boolean {
            for (stringElement in strings) {
                if (stringElement == stringToCheck) return true
            }
            return false
        }
    
        val myType = MyType()
        myType.strings.add("one")
        myType.strings.add("two")
        myType.strings.add("three")
        println("$myType")
        println("(myType.contains(\"four\")) = ${myType.contains("four")} , (\"three\" in myType) = ${"three" in myType}")
    }
    

    Prints :

    { "one" , "two" , "three" }
    (myType.contains("four")) = false , ("three" in myType) = true

测试尝试
根据上述内容,我尝试使用类型(Boolean, Boolean, Boolean) -> Boolean作为扩展函数invoke(flag1: Boolean, flag2: Boolean, flag3: Boolean)的接收器类型,创建一个函数的invoke()运算符重载的示例。然而,这并没有按预期工作。

    fun attemptFunctionInvokeOperatorOverloading() {
        operator fun ((Boolean, Boolean, Boolean) -> Boolean).invoke(flag1: Boolean, flag2: Boolean, flag3: Boolean): Boolean {
            println("Overloaded invoke operator")
            return flag1 && flag2 && flag3
        }

        var func1: ((Boolean, Boolean, Boolean) -> Boolean) = { flag1, flag2, flag3 ->
            println("func1 body")
            flag1 && flag2 && flag3
        }

        fun func2(flag1: Boolean, flag2: Boolean, flag3: Boolean): Boolean {
            println("func2 body")
            return flag1 && flag2 && flag3
        }

        func1(true, true, false)
        func2(true, true, true)
    }

打印:

函数1体
  函数2体

预期结果:

重载调用运算符
  重载调用运算符

另一个问题
这到底是什么?(如果不是运算符重载)

        operator fun ((Boolean, Boolean, Boolean) -> Boolean).invoke(flag1: Boolean, flag2: Boolean, flag3: Boolean): Boolean {
            println("Overloaded invoke operator")
            return flag1 && flag2 && flag3
        }
2个回答

15

正如另一个答案中所说,invoke在函数对象本身上定义,因此您无法使用扩展方法覆盖它。

我认为这里更深层次的问题是对该功能目的的轻微误解。让我们来看看plus代替+操作符。

我想您会同意尝试定义operator fun Int.plus(b: Int): Int { /* ... */}没有意义,因为覆盖int默认的+操作符是一件非常危险的事情,不是吗?

但是,如果您定义了一个复数类:

data class Complex(real: Double, img: Double)

那么定义这个来求和复数,就是完全合理的:

operator fun Complex.plus(other: Complex): Complex {
  val real = this.real + other.real
  val img  = this.img + other.img
  return Complex(real, img)
}

所以,invoke()也是一样的道理: ()的意思是它相当于为您的类型调用一个函数,并在已经是函数的东西上覆盖invoke只会引发问题。相反,您想要使用它来为自己的对象提供类似于函数的语法。

例如,假设您定义了以下接口:

interface MyCallback {
  fun call(ctx: MyContext)
}

您可以按照惯常方式使用:

callback.call(ctx)

但是通过实现invoke运算符重载,您可以将其用作函数:

operator fun MyCallback.invoke(ctx: Context) = this.call(ctx)

/* Elsewhere... */
callback(ctx)

希望这样解释清楚了如何使用invoke/()


3
你的问题涉及于分辨率优先级。根据Kotlin文档的说明
如果一个类有一个成员函数,并且定义了一个扩展函数,该扩展函数具有相同的接收器类型、相同的名称并且适用于给定参数,则成员函数总是胜出
因此,你的operator fun ((Boolean, Boolean, Boolean) -> Boolean).invoke(...)扩展函数从未被调用,因为成员invoke具有优先级。
另一个答案:
确实是运算符重载,但通过扩展实现。同样,由于(Boolean, Boolean, Boolean) -> Boolean已经定义了fun invoke(Boolean, Boolean, Boolean): Boolean,因此你的扩展失效。

2
这是因为,如果一个扩展的优先级高于成员函数,函数调用的结果可能是不确定的,并且仅仅通过在项目中包含另一个库而不知道(假设该扩展在作用域或已导入)。 - Ruckus T-Boom
这是否意味着无法有效地重载invoke函数?因为每次成员函数都优先执行。而且由于在Kotlin中没有定义函数类型的类,因此无法将运算符重载实现为函数的成员函数。 - user5132301
1
对于函数类型是的,但任何具有开放调用函数的类都可以被覆盖。函数类型的重点是接受与签名匹配的任何内容。与其他任何内容一样,如果您想要特定的功能,则创建具有该功能的类。 - Ruckus T-Boom

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