Kotlin属性与字段
Kotlin属性与字段
1、声明属性
Kotlin类中的属性可以使用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"
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
使用属性,只需要通过名字引用它:
fun copyAddress(address: Address): Address {
val result = Address()
result.name = address.name
result.street = address.street
return result
}
- 1
- 2
- 3
- 4
- 5
- 6
2、Getters和Setters方法
定义一个属性的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
- 1
- 2
- 3
initializer、getter、setter都是可选的。如果可以从initializer推断出来(或从getter返回的类型),那么PropertyType也是可省略的。
var allByDefault: Int? // 报错,在Kotlin类里必须对属性初始化,要求显示的initializer,默认 getter and setter
var initialized = 1 // 类型为Int,默认getter和setter
- 1
- 2
只读属性声明的完整语法不同于可变属性的两个方面,只读属性用val
开始而不是var
,并且不允许有setter
:
val simple: Int? // Int类型,默认getter,必须在构造函数中初始化
val inferredType = 1 // Int类型,默认getter
- 1
- 2
我们可以定义一个自定义的属性访问器。如果我们定义了一个自定义getter
,每一个次我们访问 这个属性时,它都会被调用。这允许我们实现一个计算属性。
val isEmpty: Boolean
get() = this.size == 0
- 1
- 2
如果我们定义了一个自定义setter
,每一次我们给属性赋值时,它都会被调用。
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
- 1
- 2
- 3
- 4
- 5
为了方便,setter
参数的名字是value
,但你可以选择一个不同的名字,如果你想的话。从Kotlin1.1开始,你可以省略属性类型,如果它可以从getter
方法推断出来,否则,就不能省略属性类型。
val isEmpty get() = this.size == 0 // Boolean
- 1
如果你需要改访问器的可见性或注解它,但不需要改变默认的实现,你可以定义一个访问器,而无需定义它的主体。
var setterVisibility: String = "abc"
private set // setter的可见性改成了private,它的默认实现没有改变
var setterWithAnnotation: Any? = null
@Inject set // 使用@Inject注解setter,它的默认实现没有改变
- 1
- 2
- 3
- 4
3、返回字段
字段不能直接在Kotlin类里面声明。然而,当一个属性需要一个返回的字段,Kotlin会自动提供给它。返回字段可以用field
标识符在访问器中引用它。
var counter = 0 // 初始化器直接赋值返回字段
set(value) {
if (value >= 0) field = value
}
- 1
- 2
- 3
- 4
field
标识符只能被用在属性的访问器中。如果属性使用到少一个访问器的默认实现,或一个自定义访问器通过field
标识符引用它,那么编译器将会为属性生成一个返回字段。
下面的例子是没有返回字段的:
val isEmpty: Boolean
get() = this.size == 0
- 1
- 2
4、返回属性
如果你想做些Kotlin提供的“隐式返回字段”方案不适合做的事,那么你可以用一个返回属性来解决。
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) { _table = HashMap() // 引用类型参数
}
return _table ?: throw AssertionError("Set to null by another thread")
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在JVM中,用默认getter
和setter
访问private
属性会被优化,所以在这种情况下,没有引入函数调用开销。
5、编译时常量
如果在编译时只读属性的值是已知的,那么使用const
修饰符标记它为一个编译时常量。这样的属性需要满足以下要求:
- 对象声明或伴生对象的成员或顶层属性
- 用
String
类型或原生类型的值初始化 - 没有自定义的
getter
访问器
这样的属性可以被用在注解中:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
- 1
- 2
6、延迟初始化的属性和变量
正常情况下,声明为具有非空类型的属性必须在构造函数里初始化。但是,这经常不方便。例如,属性可以通过依赖注入或在单元测试的设置方法里初始化。在这种情况下,你不能在构造函数中提供一个非空的初始化器,但你仍然想要在类的主体时引用这个属性时避免null检查。为了处理这种情况,你可以用lateinit
修饰符标记这个属性。
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
lateinit
修饰符也可以用在类的类体里(不是在主构造函数中,并且只有当属性没有自定义的getter或setter方法时)声明的var
属性。从Kotlin1.2 开始,顶层属性和本地变量也可以使用此修饰符。属性或变量的类型必须是非null,并且不必是原生类型。在一个lateinit
属性初始化之前,访问它会抛一个清楚的标识被访问的属性和它还没有被初始化的特殊的异常。
从Kotlin1.2开始,可以使用.isInitialized
检查一个lateinit var
是否被初始化:
if (foo::bar.isInitialized) {
println(foo.bar)
}
- 1
- 2
- 3
这个检查只对在词法上可访问的属性可用。如以相同的类型或外部类型之一或在同一个文件的顶层的声明。
7、重写属性
8、委托属性
最常见的一类属性就是从返回字段读取(或写入)。另外,用自定义getter
和setter
可以实现一个属性的任何行为。
还有一类属性,虽然,每次我们需要它们时,我们可以手动实现他们。如果可以只需要实现一次,那么将会很不错。我们将这些实现放入库中,到时直接就可以使用了,不需要再实现他们了。这样的例子有:
- 懒属性:只有第一次访问时才计算该值
- 可观察的属性:通知监听器关于属性的变化
- map中的存储属性,而不是为每一个属性单独存一个字段
为了涵盖这些情况,Kotlin提供了delegated property
委托属性:
class Example {
var p: String by Delegate()
}
- 1
- 2
- 3
语法:val/var <property_name>: <Type> by <expression>
expression就是这个委托。因为对应属性的get()
(set()
)会委托给它的getValue()
和setValue()
方法。属性委托不必实现任何接口,但它们一定要提供 getValue()
(和setValue()
——对var),如:
import kotlin.reflect.KProperty
class Delegate { // 第一个参数thisRef就是我正在读取的对象,第二个参数是对我们正在读取的对象的描述。
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
} // 第一个参数thisRef就是我正在写入的对象,第二个参数是对我们正在读取的对象的描述。第三个参数value是要被赋予的值
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
当我们从委托给Delegate实例的p
读取时,Delegate的getValue()
函数就会被调用。
Kotlin标准库为几个常用的属性委托提供了工厂方法:
- Lazy
lazy()
是一个接受一个lambda和返回一个Lazy<T>
实例的函数。这个实例可以作为实现lazy属性的委托。第一次调用get()
会执行传递给lazy()
的lambda表达式,并记住结果。后续对get()
的调用都简单地返回记住了的结果 :
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
默认情况下,lazy属性的评估是同步的,即值的计算只在一个线程中,然后所有的线程都将看到同样的值。如果不要求初始化委托的同步,以至于多个线程可以同时操作它,那么把LazyThreadSafetyMode.PUBLICATION
作为参数传递给lazy()
函数。如果你确定初始化将始终与你使用属性的线程在同一个线程进行,你可以使用LazyThreadSafetyMode.NONE
,它不会产生任何线程安全保证和相关的开销。
-
Observable
Delegates.observable()
接授两个参数:一个是初始值,另一个修改的处理程序。每一次我们分配给属性(在分配完成后),处理程序就会被调用。处理程序有三个参数:分配给的属性、旧值、新值。import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main() { val user = User() user.name = "first" user.name = "second" }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如果你想拦截任务并否决它们,那么请使用
vetoable()
而不是observable()
。传递给vetoable()
的处理程序在新属性值的分配执行之前被调用。 -
Map的存储属性
常见的用例是在map中存储属性值。这常见于诸如解析JSON或做其他“动态”的事。在这种情况里,你可以使用map实例自己作为委托属性的委托。
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map }
- 1
- 2
- 3
- 4
在这个例子中,构造函数需要一个map实例:
val user = User(mapOf( "name" to "John Doe", "age" to 25 ))
- 1
- 2
- 3
- 4
委托的属性需要map里的值(通过字符串的键——属性的名称):
println(user.name) // Prints "John Doe" println(user.age) // Prints 25
- 1
- 2
-
本地委托的属性
你可以声明本地变量作为委托的属性。如你定义本地lazy变量:
fun example(computeFoo:()-> Foo){ val memoizedFoo by lazy(computeFoo) if(someCondition && memoizedFoo.isValid()){ computeFoo.doSomething() } }
- 1
- 2
- 3
- 4
- 5
- 6
memoizedFoo变量只在第一次访问时计算。如果someCondition失败,变量将不会被计算。
属性委托要求:
1、对于只读属性(val),委托必须提供一个用以下参数的运算符函数
getValue()
:-
thisRef
:必须和属性的所有者一样,或是属性的所有者的子类型; -
property
:必须是KProperty<*>
或者它的子类型:class Resource class Owner { val valResource: Resource by ResourceDelegate() } class ResourceDelegate { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return Resource() } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
getValue()
必须返回和属性样的类型或它的子类型。
2、对于可变的属性(var),委托必须额外提供一个用以下参数的运算符函数setValue()
:-
thisRef
:必须和属性的所有者一样,或是属性的所有者的子类型; -
property
:必须是KProperty<*>
或者它的子类型; -
value
:必须是与属性类型相同:class Resource class Owner { var varResource: Resource by ResourceDelegate() } class ResourceDelegate(private var resource: Resource = Resource()) { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return resource } operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) { if (value is Resource) { resource = value } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
getValue()
和/或setValue()
函数可以是委托类的成员函数也可以扩展自函数。当你需要委托一个属性给一个没有提供这些函数的对象时,后者比较方便。但是两者需要用operator
关键字标记。委托类可以实现包含所需要的
operator
方法的ReadOnlyProperty
和ReadWriteProperty
接口中的一个,这两个接口都定义在Kotlin的标准库中:interface ReadOnlyProperty<in R, out T> { operator fun getValue(thisRef: R, property: KProperty<*>): T } interface ReadWriteProperty<in R, T> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
9、Kotlin编译器对委托属性的处理
在背后,Kotlin编译器会为每一个委托属性生成一个辅助的属性并委托给它。如下面这个例子,属性prop
对应生成的辅助属性prop$delegate
,访问器的代码只需要委托给这个附加的属性:
class C {
var prop: Type by MyDelegate()
}
// 上面的代码,编译器会生成下面的代码:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Kotlin编译器在参数中提供所有关于prop
的必要信息:第一个参数this引用外部类 C
的实例,this::prop
是一个描述prop
本身的KProperty
类型的反射对象。
10、提供委托
通过定义provideDelegate
运算符,你可以扩展创建将属性实现委派到的对象的逻辑。如果被用在by
右手边的对象定义了provideDelegate
作为成员或扩展函数,那么创建属性委托实例时函数就会被调用。provideDelegate
可能的一种使用是去检查属性一致性,当属性已创建,不仅仅在它的getter
和setter
如果你想在绑定前检查属性名,你可以这样写:
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(thisRef: MyUI,prop: KProperty<*>):ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
// create delegate
return ResourceDelegate()
}
private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
class MyUI {
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
val image by bindResource(ResourceID.image_id)
val text by bindResource(ResourceID.text_id)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
provideDelegate
的参数与 getValue
一样:
thisRef
:必须与属性所有者的本身或它的超类一样。(对于扩展属性,则是被扩展的类型)property
:必须是KProperty<*>
或它的超类型
在MyUI
实例创建期间,会为每个属性调用provideDelegate
方法,并马上执行必要的校验。如果没有这种能力来拦截属性及其委托之间的绑定,则要实现相同的功能,您必须显式地传递属性名称:
// Checking the property name without "provideDelegate" functionality
class MyUI {
val image by bindResource(ResourceID.image_id, "image")
val text by bindResource(ResourceID.text_id, "text")
}
fun <T> MyUI.bindResource(id: ResourceID<T>,propertyName: String):ReadOnlyProperty<MyUI, T> {
checkProperty(this, propertyName)
// create delegate
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/107428810
- 点赞
- 收藏
- 关注作者
评论(0)