属性
属性是属于类的
var
或val
。
定义属性可以在类内部维护状态。维护状态是创建类的主要原因,而不仅仅是编写一个或多个独立函数。
var
属性可以重新赋值,而 val
属性则不能。每个对象都有自己的属性存储:
// Properties/Cup.kt
class Cup {
var percentFull = 0
}
fun main() {
val c1 = Cup()
c1.percentFull = 50
val c2 = Cup()
c2.percentFull = 100
println(c1.percentFull)
println(c2.percentFull)
}
/* 输出:
50
100
*/
在类内部定义 var
或 val
属性的方式与在函数内部定义它们的方式非常相似。然而,var
或 val
将成为该类的一部分,您必须通过点表示法来引用它,即在对象和属性名称之间放置一个点。您可以在每次引用 percentFull
时看到点表示法的用法。
percentFull
属性表示相应的 Cup
对象的状态。c1.percentFull
和 c2.percentFull
包含不同的值,显示每个对象都有自己的存储。
成员函数可以在其对象内部引用属性,而无需使用点表示法(即限定):
// Properties/Cup2.kt
class Cup2 {
var percentFull = 0
val max = 100
fun add(increase: Int): Int {
percentFull += increase
if (percentFull > max)
percentFull = max
return percentFull
}
}
fun main() {
val cup = Cup2()
cup.add(50)
println(cup.percentFull)
cup.add(70)
println(cup.percentFull)
}
/* 输出:
50
100
*/
add()
成员函数试图将 increase
添加到 percentFull
,但确保它不会超过 100%。
在类外部,您必须限定类的属性和成员函数。
您可以定义顶层属性:
// Properties/TopLevelProperty.kt
val constant = 42
var counter = 0
fun inc() {
counter++
}
定义顶层 val
是安全的,因为它不能被修改。然而,定义可变(var
)的顶层属性被认为是一种反模式。随着程序变得越来越复杂,正确推理共享可变状态变得越来越困难。如果代码库中的每个人都可以访问 var counter
,您无法保证它会正确地发生变化:尽管 inc()
将 counter
增加一,但程序的其他某个部分可能将 counter
减少十,导致出现难以理解的错误。最好将可变状态限制在类内部。在限制可见性中,您将看到如何使其真正隐藏。
说 var
可以更改,而 val
不能更改是一种过于简单化的说法。打个比方,将 house
视为 val
,而位于 house
内部的 sofa
视为 var
。您可以修改 sofa
,因为它是 var
。但是,您不能重新分配 house
,因为它是 val
:
// Properties/ChangingAVal.kt
class House {
var sofa: String = ""
}
fun main() {
val house = House()
house.sofa = "Simple sleeper sofa: $89.00"
println(house.sofa)
house.sofa = "New leather sofa: $3,099.00"
println(house.sofa)
// 不能重新分配 val 到新的 House:
// house = House()
}
/* 输出:
Simple sleeper sofa: $89.00
New leather sofa: $3,099.00
*/
尽管 house
是 val
,但其对象可以修改,因为 class House
中的 sofa
是 var
。将 house
定义为 val
只会防止将其重新分配给新对象。
如果将属性定义为 val
,则不能重新分配它:
// Properties/AnUnchangingVar.kt
class Sofa {
val cover: String = "Loveseat cover"
}
fun main() {
var sofa = Sofa()
// 不允许:
// sofa.cover = "New cover"
// 重新分配一个 var:
sofa = Sofa()
}
尽管 sofa
是 var
,但其对象无法修改,因为 class Sofa
中的 cover
是 val
。然而,sofa
可以重新分配给新对象。
我们谈到了诸如 house
和 sofa
之类的标识符,好像它们是对象一样。实际上,它们是对象的引用。看待这一点的一种方式是观察两个标识符可以引用同一个对象:
// Properties/References.kt
class Kitchen {
var table: String = "Round table"
}
fun main() {
val kitchen1 = Kitchen()
val kitchen2 = kitchen1
println("kitchen1: ${kitchen1.table}")
println("kitchen2: ${kitchen2.table}")
kitchen1.table = "Square table"
println("kitchen1: ${kitchen1.table}")
println("kitchen2: ${kitchen2.table}")
}
/* 输出:
kitchen1: Round table
kitchen2: Round table
kitchen1: Square table
kitchen2: Square table
当 kitchen1
修改 table
时,kitchen2
也会看到修改。kitchen1.table
和 kitchen2.table
显示相同的输出。
请记住,var
和 val
控制的是引用,而不是对象。var
允许您将引
用重新绑定到不同的对象,而 val
则会防止您这样做。
可变性意味着对象可以更改其状态。在上述示例中,class House
和 class Kitchen
定义了可变对象,而 class Sofa
定义了不可变对象。
练习和解答可以在 www.AtomicKotlin.com 找到。