Kotlin 类中的 init 块位置
Posted
技术标签:
【中文标题】Kotlin 类中的 init 块位置【英文标题】:init block position in class in Kotlin 【发布时间】:2018-05-06 23:13:22 【问题描述】:我最近遇到了一种情况,即使我使用 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 块的位置很重要。如果我将 init 块放在类的最后,它首先用默认值初始化名称,然后调用 function() 将值替换为“新名称”。
如果我把它放在第一位,它没有找到名称,并且在初始化属性时被“默认名称”替换。
这对我来说很奇怪。谁能解释为什么会这样?
【问题讨论】:
类初始化是自上而下的,初始化块没有被豁免。 这个练习仅仅是为了理解 Kotlin 初始化吗?如果没有,您可以通过class Example(val name: String = "default name")
实现此目的
我试图在我的库 (github.com/kirtan403/k4kotlin) 中执行此操作,但在将其放在顶部之后,整个功能都崩溃了。
签出这个???? init-blocks kotlin vs Java我解释了什么是init块以及它的调用顺序是如何初始化块和全局变量
【参考方案1】:
原因是kotlin遵循从上到下的方法
根据文档 (An in-depth look at Kotlin’s initializers) 初始化程序(属性初始化程序和初始化块)按照它们在类中定义的顺序执行,从上到下。
您可以定义多个辅助构造函数,但在创建类实例时只会调用一个,除非该构造函数显式调用另一个。
构造函数也可以有默认参数值,每次调用构造函数时都会计算这些值。与属性初始化器一样,它们可以是函数调用或其他将运行任意代码的表达式。
初始化程序在类的主构造函数的开头从上到下运行。
这是正确的方法
class Example(function: Example.() -> Unit)
var name = "default name"
init
function()
【讨论】:
在大多数情况下将 init 块放在底部是什么意思?除非你有一些特殊的需求......【参考方案2】:如 Kotlin 文档中所述:
在实例初始化期间,初始化程序块按照它们出现在类主体中的相同顺序执行,并与属性交错初始化器:...
https://kotlinlang.org/docs/classes.html#constructors
【讨论】:
【参考方案3】:Java 构造函数只是一个在对象创建后运行的方法。在运行构造函数之前,所有的类字段都会被初始化。
在 Kotlin 中有两种类型的构造函数,即主构造函数和次构造函数。我将主构造函数视为支持内置字段封装的常规 Java 构造函数。编译后,如果声明对整个类可见,主构造函数字段将放在类的顶部。
在 java 或 kotlin 中,构造函数在初始化类字段后被调用。但是在主构造函数中我们不能写任何语句。如果我们要编写在对象创建后需要执行的语句,我们必须将它们放在初始化块中。但是 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 块都被执行。这是因为 init 块按照它们在类体中出现的顺序成为构造函数的一部分。
让我们将 kotlin 代码编译为 java 字节码并将其反编译回 java。我使用jd-gui
来反编译java 类。您可以在基于 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 = "\000\034\n\002\030\002\n\002\020\000\n\000\n\002\020\016\n\000\n\002\030\002\n\002\b\007\n\002\020\b\030\0002\0020\001B\027\022\006\020\002\032\0020\003\022\b\b\002\020\004\032\0020\005\006\002\020\006R\021\020\007\032\0020\005\006\b\n\000\032\004\b\b\020\tR\021\020\n\032\0020\005\006\b\n\000\032\004\b\013\020\tR\016\020\f\032\0020\rX\006\002\n\000R\016\020\002\032\0020\003X\004\006\002\n\000", 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 块都按照它们在类主体中出现的顺序成为构造函数的一部分。我注意到与java不同的一件事。类字段在构造函数中初始化。类字段和初始化块按照它们在类主体中出现的顺序进行初始化。似乎顺序在 kotlin 中非常重要。
【讨论】:
以上是关于Kotlin 类中的 init 块位置的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin类的初始化 ③ ( init 初始化块 | 初始化顺序 : 主构造函数属性赋值 -> 类属性赋值 -> init 初始化块代码 -> 次构造函数代码 )