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

为什么可以在子类中调用密封类的私有构造函数?

  •  4
  • rosshjb  · 技术社区  · 7 年前

    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()
    

    正如上面的代码所示,密封类的 私有的 构造函数在密封类本身之外调用。当子类被实例化时,在调用子类自己的构造函数之前,将调用父类(密封类)的构造函数。它只是可见性修饰符的例外吗?

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

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

    2 回复  |  直到 7 年前
        1
  •  3
  •   Deepak    7 年前

    请考虑以下代码:

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

    上面的代码编译得很好,因为构造函数是在类A内部调用的。 如果类D试图在外部继承,它将不会编译。

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

    如本页所述 Sealed class in Kotlin ,

    密封类可以有子类,但所有子类都必须声明在与密封类本身相同的文件中。(在Kotlin1.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")
    }
    

    因此,允许在类外部(但在同一个文件中)使用密封类的私有构造函数可以被视为一种增强,以使代码更干净。

        2
  •  3
  •   zsmb13    7 年前

    您可以通过查看生成的字节码(您可以通过 Tools -> Kotlin -> 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 ...
    }
    

    您仍然不能子类 EXPR 从Kotlin,因为Kotlin编译器知道它是来自文件中元数据的密封类,并且会尊重它。

    至于Java客户端代码,您不能自己访问同一个构造函数,因为 DefaultConstructorMarker 包在中是私有的吗 kotlin.jvm.internal 它所在的包,因此即使手动为其编写import语句,编译器也不允许这样做。

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