在 Kotlin 中,类的属性与 field
关键字的使用是语法设计中的重要特性。以下是详细解析:
一、Kotlin 类的定义
Kotlin 通过 class
关键字定义类,属性默认自动生成 getter/setter
:
kotlin
class Person {
var name: String = "默认值" // 自动生成 getter 和 setter
val age: Int = 0 // 只读属性,仅生成 getter
}
二、field
关键字的作用
field
是 Kotlin 中用于访问属性幕后字段 (backing field)的关键字,仅在属性的 get()
或 set()
自定义逻辑中使用。
1. **隐式使用 field
**
默认情况下,Kotlin 会自动为属性生成幕后字段:
csharp
var counter = 0
// 等价于以下隐式代码:
// get() = field
// set(value) { field = value }
2. **显式使用 field
**
当自定义 get()
或 set()
时,必须通过 field
访问实际存储值:
javascript
var name: String = ""
get() = field.uppercase() // 返回大写值,但不修改存储值
set(value) {
field = value.trim() // 赋值前去除首尾空格
}
三、使用 field
的注意事项
1. 避免递归调用
直接使用属性名会触发 getter/setter
,导致递归:
javascript
// ❌ 错误示例:导致栈溢出
var counter = 0
set(value) {
if (value >= 0) this.counter = value // 递归调用 set()
}
// ✅ 正确写法:使用 field
var counter = 0
set(value) {
if (value >= 0) field = value
}
2. 仅限在 get()
/set()
中使用
field
不可在类其他方法中直接访问:
kotlin
class Demo {
var data = 0
fun update() {
field = 10 // ❌ 编译错误:field 只能在 getter/setter 中使用
}
}
四、无幕后字段的场景
如果属性不依赖幕后字段存储数据(例如通过计算获得值),则无需使用 field
:
kotlin
val isAdult: Boolean
get() = age >= 18 // 直接依赖其他属性,无需 field
// 计算属性 下面这样写 get函数覆盖了 field 内容本身,相当于field失效了,无用了,以后用不到了
var number2 : Int = 0
get() = (1..1000).shuffled().first() // 从1到1000取出随机值 返回给 getNumber2()函数
/*
背后隐式代码:
为什么没有看到 number2 属性定义?
答:因为属于 计算属性 的功能,根本在getNumber2函数里面,就没有用到 number2属性,所以 number2属性 失效了,无用了,以后用不到了
public int getNumber2() {
return (1..1000).shuffled().first()java的随机逻辑 复杂 ;
}
*/
五、与 Java 的对比
特性 | Java | Kotlin |
---|---|---|
字段定义 | 显式声明为 private 字段 |
自动生成幕后字段,通过 field 访问 |
访问控制 | 手动实现 getter/setter |
默认自动生成,可自定义逻辑 |
属性访问 | 直接访问字段或调用方法 | 统一通过属性名访问,隐藏底层实现 |
六、完整示例
ini
class KtBase69 {
var name = "Derry"
get() = field
set(value) {
field = value
}
/* 背后做的事情:
@NotNull
private String name = "Lili";
public void setName( @NotNull String name) {
this.name = name;
}
@NotNull
public String getName() {
return this.name;
}
*/
var value = "ABCDEFG"
// 下面的隐式代码,不写也有,就是下面这个样子
get() = field
set(value) {
field = value
}
var info = "abcdefg ok is success"
get() = field.capitalize() // 把首字母修改成大写
set(value) {
field = "**【$value】**"
}
/* 背后做的事情:
@NotNull
private String info = "abcdefg ok is success";
public void setInfo( @NotNull String info) {
this.info = "**【" + info + "】**";
}
@NotNull
public String getInfo() {
return StringKt.capitalize(this.info)
}
*/
}
class User {
var email: String = ""
set(value) {
field = if (value.contains("@")) value else "invalid"
}
val info: String
get() = "Email: $email"
}
//Kotlin 定义类与field关键
fun main(){
// 背后隐式代码:new KtBase69().setName("wangwu");
val ktBase69 = KtBase69()
ktBase69.name = "wangwu"
println(ktBase69.name)
println("value:${ktBase69.value}")
println("info: ${ktBase69.info}")
val user = User()
user.email = "test" // 自动调用 setter,赋值 "invalid"
println(user.info)
}
七、总结
- **
field
的作用**:在自定义get()
/set()
中访问属性的实际存储值。 - 使用场景:需要控制属性读写逻辑时(如数据校验、格式化)。
- 避免错误 :在自定义访问器中,始终用
field
替代属性名以避免递归。