Kotlin如何调度调用运算符?

时间:2019-01-04 20:33:07

标签: kotlin

Kotlin如何消除函数调用,构造函数,伴随对象和调用重载的歧义?在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编译器将在同一个作用域中接受可调用变量和同义函数,但是在没有同义类的情况下接受(在某些情况下,但在其他情况下则不这样)?另外,在具有作用域相同的调用站点语法的作用域构造函数或伴随对象存在的情况下,调用如何工作?

2 个答案:

答案 0 :(得分:3)

根据我在kotlin-spec.asc#order-of-evaluation中看到的内容,有3条规则在起作用(不幸的是,某些时候文本不完整):

  1. 具有最佳类型匹配的表达式(在您的问题中不会出现)
  2. 本地声明优先于非本地声明。这也称为阴影。
      

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

  3. 如果所有相同名称的符号都处于同一级别,则具有invoke的属性优先于功能

    实际订单为

      
        
    • 函数描述符(包含类中的fun foo()
    •   
    • 调度接收器(请参阅declaring-extensions-as-members)      
          
      •     

        如果调度接收方的成员与扩展接收方的成员之间发生名称冲突,则扩展接收方优先。

          
      •   
    •   
    • 分机接收器(fun A.foo() defined outside of the class
    •   
    • 任务优先级排序器(据我了解,可以找到按类型划分的最佳匹配项,或者例如当存在一些默认参数时。我认为这是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.
}

答案 1 :(得分:2)

第一个代码段(简体):

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的对象。然后,让我们显式调用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
}

如果要调用对象的invoke函数,只需使用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遮盖了函数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的对象一样,我们可以使用()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的{​​{1}}函数,则必须明确指定它:

invoke

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

第一段:

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
}

希望这能回答您的问题。