什么是 Kotlin 后备字段?
What's Kotlin Backing Field For?
作为一名 Java 开发人员,支持字段的概念对我来说有点陌生。鉴于:
class Sample {
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
}
这个支持字段有什么用? Kotlin docs 说:
Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors.
为什么?在 setter 中使用属性名称本身有什么区别,例如 *
class Sample {
var counter = 0
set(value) {
if (value >= 0) this.counter = value // or just counter = value?
}
}
因为,如果您没有 field
关键字,您实际上将无法 set/get get()
或 set(value)
中的值。它使您能够访问自定义访问器中的支持字段。
这是您示例的等效 Java 代码:
class Sample {
private int counter = 0;
public void setCounter(int value) {
if (value >= 0) setCounter(value);
}
public int getCounter() {
return counter;
}
}
显然这不好,因为 setter 只是对自身的无限递归,从不改变任何东西。请记住,在 kotlin 中,每当您编写 foo.bar = value
时,它都会被转换为 setter 调用而不是 PUTFIELD
.
编辑:Java 有 字段 而 Kotlin 有 属性 ,这是比字段更高级别的概念。
有两种类型的属性:一种有支持字段,一种没有。
具有支持字段的 属性 将以字段的形式存储值。该字段使在内存中存储值成为可能。 属性 的一个例子是 Pair
的 first
和 second
属性。 属性 将更改 Pair
.
在内存中的表示
没有支持字段的 属性 将不得不以其他方式存储它们的值,而不是直接将其存储在内存中。它必须根据其他属性或对象本身计算得出。 属性 的一个例子是 List
的 indices
扩展 属性,它没有字段支持,而是基于 size
属性。所以它不会改变 List
的内存表示(它根本不能这样做,因为 Java 是静态类型的)。
支持字段适用于 运行 验证或触发状态更改事件。想一想您向 Java setter/getter 添加代码的次数。支持字段在类似情况下会很有用。当您需要控制或查看 setters/getters.
时,您可以使用支持字段
使用字段名称本身分配字段时,您实际上是在调用 setter(即 set(value)
)。在您的示例中, this.counter = value
将递归到 set(value) 直到我们溢出堆栈。使用 field
绕过 setter(或 getter)代码。
最初,我也很难理解这个概念。所以让我用一个例子向你解释一下。
考虑这个 Kotlin class
class DummyClass {
var size = 0;
var isEmpty
get() = size == 0
set(value) {
size = size * 2
}
}
现在,当我们查看代码时,我们可以看到它有 2 个属性,即 - size
(使用默认访问器)和 isEmpty
(使用自定义访问器)。但它只有 1 个字段,即 size
。要了解它只有 1 个字段,让我们看一下 Java 等效于此 class.
转到工具 -> Kotlin -> 在 Android Studio 中显示 Kotlin 字节码。点击反编译。
public final class DummyClass {
private int size;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.size == 0;
}
public final void setEmpty(boolean value) {
this.size *= 2;
}
}
很明显我们可以看到javaclass对于isEmpty
只有getter和setter两个函数,并且没有为其声明字段.同样,在 Kotlin 中,属性 isEmpty
没有支持字段,因为 属性 根本不依赖于该字段。因此没有支持字段。
现在让我们删除 isEmpty
属性 的自定义 getter 和 setter。
class DummyClass {
var size = 0;
var isEmpty = false
}
而上面 class 的 Java 等价于
public final class DummyClass {
private int size;
private boolean isEmpty;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.isEmpty;
}
public final void setEmpty(boolean var1) {
this.isEmpty = var1;
}
}
这里我们看到字段 size
和 isEmpty
。 isEmpty
是支持字段,因为 isEmpty
属性 的 getter 和 setter 依赖于它。
我的理解是使用 field 标识符作为对 属性 在 get 或 [=18 中的值的引用=]set,当你想在 get 或 set 中更改或使用 属性 的值时。
例如:
class A{
var a:Int=1
get(){return field * 2} // Similiar to Java: public int geta(){return this.a * 2}
set(value) {field = value + 1}
}
然后:
var t = A()
println(t.a) // OUTPUT: 2, equal to Java code: println(t.a * 2)
t.a = 2 // The real action is similar to Java code: t.a = t.a +1
println(t.a) // OUTPUT: 6, equal to Java code: println(t.a * 2)
术语backing field
充满了神秘色彩。使用的关键字是 field
。 get/set
方法,紧跟在即将通过此门保护方法机制get或set的成员变量之后。 field
关键字只是引用要set或get的成员变量。目前Kotlin,你不能直接在get或set保护门方法中引用成员变量,因为它会不幸地导致无限递归,因为它将重新调用 get 或 set 从而导致运行时陷入深渊。
在C#中,你可以直接在getter/setter方法中引用成员变量。我引用这个比较来提出这个 field
关键字是当前 Kotlin 实现它的方式的想法,但我希望它会在以后的版本中被删除,并允许我们直接引用成员变量而不会导致无限递归。
作为一名 Java 开发人员,支持字段的概念对我来说有点陌生。鉴于:
class Sample {
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
}
这个支持字段有什么用? Kotlin docs 说:
Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors.
为什么?在 setter 中使用属性名称本身有什么区别,例如 *
class Sample {
var counter = 0
set(value) {
if (value >= 0) this.counter = value // or just counter = value?
}
}
因为,如果您没有 field
关键字,您实际上将无法 set/get get()
或 set(value)
中的值。它使您能够访问自定义访问器中的支持字段。
这是您示例的等效 Java 代码:
class Sample {
private int counter = 0;
public void setCounter(int value) {
if (value >= 0) setCounter(value);
}
public int getCounter() {
return counter;
}
}
显然这不好,因为 setter 只是对自身的无限递归,从不改变任何东西。请记住,在 kotlin 中,每当您编写 foo.bar = value
时,它都会被转换为 setter 调用而不是 PUTFIELD
.
编辑:Java 有 字段 而 Kotlin 有 属性 ,这是比字段更高级别的概念。
有两种类型的属性:一种有支持字段,一种没有。
具有支持字段的 属性 将以字段的形式存储值。该字段使在内存中存储值成为可能。 属性 的一个例子是 Pair
的 first
和 second
属性。 属性 将更改 Pair
.
没有支持字段的 属性 将不得不以其他方式存储它们的值,而不是直接将其存储在内存中。它必须根据其他属性或对象本身计算得出。 属性 的一个例子是 List
的 indices
扩展 属性,它没有字段支持,而是基于 size
属性。所以它不会改变 List
的内存表示(它根本不能这样做,因为 Java 是静态类型的)。
支持字段适用于 运行 验证或触发状态更改事件。想一想您向 Java setter/getter 添加代码的次数。支持字段在类似情况下会很有用。当您需要控制或查看 setters/getters.
时,您可以使用支持字段使用字段名称本身分配字段时,您实际上是在调用 setter(即 set(value)
)。在您的示例中, this.counter = value
将递归到 set(value) 直到我们溢出堆栈。使用 field
绕过 setter(或 getter)代码。
最初,我也很难理解这个概念。所以让我用一个例子向你解释一下。
考虑这个 Kotlin class
class DummyClass {
var size = 0;
var isEmpty
get() = size == 0
set(value) {
size = size * 2
}
}
现在,当我们查看代码时,我们可以看到它有 2 个属性,即 - size
(使用默认访问器)和 isEmpty
(使用自定义访问器)。但它只有 1 个字段,即 size
。要了解它只有 1 个字段,让我们看一下 Java 等效于此 class.
转到工具 -> Kotlin -> 在 Android Studio 中显示 Kotlin 字节码。点击反编译。
public final class DummyClass {
private int size;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.size == 0;
}
public final void setEmpty(boolean value) {
this.size *= 2;
}
}
很明显我们可以看到javaclass对于isEmpty
只有getter和setter两个函数,并且没有为其声明字段.同样,在 Kotlin 中,属性 isEmpty
没有支持字段,因为 属性 根本不依赖于该字段。因此没有支持字段。
现在让我们删除 isEmpty
属性 的自定义 getter 和 setter。
class DummyClass {
var size = 0;
var isEmpty = false
}
而上面 class 的 Java 等价于
public final class DummyClass {
private int size;
private boolean isEmpty;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.isEmpty;
}
public final void setEmpty(boolean var1) {
this.isEmpty = var1;
}
}
这里我们看到字段 size
和 isEmpty
。 isEmpty
是支持字段,因为 isEmpty
属性 的 getter 和 setter 依赖于它。
我的理解是使用 field 标识符作为对 属性 在 get 或 [=18 中的值的引用=]set,当你想在 get 或 set 中更改或使用 属性 的值时。
例如:
class A{
var a:Int=1
get(){return field * 2} // Similiar to Java: public int geta(){return this.a * 2}
set(value) {field = value + 1}
}
然后:
var t = A()
println(t.a) // OUTPUT: 2, equal to Java code: println(t.a * 2)
t.a = 2 // The real action is similar to Java code: t.a = t.a +1
println(t.a) // OUTPUT: 6, equal to Java code: println(t.a * 2)
术语backing field
充满了神秘色彩。使用的关键字是 field
。 get/set
方法,紧跟在即将通过此门保护方法机制get或set的成员变量之后。 field
关键字只是引用要set或get的成员变量。目前Kotlin,你不能直接在get或set保护门方法中引用成员变量,因为它会不幸地导致无限递归,因为它将重新调用 get 或 set 从而导致运行时陷入深渊。
在C#中,你可以直接在getter/setter方法中引用成员变量。我引用这个比较来提出这个 field
关键字是当前 Kotlin 实现它的方式的想法,但我希望它会在以后的版本中被删除,并允许我们直接引用成员变量而不会导致无限递归。