当你声明一个属性时,Kotlin 如何为你隐式实现 field、getter 和 setter 函数?
Kotlin 中的属性和字段术语有时有点令人困惑,因为从技术上讲,Kotlin 没有字段。你不能声明一个字段。一切都是属性!
但是,为了避免混淆,我更喜欢根据以下内容分别定义字段和属性:
- 字段是类的私有成员变量。内存已分配。
- 属性是公共或受保护的 getter 或 setter 函数,允许您访问私有字段。
我喜欢这样定义,因为它有助于我的理解,也让事情更容易解释。
隐式字段,隐式 Getter/Setter
让我们看看这个例子。name是一个属性。
class Person {
var name = "Vincent"
}
当您像这样声明一个属性时,Kotlin 会为您隐式创建字段、getter 和 setter 函数。
在 Java 反编译的代码中,它看起来像这样:
public final class Person {
@NotNull
private String name = "Vincent";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
}
如您所见,private String name是字段(成员变量)。getName()并且setName()是属性 getter 和 setter 函数(也称为属性访问器)
隐式字段,显式 Getter/Setter
当然,你也可以显式定义属性的getter和setter函数,这样也会生成非常类似的反编译Java代码。
class Person {
var name: String = "Vincent"
get() { return field }
set(value) { field = value }
}
field在这里隐式创建,也称为Backing Fields。为属性提供属性访问器(即get()和set())称为支持属性。
显式字段,显式getter/setter
您也可以显式定义字段。基本上一切都是明确的。
class Person {
private var _name:String = "Vincent"
var name: String
get() { return _name }
set(value) { _name = value }
}
field这里是显式字段,而不是隐_name式字段。
私人设置还是备用属性?
现在,您希望该属性name在类外只读。因此,您可以使用private set.
例如:
class Person {
var name: String = "Vincent"
private set
}
或者您也可以使用支持属性。删除set()并更改var为val。
class Person {
private var _name:String = "Vincent"
val name: String
get() { return _name }
}
这两个代码都生成与以下相同的反编译Java代码。请注意,该setName()功能已删除。
public final class Person {
@NotNull
private String name = "Vincent";
@NotNull
public final String getName() {
return this.name;
}
}
我个人更喜欢private set,因为它的代码较少。
滥用私有集
但是等等,没那么快。如果您转换以下支持属性怎么办
class MainViewModel: ViewModel() {
private val _state: MutableState<Int?> = mutableStateOf(null)
val state: State<Int?> = _state
/*...*/
}
至private set
class MainViewModel: ViewModel() {
var state: MutableState<Int?> = mutableStateOf(null)
private set
}
这是滥用private set. 它真正的意思是你不能state在类外分配一个新变量MainViewModel。state变量本身仍然是可变的(这意味着您可以修改它的值)。
上面的支持属性仅揭露读取State,将其更改为private set击败其原始目的。因此,在这种情况下,您不使用private set。这适用于任何可变数据。
结论
我认为在这里了解字段和属性概念很重要。
当您声明一个属性时,它不会分配新内存,因为它只是一个 getter 或 setter 函数。但是,如果推断隐式字段实现(如上面的代码示例),则是的,它占用内存分配。
最后,不要将每个支持属性转换为private set。您不应该这样做,尤其是您的数据是可变的。