编程Kotlin/Java 开发者

Kotlin 中构造函数的继承是如何工作的?super 构造函数调用是如何运作的?在结合 primary 和 secondary 构造函数时有哪些潜在的陷阱?请提供不同情况的示例,并解释与 Java 的区别。

用 Hintsage AI 助手通过面试

答案

在 Kotlin 中,一个类可以有一个 primary constructor 和任意数量的 secondary constructors。与 Java 的不同之处在于,Kotlin 的 primary constructor 在类头中声明,并可以包含参数和修饰符。Secondary constructors 必须调用另一个 secondary constructor、primary constructor,或者超类的构造函数(通过关键字 super)。

关键特点:

  • 如果超类有一个带必需参数的 primary constructor,派生类必须明确通过 : super(...) 调用它。
  • 在 secondary constructor 中,通过 super(...) 或另一个 secondary/primary constructor 调用基类。
  • 不允许初始化冲突:所有字段必须始终被正确初始化。

使用继承的示例:

open class A(val a: Int) { constructor(a: Int, str: String) : this(a) { println("A中的二级构造函数: $str") } } class B : A { constructor(a: Int) : super(a) { println("B中的二级构造函数") } constructor(a: Int, s: String) : super(a, s) { println("B中的二级构造函数 #2") } }

与 Java 的方法有何不同:

  • 在 Kotlin 中,不可以不调用超类构造函数。
  • Primary constructor 总是初始化基参数,并且可以是隐式的。

难题

在 Kotlin 中,如果基础类只有特定构造函数,是否可以在不明确调用超类构造函数的情况下创建子类?

答案: 不可以,与 Java 不同,在 Kotlin 中必须调用超类构造函数,并且明确在类头或在关键字 : super() 后指定。

错误示例:

open class Base(val x: Int) class Derived : Base // 编译错误!

由于不熟悉该主题而导致的实际错误示例


故事

在微服务项目中,迁移到 Kotlin 后忘记了显式指定父构造函数:未调用带必需参数的超类构造函数,服务无法编译,必须重构签名。


故事

Android 项目具有深层次的层次结构(Activity → BaseActivity → CustomActivity),添加 secondary constructor 时丢失了参数,最终调用了错误的基构造函数,部分字段保持为 null,应用程序在运行时因 NPE 崩溃。


故事

在开源库代码中,子类的 secondary constructor 偶然绕过了 primary constructor,导致两个不同的初始化路径:有时字段被初始化,有时没有。这个错误直到经过很长时间和复杂的 bug 报告才被发现。