Effective Kotlin - 属性应代表状态,而非行为

Kotlin 的属性看起来与 Java 的字段相似,但它们有着不同的概念。


//Kotlin property
var name:String?=null

//Java field
String name=null;

即使使用方式相同,Kotlin 的属性也能做更多的事情。

属性声明的完整语法


var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

自定义 Get, Set ( var )


var name: String? = null
    get() = field?.toUpperCase()
    set(value) { if(!value.isNullOrBlank()) { field = value }

自定义 Get ( val )


val fullName: String?
    get() = "$name $surname"

幕后字段 (Backing Fields)

字段无法自定义声明。但是,如果属性需要幕后字段,系统会自动生成。在访问器中,可以通过 field 标识符来引用该幕后字段。


var counter = 0 // 初始化程序直接赋值给幕后字段
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // 错误:堆栈溢出(StackOverflow),使用实际名称 'counter' 会使 setter 递归调用
    }

与函数有何不同?

Kotlin 中的属性表示的是对象的 状态 ,而函数则表示对象的 行为


// 属性
val isEmpty: Boolean
  get() = amount == 0


// 函数
fun isEmpty(): Boolean {
  return amount == 0
}

让我们思考一下。

始终使用属性的方式更好吗?


class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int
    get() = price - discount
}

若要改进上述代码,可以将其修改为如下形式。
因为每次访问时都进行计算是非常低效的。


class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int = price - discount
}

可以在不持有数据的情况下使用该属性。

由于某些原因,即使无法直接使用 Date 类型,也可以利用 Kotlin 的属性功能,不存储 Date 类型的数据,而是利用其他值来使用 Date 类型的值(例如在序列化时,只持有 millis 值,使用时转换为 Date 形式)。


var date: Date
    get() = Date(millis)
    set(value) {
        millis = value.time
    }

属性不仅是简单的字段,还可以用作访问器。


val Context.preferences: SharedPreferences
    get() = PreferenceManager.getDefaultSharedPreferences(this)
val Context.inflater: LayoutInflater
    get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val Context.notificationManager: NotificationManager
    get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

属性功能是万能的吗?

包含循环、递归等特定逻辑或算法部分的 属性 并非正确的用法。


// 不要这样做!
val Tree<Int>.sum: Int
    get() = when (this) {
        is Leaf -> value
        is Node -> left.sum + right.sum
    }

属性(Property)通常仅应用于表示或设置状态。
以下是建议优先使用函数而非属性的示例:

  • 复杂度高于 O(1) 的情况(计算成本较高时)
  • 包含简单操作(如日志记录、元素更新等)之外的业务逻辑时
  • 连续两次调用成员产生不同结果时
  • 如 Int.toDouble() 这样产生重复的习惯表达时
  • 在 Getter 中修改属性状态时

参考资料

AD