为什么我无法访问 Kotlin 中匿名对象的非私有成员?

Why do I can't access to non-private member of anonymous object in Kotlin?

提问人:gstackoverflow 提问时间:6/29/2023 更新时间:6/29/2023 访问量:126

问:

我是一名 Java 开发人员,现在我正在学习 Kotlin。

我遇到了一段有趣的(工作)代码:

class C {
    private fun getObject() = object {
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x)
    }
}

但是,如果我删除方法的修饰符:privategetObject

class C {
    fun getObject() = object {
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x)
    }
}

Kotlin 抱怨

Unresolved reference: x

你能解释一下为什么这样做吗?从我的角度来看,它看起来有点奇怪。

kotlin access-modifiers 类匿名 对象

评论

3赞 marstran 6/29/2023
我的猜测是,当函数是公共的时,它会迫使开发人员明确返回类型。顺便说一句,该行为在此处的文档中定义:kotlinlang.org/docs/...
0赞 marstran 6/29/2023
另一个原因(这可能是我考虑它的主要原因)是公共函数可以在外部调用(例如,如果您正在编写库,则可以从应用程序中调用),并且无法进行类型检查,因为匿名类型(非名义类型)无法在字节码中表示。它只能在编译单个编译单元时在 Kotlin 编译器中表示。当函数是私有的时,这是可以保证的。
0赞 k314159 6/29/2023
@marstran 我不认为你写的“匿名类型(非名义类型)不能在字节码中表示”是正确的:查看字节码,它创建了一个公共最终类 C$getObject$1 和一个公共 getter 方法“getX()”。也许这是一个故意的决定,不允许它,以便匿名对象的创建者可以更改其内容,而不必担心向后兼容性。
0赞 marstran 6/29/2023
啊哈,感谢您查看@k314159
0赞 Ivo 6/29/2023
也许与子类能够覆盖公共方法有关,尽管当它没有被定义时,这应该无关紧要open

答:

1赞 broot 6/29/2023 #1

为什么它不起作用?

这是因为我们创建了一个匿名对象/类,因此它在用户可用的类型系统中没有表示形式。没有带属性的类,我们只能将这个对象表示为 。当然,编译器在内部会创建一个带有 的类,但是这个类对我们来说是不可见的,我们不能将此类对象及其真实类型存储在属性中,等等。xAnyx

那么为什么它确实有效呢?

Kotlin 和其他一些语言有时会为本地代码提供“超能力”。这方面有多个例子:

  • Kotlin:如上所述,访问成员,但前提是私有。object
  • Kotlin:属性的智能转换,但前提是在本地模块中。
  • Java:类型推断,但仅适用于局部变量。
  • Kotlin/Java:交集类型,但仅适用于局部变量。
  • Kotlin/Java:访问嵌套/外部类的私有字段。

很难提供一个简单的答案来解释我们为什么要这样做。但一般来说,当我们处理本地或私有代码时,我们会关注这个小的功能片段,我们考虑文件或类的实现细节。然后,我们通常更喜欢方便和隐含,而不是保持代码的严格和明确。如果我们必须改变任何事情,我们会在一个地方完成。

如果组件是公共的,则相反。在这种情况下,它们可以在代码周围的几十个地方使用,我们无法记住它们,因此如果我们保持代码更严格,维护起来会更安全、更容易,例如创建一个常规的命名类而不是匿名类。

如果从另一个模块访问代码,情况会更糟。在这种情况下,编译器不知道原始代码的完整上下文,它只看到一个返回具有某个随机名称的类的方法。Kotlin 可以通过使用注释在字节码中添加额外的信息,但 Java/JavaScript 会完全忽略它们。此外,由于我们没有为匿名类提供名称,编译器必须自动生成它。如果它将来会生成不同的名称,那将是二进制不兼容的更改。

另一个可能的原因是编译器的复杂性。为了实现这些功能,编译器必须保留有关代码的附加元数据,并且必须应用一些技巧来“打破规则”。如果只允许在本地进行,编译器可以一次对单个文件/类/模块执行这些复杂的转换,但通常保持更简单的代码表示形式。