定义属性
类属性可使用var
和val
定义
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
属性使用
fun copyAddress(address: Address): Address {
val result = Address()
result.name = address.name
result.street = address.street
// ...
return result
}
Getters/setters
完整的定义一个属性的公式如下
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
val <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[]
中的内容都是可选的
var initialized = 1 // 有类型Int, 默认getter和默认setter
val simple: Int? // 有类型Int, 默认getter, 必须在构造函数中初始化
val inferredType = 1 // 类型Int,默认getter
如果定义了getter,则每次访问该属性时都会调用
class Rectangle(val width: Int, val height: Int) {
val area: Int // 属性Int在这里是可选的,因为可以从getter中推断出类型
get() = this.width * this.height
}
val area get() = this.width * this.height
如果自定义了setter
,除属性初始化除外,每次属性赋值时都将调用它
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并把值赋给其他字段
}
如果想要一个var
类型的属性不能在外部被修改,可以使用private
或者@Inject
修饰setter
方法
var setterVisibility: String = "abc"
private set
var setterWithAnnotation: Any? = null
@Inject set // 官方说可以使用这个,但是未找到这个怎么用,说找不到这个注解
Backing fields
我称为隐藏字段
官方介绍是这样说的,让我觉得头大
In Kotlin, a field is only used as a part of a property to hold its value in memory. Fields cannot be declared directly. However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced in the accessors using the field identifier
我理解的意思是:在kotlin
中,字段(field
)并不是类中定义的属性,而是用来存属性值的一个东西。不能手动定义字段,当在访问器中需要用到字段的时候,kotlin
已经为我们自动的定义好了,直接用就行了,字段用field
关键字表示
class Rectangle() {
var test = 4
set(value) {
field = value
}
get() = field
}
实例中,
field
,也就是字段,指向存储4
的位置,而不是test
如果在访问器中不使用field
,而是使用属性本身会怎样
class Rectangle() {
var test = 4
get() = test
}
fun main() {
val rectangle = Rectangle()
println(rectangle.test)
}
idea
提示这是一个递归调用,第13行也标志是一个递归调用。如果执行了代码则会报错Exception in thread "main" java.lang.StackOverflowError
,这是因为在访问器中使用了属性名,而使用属性名会调用访问器,循环递归后内存溢出,所以需要使用field
来代指属性,避免这个问题,就目前看field
更像一个补丁
Backing properties
如果做一些不适合隐藏字段的事情,可以使用隐藏属性
class Rectangle() {
private var _table: Map<String, Int>? = null
val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
}
在JVM上:使用默认
getter
和setter
访问私有属性做了优化,以避免函数调用开销。
编译时常量(Compile-time constants)
如果只读属性的值在编译时已知,则使用const
修饰符将其标记为编译时常量。此类属性需要满足以下要求:
- 它必须是顶级属性,或者是对象或伴生对象的成员。
- 它必须使用
String
或原始类型的值进行初始化 - 它不能是自定义
getter
编译器将内联常量的用法,将对常量的引用替换为其实际值。但是,该字段不会被删除,因此可以使用反射与之交互
/**
* 顶级变量
*/
const val SUBSYSTEM_DEPRECATED: String = "方法过期了"
class Demo() {
@Deprecated(SUBSYSTEM_DEPRECATED) // 注解中可使用const修饰的属性
fun foo() { }
}
延迟初始化属性和变量(Late-initialized properties and variables)
通常,声明为非空类型的属性必须在构造函数中初始化。然而,这样做并不方便。例如,属性可以通过依赖注入进行初始化,或者在单元测试的设置方法中进行初始化。
为了处理这种情况,可以使用lateinit
修饰符标记属性
public class MyTest {
lateinit var subject: TestSubject // 延迟初始化 subject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method()
}
}
可以用在类的主体内声明的var
属性(不在主构造函数中,且没有自定义的getter或setter时),以及顶层属性和局部变量。属性或变量的类型必须是非空的,并且不能是原始类型
在初始化之前访问lateinit
属性会抛出一个特殊的异常,清楚地标识被访问的属性以及它尚未被初始化的事实
检查是否已经初始化
使用.isInitialized
方法检查是否已经初始化
class TestSubject() {
fun method() {}
}
class MyTest {
lateinit var subject: TestSubject
fun test() {
if (this::subject.isInitialized)
subject.method()
}
}
重写属性(Overriding properties)
下章讲解
委托属性(Delegated properties)
后边文章中再讲