Kotlin 中 class 中的初始块位置
init block position in class in Kotlin
我最近遇到了这样一种情况,即使我已经使用 init 块通过构造函数分配了一个值,但我的标准变量的值被默认值替换了。
我试过的是:
class Example(function: Example.() -> Unit) {
init {
function()
}
var name = "default name"
}
// assigning it like this:
val example = Example { name = "new name" }
// print value
print(example.name) // prints "default name"
经过一番努力,我发现初始化块的位置很重要。如果我将 init 块放在 class 的最后,它会首先使用默认名称初始化名称,然后调用 function() 将值替换为 "new name".
如果我把它放在第一位,它找不到名称,它会在属性初始化时被 "default name" 替换。
这对我来说很奇怪。谁能解释为什么会这样?
原因是 kotlin 遵循 自上而下 方法
来自文档 (An in-depth look at Kotlin’s initializers) 初始化程序(属性 初始化程序和 init 块)按照它们在 class、top 中定义的顺序执行-到底部.
您可以定义多个辅助构造函数,但在创建 class 实例时只会调用一个,除非该构造函数显式调用另一个。
构造函数也可以有默认参数值,每次调用构造函数时都会计算这些值。与 属性 初始值设定项一样,这些可以是函数调用或其他将 运行 任意代码的表达式。
初始化器 运行 在 class 主构造函数的开头从上到下。
这是正确的方法
class Example(function: Example.() -> Unit) {
var name = "default name"
init {
function()
}
}
如 Kotlin 文档中所述:
During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers: ...
Java 构造函数只是在对象创建后 运行 的一个方法。在 运行 构建构造函数之前,所有 class 字段都被初始化。
在 Kotlin 中有两种类型的构造函数,即 主构造函数 和 辅助构造函数。我将主构造函数视为支持字段封装 built-in 的常规 java 构造函数。编译后,如果主构造函数字段已声明对整个 class.
可见,则它们将放在 class 的顶部
在java或kotlin中,构造函数在初始化class字段后被调用。但是在主构造函数中我们不能写任何语句。如果我们要编写需要在对象创建后执行的语句,我们必须将它们放在初始化块中。但是 init 块是按照它们出现在 class 主体中的方式执行的。我们可以在 class 中定义多个 init 块。它们将从上到下执行。
让我们用 init 块做一些实验..
Test.kt
fun main() {
Subject("a1")
}
class Element {
init {
println("Element init block 1")
}
constructor(message: String) {
println(message)
}
init {
println("Element init block 2")
}
}
class Subject(private val name: String, e: Element = Element("$name: first element")) {
private val field1: Int = 1
init {
println("$name: first init")
}
val e2 = Element("$name: second element")
init {
println("$name: second init")
}
val e3 = Element("$name: third element")
}
让我们编译上面的代码并 运行 它。
kotlinc Test.kt -include-runtime -d Test.jar
java -jar Test.jar
以上程序的输出为
Element init block 1
Element init block 2
a1: first element
a1: first init
Element init block 1
Element init block 2
a1: second element
a1: second init
Element init block 1
Element init block 2
a1: third element
如你所见,第一个主构造函数被调用,在次构造函数之前,所有的初始化块都被执行。这是因为 init 块按照它们在 class 主体中出现的顺序成为构造函数的一部分。
让我们将 kotlin 代码编译为 java 字节代码并将其反编译回 java。我用jd-gui
反编译了javaclasses。您可以在基于 arch linux 的发行版中使用 yay -S jd-gui-bin
安装它。
这是我反编译Subject.class
文件后得到的输出
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(mv = {1, 6, 0}, k = 1, xi = 48, d1 = {"[=13=]04\n[=13=]20[=13=]2\n[=13=]20[=13=]0\n[=13=]0\n[=13=]206\n[=13=]0\n[=13=]20[=13=]2\n[=13=]2\b[=13=]7\n[=13=]20\b0[=13=]02[=13=]20[=13=]1B72[=13=]60[=13=]22[=13=]20[=13=]32\b\b[=13=]20[=13=]42[=13=]20[=13=]5[=13=]6[=13=]20[=13=]6R10[=13=]72[=13=]20[=13=]5[=13=]6\b\n[=13=]02[=13=]4\b\b0\tR10\n2[=13=]20[=13=]5[=13=]6\b\n[=13=]02[=13=]4\b30\tR60\f2[=13=]20\rX[=13=]6[=13=]2\n[=13=]0R60[=13=]22[=13=]20[=13=]3X[=13=]4[=13=]6[=13=]2\n[=13=]0"}, d2 = {"LSubject;", "", "name", "", "e", "LElement;", "(Ljava/lang/String;LElement;)V", "e2", "getE2", "()LElement;", "e3", "getE3", "field1", ""})
public final class Subject {
@NotNull
private final String name;
private final int field1;
@NotNull
private final Element e2;
@NotNull
private final Element e3;
public Subject(@NotNull String name, @NotNull Element e) {
this.name = name;
this.field1 = 1;
System.out
.println(Intrinsics.stringPlus(this.name, ": first init"));
this.e2 = new Element(Intrinsics.stringPlus(this.name, ": second element"));
System.out
.println(Intrinsics.stringPlus(this.name, ": second init"));
this.e3 = new Element(Intrinsics.stringPlus(this.name, ": third element"));
}
@NotNull
public final Element getE2() {
return this.e2;
}
@NotNull
public final Element getE3() {
return this.e3;
}
}
如您所见,所有 init 块都按照它们在 class 主体中出现的顺序成为构造函数的一部分。我注意到一件事与 java 不同。 Class 字段已在构造函数中初始化。 Class 字段和 init 块按照它们在 class 正文中出现的顺序进行初始化。看来顺序在 kotlin 中是如此重要。
我最近遇到了这样一种情况,即使我已经使用 init 块通过构造函数分配了一个值,但我的标准变量的值被默认值替换了。
我试过的是:
class Example(function: Example.() -> Unit) {
init {
function()
}
var name = "default name"
}
// assigning it like this:
val example = Example { name = "new name" }
// print value
print(example.name) // prints "default name"
经过一番努力,我发现初始化块的位置很重要。如果我将 init 块放在 class 的最后,它会首先使用默认名称初始化名称,然后调用 function() 将值替换为 "new name".
如果我把它放在第一位,它找不到名称,它会在属性初始化时被 "default name" 替换。
这对我来说很奇怪。谁能解释为什么会这样?
原因是 kotlin 遵循 自上而下 方法
来自文档 (An in-depth look at Kotlin’s initializers) 初始化程序(属性 初始化程序和 init 块)按照它们在 class、top 中定义的顺序执行-到底部.
您可以定义多个辅助构造函数,但在创建 class 实例时只会调用一个,除非该构造函数显式调用另一个。
构造函数也可以有默认参数值,每次调用构造函数时都会计算这些值。与 属性 初始值设定项一样,这些可以是函数调用或其他将 运行 任意代码的表达式。
初始化器 运行 在 class 主构造函数的开头从上到下。
这是正确的方法
class Example(function: Example.() -> Unit) {
var name = "default name"
init {
function()
}
}
如 Kotlin 文档中所述:
During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers: ...
Java 构造函数只是在对象创建后 运行 的一个方法。在 运行 构建构造函数之前,所有 class 字段都被初始化。
在 Kotlin 中有两种类型的构造函数,即 主构造函数 和 辅助构造函数。我将主构造函数视为支持字段封装 built-in 的常规 java 构造函数。编译后,如果主构造函数字段已声明对整个 class.
可见,则它们将放在 class 的顶部在java或kotlin中,构造函数在初始化class字段后被调用。但是在主构造函数中我们不能写任何语句。如果我们要编写需要在对象创建后执行的语句,我们必须将它们放在初始化块中。但是 init 块是按照它们出现在 class 主体中的方式执行的。我们可以在 class 中定义多个 init 块。它们将从上到下执行。
让我们用 init 块做一些实验..
Test.kt
fun main() {
Subject("a1")
}
class Element {
init {
println("Element init block 1")
}
constructor(message: String) {
println(message)
}
init {
println("Element init block 2")
}
}
class Subject(private val name: String, e: Element = Element("$name: first element")) {
private val field1: Int = 1
init {
println("$name: first init")
}
val e2 = Element("$name: second element")
init {
println("$name: second init")
}
val e3 = Element("$name: third element")
}
让我们编译上面的代码并 运行 它。
kotlinc Test.kt -include-runtime -d Test.jar
java -jar Test.jar
以上程序的输出为
Element init block 1
Element init block 2
a1: first element
a1: first init
Element init block 1
Element init block 2
a1: second element
a1: second init
Element init block 1
Element init block 2
a1: third element
如你所见,第一个主构造函数被调用,在次构造函数之前,所有的初始化块都被执行。这是因为 init 块按照它们在 class 主体中出现的顺序成为构造函数的一部分。
让我们将 kotlin 代码编译为 java 字节代码并将其反编译回 java。我用jd-gui
反编译了javaclasses。您可以在基于 arch linux 的发行版中使用 yay -S jd-gui-bin
安装它。
这是我反编译Subject.class
文件后得到的输出
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(mv = {1, 6, 0}, k = 1, xi = 48, d1 = {"[=13=]04\n[=13=]20[=13=]2\n[=13=]20[=13=]0\n[=13=]0\n[=13=]206\n[=13=]0\n[=13=]20[=13=]2\n[=13=]2\b[=13=]7\n[=13=]20\b0[=13=]02[=13=]20[=13=]1B72[=13=]60[=13=]22[=13=]20[=13=]32\b\b[=13=]20[=13=]42[=13=]20[=13=]5[=13=]6[=13=]20[=13=]6R10[=13=]72[=13=]20[=13=]5[=13=]6\b\n[=13=]02[=13=]4\b\b0\tR10\n2[=13=]20[=13=]5[=13=]6\b\n[=13=]02[=13=]4\b30\tR60\f2[=13=]20\rX[=13=]6[=13=]2\n[=13=]0R60[=13=]22[=13=]20[=13=]3X[=13=]4[=13=]6[=13=]2\n[=13=]0"}, d2 = {"LSubject;", "", "name", "", "e", "LElement;", "(Ljava/lang/String;LElement;)V", "e2", "getE2", "()LElement;", "e3", "getE3", "field1", ""})
public final class Subject {
@NotNull
private final String name;
private final int field1;
@NotNull
private final Element e2;
@NotNull
private final Element e3;
public Subject(@NotNull String name, @NotNull Element e) {
this.name = name;
this.field1 = 1;
System.out
.println(Intrinsics.stringPlus(this.name, ": first init"));
this.e2 = new Element(Intrinsics.stringPlus(this.name, ": second element"));
System.out
.println(Intrinsics.stringPlus(this.name, ": second init"));
this.e3 = new Element(Intrinsics.stringPlus(this.name, ": third element"));
}
@NotNull
public final Element getE2() {
return this.e2;
}
@NotNull
public final Element getE3() {
return this.e3;
}
}
如您所见,所有 init 块都按照它们在 class 主体中出现的顺序成为构造函数的一部分。我注意到一件事与 java 不同。 Class 字段已在构造函数中初始化。 Class 字段和 init 块按照它们在 class 正文中出现的顺序进行初始化。看来顺序在 kotlin 中是如此重要。