编程Kotlin开发人员

描述Kotlin中使用init块和关键字'super'进行对象初始化的机制。这与Java中的初始化顺序有什么不同,继承层次和构造函数的调用有什么细节?

用 Hintsage AI 助手通过面试

答案。

在Kotlin中,init块和super的调用负责在考虑类层次结构的情况下正确初始化对象。这是基本机制,影响构造和继承的工作。

问题的历史

在Java中,可以通过显式调用super(...)或隐式默认调用构造函数。而在Kotlin中,情况完全不同:主构造函数和辅构造函数之间有明确的划分,init块用于初始化。

问题

主要的复杂性在于正确理解属性何时初始化,init块何时被调用,以及构造函数的调用顺序,特别是在类继承和主/辅构造函数的组合时。

解决方案

  • 在Kotlin中,首先执行所有辅构造函数对主构造函数的委托,然后调用超类构造函数,最后执行init块;
  • 为了将参数传递给super,需要在构造函数中显式指定;
  • 重要的是要记住,在执行init块之前,类体中声明的属性会被初始化。

代码示例:

open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // 输出: // Base init: Ivan // Derived init: Ivan, 25

关键特点:

  • 外部类的所有属性在执行init块之前被初始化;
  • 首先执行超类构造函数;
  • 辅构造函数必须直接或间接调用主构造函数。

有陷阱的问题。

在继承链中,init块以何种顺序执行?

首先执行超类的init块,在初始化其属性之后,然后执行子类的init块,顺序是基于各自的属性。

代码示例:

open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B

在Kotlin中可以在构造函数之外调用super()吗?

不可以,超类的调用只能作为构造函数声明的一部分来提供超构造函数的参数。

在Kotlin中,辅构造函数可以不调用主构造函数吗?

不可以,所有的辅构造函数必须将调用委托给另一个辅构造函数或主构造函数。主构造函数反过来会调用超构造函数。

常见错误和反模式

  • 在调用超构造函数之前设置计算属性——将导致错误;
  • 在使用尚未初始化的属性时出现初始化顺序错误;
  • 辅构造函数中的循环依赖。

生活中的例子

消极示例

开发人员试图在超类的init块中访问在子类中初始化的属性:

open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **优点:** - 使用多态性。 **缺点:** - 属性s在调用超类init时尚未初始化——结果为null或错误。 ## 积极示例 将初始化分开,以避免在构造函数中调用可重写的方法或属性: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **优点:** - 不访问未初始化的数据; - 保证初始化顺序。 **缺点:** - 为了正确初始化,代码量增加。