【问题标题】:Why private constructor of sealed class can be called in sub class?为什么密封类的私有构造函数可以在子类中调用?
【发布时间】:2019-02-10 06:21:43
【问题描述】:

Sealed class in Kotlin 只能有 private 构造函数。这意味着我们只能调用构造函数本身:

密封类不允许有非私有的构造函数(它们的构造函数默认是私有的)。

// `private` and `constructor()` are redundant.
sealed class Expr private constructor()

但是,当我们使用密封类时,子类必须继承密封类:

// Above Kotlin 1.1
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()

正如您在上面的代码中看到的,密封类的private 构造函数是在密封类本身之外调用的。子类实例化时,先调用父类(密封类)的构造函数,再调用子类自己的构造函数。它只是可见性修饰符的例外吗?

https://kotlinlang.org/docs/reference/visibility-modifiers.html#classes-and-interfaces

对于在类中声明的成员:private 表示仅在该类中可见(包括其所有成员);

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    考虑以下代码:

    open class A private constructor(var name: String){
        class B : A("B")
        class C : A("C")
    }
    

    上面的代码编译得很好,因为构造函数是在类 A 中调用的。 如果一个类 D 试图从 A 之外继承,它将无法编译。

    class D : A("D") // Error: Cannot access '<init>': it is private in 'A'
    

    正如页面上提到的Sealed class in Kotlin

    密封类可以有子类,但所有子类都必须在与密封类本身相同的文件中声明。 (在 Kotlin 1.1 之前,规则更加严格:类必须嵌套在密封类的声明中)。

    似乎 kotlin 只放宽了嵌套类的要求。

    因此,以下代码在 1.1+ 中可以正常工作,但在早期版本中会失败:

    sealed class A(var name: String)
    class B : A("B")
    class C : A("C")
    

    而在 1.1 之前的版本中需要以下代码,它尊重私有构造函数。

    sealed class A (var name: String){
        class B : A("B")
        class C : A("C")
    }
    

    因此,允许在类之外(但在同一个文件中)密封类的私有构造函数可以被认为是使代码更清晰的增强。

    【讨论】:

    • 您是说最初的 Kotlin(1.1) 严格遵循私有成员只能在类自身中引用的语言规则吗?但是,语言设计者现在放宽了规则,允许密封类的子类可以在密封类之外声明,尽管它违反了不能在类本身之外引用私有成员的语言规则。
    • 是的,我想是这样的。
    【解决方案2】:

    您可以通过查看生成的字节码来了解发生了什么(您可以通过转到Tools -&gt; Kotlin -&gt; Show Kotlin Bytecode 然后在出现的窗格中选择Decompile 来做到这一点。)。将其反编译为 Java 会显示 Expr 类的以下代码:

    public abstract class Expr {
       private Expr() {
       }
    
       // $FF: synthetic method
       public Expr(DefaultConstructorMarker $constructor_marker) {
          this();
       }
    }
    

    所以Expr 类生成的非私有构造函数,带有一个特殊参数。然后,如您所料,如果您查看 Const 的反编译字节码,您会看到它调用了这个构造函数:

    public final class Const extends Expr {
       public Const(double number) {
          super((DefaultConstructorMarker)null);
          this.number = number;
       }
    
       // other fields and methods ...
    }
    

    您仍然不能从 Kotlin 继承 Expr,因为 Kotlin 编译器知道它是文件中元数据的密封类,并且会尊重这一点。

    至于 Java 客户端代码,你不能自己访问这个相同的构造函数,因为 DefaultConstructorMarker 在它所在的 kotlin.jvm.internal 包中是包私有的,所以即使你手动为它写出导入语句,编译器不会允许的。

    我的猜测是包私有可见性可能只在编译时强制执行,这就是 Kotlin 编译器能够输出与上面的 sn-p 对应的字节码的原因(虽然不完全确定)。

    【讨论】:

    • 感谢您提供详细的技术答案,但我想知道为什么 Kotlin 中的私有成员不能在其外部引用的语言规则被违反的原因。但是,您的回答很高兴知道它在下面是如何工作的!
    • “至于 Java 客户端代码,你不能自己访问这个相同的构造函数,因为 DefaultConstructorMarker 在 kotlin.jvm.internal 包中是包私有的”即使没有这个,你也无法访问直接从代码合成方法(或其他元素)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    • 2014-12-16
    • 1970-01-01
    • 2013-10-31
    • 1970-01-01
    • 1970-01-01
    • 2015-04-24
    相关资源
    最近更新 更多