代码之家  ›  专栏  ›  技术社区  ›  breandan

Kotlin如何分派invoke操作符?

  •  10
  • breandan  · 技术社区  · 7 年前

    Kotlin如何消除函数调用、构造函数、伴随对象和调用重载的歧义?在科特林 1.3.11 ,我可以在同一范围内声明两个同名成员:

    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints: "test invocation"
    
        // I think this should fail to compile, but it works
        fun test() = println("test function")
    
        test() // Prints: "test function"
    }
    

    您可能认为它使用了最新的声明,但事实并非如此!

    fun main(args: Array<String>) {
        fun test() = println("test function")
    
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints: "test function"
    }
    

    fun test() = println("test function")
    
    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints "test invocation"
    }
    

    类似地,如果将对象移到外部,也会编译:

    val test = object {
        operator fun invoke() = println("test invocation")
    }
    
    fun main(args: Array<String>) {
        fun test() = println("test function")
        test() // Prints: "test function"
    }
    

    我也可以把它们都搬到外面:

    val test = object {
        operator fun invoke() = println("test invocation")
    }
    
    fun test() = println("test function")
    
    fun main(args: Array<String>) {
        test() // Prints: "test function"
    }
    

    但如果我超负荷 test 使用类名,它不会编译:

    class test {} // Does not compile
    
    fun test() = println("test function")
    
    val test = object {
        operator fun invoke() = println("test invocation")
    }
    

    试图编译此程序会导致以下错误:

    Error:(1, 6) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
    Error:(1, 6) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test
    Error:(2, 0) Conflicting overloads: public fun test(): Unit defined in root package in file Simplest version.kt, public constructor test() defined in test, public val test: Any defined in root package in file Simplest version.kt, public final class test defined in root package in file Simplest version.kt
    Error:(3, 4) Conflicting declarations: public fun test(): Unit, public constructor test(), public val test: Any, public final class test
    

    但是,在使用嵌套作用域时,它会编译:

    class test {
        constructor() {
            println("test constructor")
        }
    }
    
    fun main(args: Array<String>) {
        fun test() = println("test function")
    
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints: "test function"
    }
    

    伴随对象和构造函数之间也存在一些模糊性:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    }
    
    fun main(args: Array<String>) {
        test() // Prints: "test constructor"
    }
    

    不知何故,下面的示例也编译了:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    }
    
    fun main(args: Array<String>) {
        test() // Prints: "test constructor"
    
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints: "test invocation"
    
        fun test() = println("test function")
    
        test() // Prints: "test function"
    }
    

    这就更不直观了:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    
        operator fun invoke() = println("test invocation overload")
    }
    
    fun main(args: Array<String>) {
        val test = test() // Prints: "test constructor"
    
        val test1 = test() // Prints: "test invocation overload"
    }
    

    重载命名成员的规则是什么?为什么Kotlin编译器会接受同一作用域中的可invocable变量和同名函数,但不接受存在同名类的情况(在某些情况下,但不接受其他情况)?另外,如果存在具有相同调用站点语法的作用域构造函数或伴随对象,则invoke如何工作?

    2 回复  |  直到 7 年前
        1
  •  4
  •   leonardkraemer    7 年前

    从我看到的 kotlin-spec.asc#order-of-evaluation 有三条规则在起作用(不幸的是文本在某些方面不完整):

    1. 本地声明优先于非本地声明。这也称为阴影。

      简单名称是单个标识符。它的含义取决于该名称的符号在范围内。如果范围中只有具有该名称的符号,则简单名称引用它。如果范围中有多个具有此名称的符号,则非正式地选择其声明与简单名称的出现“最接近”的符号。有关更精确的规则,请参阅TODO

    2. 如果具有相同名称的所有符号处于同一级别,则函数优先于具有invoke的属性

      实际订单是

      • fun foo()
      • 调度接收器(参见 declaring-extensions-as-members
        • 如果调度接收方和扩展接收方的成员之间存在名称冲突,则以扩展接收方为准。

      • fun A.foo() defined outside of the class )
      • TaskPrioritizer(据我所知)按类型查找最佳匹配,例如,当有一些默认参数时。我想这就是 invoke

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    
        operator fun invoke() = println("test invocation overload")
    }
    
    fun main(args: Array<String>) {
        val test = test() // Prints: "test constructor" //you create a local variable with invoke. Constructor is executed.
    
        val test1 = test() // Prints: "test invocation overload" //invoke of the local variable is called.
    
        test.Companion() //access the companions' invoke which is shadowed by the other invoke.
    }
    
        2
  •  3
  •   xinaiz    7 年前

    第一个代码段(简化):

    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation")
        }
        test()
        fun test() = println("test function")    
        test()
    }
    

    从字节码反编译到Java如下:

    public static final void main(@NotNull String[] args) {
          Intrinsics.checkParameterIsNotNull(args, "args");
          <undefinedtype> test = new Object() {
             public final void invoke() {
                String var1 = "test invocation";
                System.out.println(var1);
             }
          };
          ((<undefinedtype>)test).invoke();
          <undefinedtype> test$ = null.INSTANCE; // <---
          test$.invoke(); // <---
       }
    

    我们不要过早下结论,让我们颠倒声明的顺序:

    fun main(args: Array<String>) {
        fun test() = println("test function")
        test()
        val test = object {
            operator fun invoke() = println("test invocation")
        }
        test()
    }
    

    public static final void main(@NotNull String[] args) {
          Intrinsics.checkParameterIsNotNull(args, "args");
          <undefinedtype> test$ = null.INSTANCE; // <---
          test$.invoke();
          Object var10000 = new Object() {
             public final void invoke() {
                String var1 = "test invocation";
                System.out.println(var1);
             }
          };
          test$.invoke(); // <---
       }
    

    invoke 当它们都在同一个作用域(类作用域、函数作用域)中声明时。然后我们显式地调用 援引 运算符而不是 () 语法:

    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation")
        }
        fun test() = println("test function")
    
        test.invoke() // calls object's invoke function
        test() // calls local function
    }
    

    如果你想调用对象的 函数,用 x.invoke() 语法。如果要调用函数,请使用 语法。

    另一件事是,当您在内部作用域中定义对象/函数/类,而在外部作用域中已经定义了一个同名的对象/函数/类时,就会发生名称阴影。因此在你的例子中:

    fun test() = println("test function")
    
    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation")
        }
    
        test() // Prints "test invocation"
    }
    

    局部变量 test 阴影函数 测试 在类范围中定义。如果我们只声明同一事物两次,一次在外部范围内,然后在内部范围内,就可以更好地看到这一点:

    val test = object {
        operator fun invoke() = println("test invocation 1")
    }
    
    fun main(args: Array<String>) {
        val test = object {
            operator fun invoke() = println("test invocation 2")
        }
        test() // Prints "test invocation 2"
    }
    

    在这里,如果它是函数阴影或属性,则没有什么不同。如果外部作用域不是类作用域,我不知道如何访问外部作用域。但是,如果所有这些都发生在一个类中,那么很简单:

    class SomeClass {
    
        fun test() = println("test function 1")
    
        fun main(args: Array<String>) {
            val test = object {
                operator fun invoke() = println("test invocation 2")
            }
    
            test() // Prints "test invocation 2"
            this@SomeClass.test() // Prints "test invocation 1"
        }
    }
    

    类声明的问题,如以下(简化示例)所示:

    class test {} // Does not compile
    fun test() = println("test function")
    

    就是不能区分函数调用和对象构造。与具有函数的对象一样 () 或 invoke() test.contruct() ),以上是允许的,但不是。

    最后,伴随的问题是:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    }
    
    fun main(args: Array<String>) {
        test() // Prints: "test constructor"
    }
    

    你要记住这点 companion object SomeClass.propertyInCompanion 事实上你打电话来了 SomeClass.Companion.propertyInCompanion . 在这里,如果有冲突,外部类总是获胜。如果你需要打电话 Companion 的 援引 函数,则必须显式指定它:

    fun main(args: Array<String>) {
        test() // Prints: "test constructor"
        test.Companion() // Prints: "test companion invocation"
    }
    

    最后两个代码段是上述所有代码(名称阴影、外部类>伴随)和局部变量阴影的组合:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    }
    
    fun main(args: Array<String>) {
        test() // class wins with companion, no local variable introducted
        val test = object { // local variable test shadows outer scope "test"
            operator fun invoke() = println("test invocation")
        }
        test() // calls local variable invoke function
        fun test() = println("test function") // local function shadows local variable
        test() // calls local function
    }
    

    第二个代码段:

    class test {
        constructor() {
            println("test constructor")
        }
    
        companion object {
            operator fun invoke() = println("test companion invocation")
        }
    
        operator fun invoke() = println("test invocation overload")
    }
    
    fun main(args: Array<String>) {
        val test = test() // class wins with companion. Also local variable shadows outer scope.
    
        val test1 = test() // calls invoke function of local variable
    }
    

    希望这能回答你的问题。