Kotlin对象表达式与对象声明、委托、委托属性
一、对象表达式
要创建⼀个继承自某个(或某些)类型的匿名类的对象:
//对象表达式中的代码可以访问来自包含它的作用域的变量
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})// ……
}
如果超类型有⼀个构造函数,则必须传递适当的构造函数参数给它。多个超类型可以由跟在冒号后面的逗号分隔的列表指定:
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*……*/ }
val ab: A = object : A(1), B {
override val y = 15
}
如果我们只需要“⼀个对象而已”,并不需要特殊超类型,那么我们可以简单地写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print (adHoc.x + adHoc.y)
}
匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any ,所以在匿名对象中添加的成员将无法访问。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
二、对象声明
总是在 object 关键字后跟⼀个名称。对象声明的初始化过程是线程安全的并且在首次访问时进行。
对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
1.伴生对象
- companion 关键字标记
- 伴生对象的成员可通过只使用类名作为限定符来调用
- 省略伴生对象的名称,在这种情况下将使用名称 Companion
- 其类的名称可用作对该类的伴生对象的引用
- 伴生对象的成员是真实对象的实例成员,还可实现接口
- 使用@JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
val companionClassA = MyClass
val companionClassB = MyClass.Companion
2.对象表达式和对象声明之间的语义差异
- 对象表达式是在使用他们的地方立即执行(及初始化)的
- 对象声明是在第⼀次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
三、类型别名
类型别名为现有类型提供替代名称。如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。类型别名不会引入新类型。它们等效于相应的底层类型
//缩减集合类型:
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
//函数类型提供另外的别名:
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean
//嵌套类和内部类创建新名称:
class A {
class Inner
}
class B {
inner class Inner
}
typealias AInner = A.Inner
typealias BInner = B.Inner
fun foo(p: Predicate<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // 输出 "true"
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // 输出 "[1]"
}
四、委托
1.由委托实现
委托模式已经证明是实现继承的⼀个很好的替代方式,而 Kotlin 可以零样板代码地原生支持它。Derived 类 可以通过将其所有公有成员都委托给指定对象来实现⼀个接口Base:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived (b).print()
}
Derived 的超类型列表中的 by-子句表式 b 将会在 Derived 中内部存储,并且编译器将生成转发给 b 的所有 Base 的方法。
2.覆盖由委托实现的接口成员
覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员
interface BaseA {
val message: String
fun print()
}
class BaseImpl(x: Int) : BaseA {
override val message = "BaseImpl: x = $x"
override fun print() {
println(message)
}
}
class DerivedA(b: BaseA) : BaseA by b {
// 在 b 的 `print` 实现中不会访问到这个属性
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = DerivedA(b)
derived.print()
println(derived.message)
}
3.委托属性
有⼀些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是如果能够为大家把他们只实现⼀次并放入⼀个库会更好。例如:
- 延迟属性(lazy properties):其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在⼀个映射(map)中,而不是每个存在单独的字段中。
语法是:val/var <属性名>: <类型> by <表达式> 。在 by 后面的表达式是该委托,因为属性对应的 get()(与 set() )会被委托给它的 getValue() 与 setValue() 方法。属性的委托不必实现任何的接口,但是需要提供⼀个 getValue() 函数(与 setValue() ——对于 var 属性)
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
fun main() {
val e = Example()
println(e.p)
e.p = "NEW"
}
输出:
Example@33a17727, thank you for delegating ‘p’ to me!
NEW has been assigned to ‘p’ in Example@33a17727.
4.标准委托:延迟属性 Lazy
lazy() 是接受⼀个 lambda 并返回⼀个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第⼀次调⽤ get() 会执⾏已传递给 lazy() 的 lambda 表达式并记录结果,后续调⽤ get() 只是返回记 录的结果。
val lazyValue: String by lazy {
println("MainActivity computed!")
return@lazy "Hello" //return@lazy 可以省略
}
fun main() {
println(lazyValue)
println(lazyValue)
}
lazy 属性的求值是同步锁的(synchronized):该值只在⼀个线程中计算,并且所有线程会看到相同的值。
LazyThreadSafetyMode.PUBLICATION设置多个线程可以同步初始化委托。如果确定初始化将总是发生在与属性使用位于相同的线程,可以使用LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。
5.标准委托:可观察属性 Observable
Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。每当我们给属性赋值时会 调⽤该处理程序(在赋值后执⾏)。它有三个参数:被赋值的属性、旧值与新值:
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"
}
截获赋值并“否决”它们,那么使用 vetoable() 取代 observable() 。在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。
6.委托给另⼀个属性
⼀个属性可以把它的 getter 与 setter 委托给另⼀个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。该委托属性可以为:
- 顶层属性
- 同⼀个类的成员或扩展属性
- 另⼀个类的成员或扩展属性
为将⼀个属性委托给另⼀个属性,应在委托名称中使用恰当的 :: 限定符
class ClassWithDelegate {
val anotherClassInt = 0
}
var topLevelInt: Int = 0
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
当想要以⼀种向后兼容的方式重命名⼀个属性的时候:引入⼀个新的属性、使用 @Deprecated 注解来注解旧的属性、并委托其实现。
class MyCla {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyCla() // 通知:'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) //42
}
7.将属性储存在映射中
⼀个常见的用例是在⼀个映射(map)里存储属性的值。这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "John Doe", "age" to 25))
fun main() {
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
}
//var属性,需要把只读的 Map 换成 MutableMap
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
8.局部委托属性
可以将局部变量声明为委托属性,如by lazy⼀个局部变量惰性初始化。
9.属性委托要求
对于⼀个只读属性(即 val 声明的),委托必须提供⼀个操作符函数 getValue() ,该函数具有以下参数:
- thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
- property —— 必须是类型 KProperty<*> 或其超类型。
getValue() 必须返回与属性相同的类型(或其子类型)。
对于⼀个可变属性(即 var 声明的),委托必须额外提供⼀个操作符函数 setValue() ,该函数具有以下参数:
- thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
- property —— 必须是类型 KProperty<*> 或其超类型。
- value — 必须与属性类型相同(或者是其超类型)。
getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。两函数都需要用 operator 关键字来进行标记。
翻译规则
在每个委托属性的实现的背后,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)
}
编译器在参数中提供了关于 prop 的所有必要信息:第⼀个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身
提供委托
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。如果 by 右侧所使⽤的对 象将 provideDelegate 定义为成员或扩展函数,那么会调⽤该函数来创建属性委托实例。
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)
// 创建委托
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)
}
provideDelegate 的参数与 getValue 相同:
- thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
- property —— 必须是类型 KProperty<*> 或其超类型。
在创建 MyUI 实例期间,为每个属性调用provideDelegate 方法,并立即执行必要的验证