Kotlin 主次构造函数理解
今天在做需求时需要自定义两个 View,在取自定义属性时稍微纠结了一下该放在哪里
- 是不是该在主构造函数中调用
- 但是主构造方法不能有方法体
- 那是不是不应该放在 init 代码块里
- 但是 init 代码块不是会在类构造前就调用的吗,那怎么取到自定义属性
搞来搞去,越来越乱,和 Java 的用法彻底搞混淆了,说来惭愧,用了 Kotlin 这么多年,还是没有彻底理清这种基础的东西,现在做点笔记记录下
Java 构造函数与 init 代码块
首先直接捋一下 Java 的,相对 Kotlin 的应该比较基础
想要在 Android 项目中直接跑 Java 代码,可以 new module 创建一个 Java or Kotlin Library,选择 Java 语言即可
1 |
|
从上面的代码和运行结果可以看出,当创建 Java 对象时:
- 先执行初始化块;
- 如果定义多个初始化块,则前面的先执行,后面的后执行;
- 然后执行构造函数。
- 虽然可以定义多个初始化块,但是没有意义, 一般合并在一起,代码更加简洁。
static init 代码块
使用 static 修饰符定义的初始化块,称为静态初始化块,也叫类初始化块。普通初始化块是对对象初始化,类初始化块是对类初始化。因此静态初始化块总是比普通初始化块先执行。
1 |
|
从上面的代码和执行结果看:
- 会先进行类初始化, 然后对象初始化,类初始化全部先执行一遍。先执行父类初始化,然后子类初始化。
- 对象初始化时,同样先执行父类的初始化块,构造函数,然后执行子类的。
另外,静态初始化块和静态成员变量,初始化的值与代码顺序相对应。如果你先 int a = 5,然后再写 init 块改变它的值为 8,最后输出 a 为 8,反之如果先 init 块中赋值 a = 8,再 int a = 5,那么最后输出 a 为 5
总结
- 初始化块是构造函数的补充。
- 初始化块总在构造函数之前执行。
- 初始化块是一段固定执行的代码, 不接受任何参数, 因此,如果一段初始化代码对所有对象相同, 且不用接收任何参数,则可以把这段初始化代码放在初始化块中。
实际上初始化块是一个假象, 使用 javac 编译 Java 类后, 初始化块消失,被”还原”到构造器中,且位于构造器所有函数的前面。
Kotlin 构造函数与 init 代码块
然后再看一下 Kotlin 相关的内容,在 Kotlin 中有两种类型的构造函数,分别是主构造函数(主构造器)和次级构造函数(次级构造器),在 Kotlin 类中只有一个主构造函数,而次级构造函数可以是一个或者多个。
construction 在 Kotlin 中是一个关键字,在 Java 中,构造方法名必须和类名相同,例如文中开头写的 Java 的构造函数;而在 Kotlin 中,是通过 constructor 关键字来标明的,对于主构造函数来说,它的位置在类的标题中声明,而对于次级构造函数来说它的位置在类中。
并且当 constructor 关键字没有注解和可见性修饰符作用于它时,constructor 关键字可以省略。
1 |
|
主构造函数
主构造函数用于初始化类,它在类标题中声明。需要注意的是主构造函数不能包含任何代码,所以这时的初始化代码块本质就是主构造函数的方法体。
当我们定义一个类并没有声明一个主构造函数的时候,Kotlin 会默认为我们生成一个无参的主构造函数,这一点和 Java 一样
次级构造函数
我们可以在同一类中使用主构造函数和次级构造函数。如果一个类有次构造函数,那么这些次构造函数就必须调用主构造函数,方式可以不同:
- 无参的主构造函数会被次级构造函数隐式调用,且顺序在次级构造函数之前
- 可以使用 this() 对同一个类中的另一个构造函数进行调用
- 可以使用 super() 来调用父类的构造函数
总结
Kotlin 的用法和 Java 的有比较大的不同,使用时要注意区分
回到问题
梳理完毕,现在我们看回一开始的问题
- 是不是该在主构造函数中调用
- 确实是应该在主构造中调用,View 构造完就该读取自定义属性了
- 但是主构造方法不能有方法体
- init 就是主构造的方法体
- 那是不是不应该放在 init 代码块里
- 应该放在 init 代码块里,因为它就是主构造的方法体
- 但是 init 代码块不是会在类构造前就调用的吗,那怎么取到自定义属性
- Java 的会在类构造前调用,但是 Kotlin 的不会,Kotlin 中 init 就是主构造函数的一部分,执行到 init 时,已经跑完主构造函数,它能够取到自定义属性