Kotlin委托

Kotlin中有委托,这个C#中也有,不过对于学Java的童鞋来说,这是什么鬼啊,到底是干什么用的… 在委托模式中,当有两个对象参与处理同一个请求是,接受请求的对象将请求委托给另一个对象来处理。委托模式已证明是实现继承的一个很好的替代方式。Kotlin中委托分为类委托和委托属性,Kotlin官方库也封装了一些常用的委托。

类委托

类 Derived 可以继承一个接口 Base,并将其所有共有的方法委托给一个指定的对象,也就是说把类 Derived 因继承而需要实现的方法委托给一个对象,从而不需要在该类内显式的实现:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
}

如果 Derived 类没有写‘by b’,那么就必须重写print方法,否则编译器会报错。

委托属性

语法:val/var <属性名>: <类型> by <表达式> 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性),因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。也可以直接继承 ReadWriteProperty ,实现其中的方法,这样就避免了自己手写可能出现的错误,例如 Kotlin 源码中这样实现判空的委托属性:

public object Delegates {
  public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
}

private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

其中 NotNullVar 继承了 ReadWriteProperty,并实现了他的两个方法,而Delegates.notNull() 属于委托属性。

属性委托要求

对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数(可以继承 ReadOnlyProperty 实现该方法):

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型,
  • property —— 必须是类型 KProperty<*> 或其超类型,

对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数(可以继承 ReadWriteProperty 实现该方法):

  • thisRef —— 同 getValue(),
  • property —— 同 getValue(),
  • new 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)
}

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

标准委托

延迟属性 Lazy

lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}
// 输出:
computed!
Hello
Hello

可观察属性 Observable

当属性值改变的时候运行函数方法。 Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}
输出:
<no name> -> first
first -> second

特殊的observable Vetoable

他让你决定这个值是否需要保存,用于在真正保存之前的一些条件判断。

var positiveNumber = Delegates.vetoable(0) {
    d, old, new ->
    new >= 0
}

NotNull 委托

他会含有一个可空变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出异常。

class App: Application() {
    companion object {
        var instance: App by Delegates.netNull()
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

把属性储存在映射中

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class User(val 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
))

使用场景

下面来总结一下委托使用的场景:

  • 有多个类或属性重复使用同一段代码。
  • 实现继承的代替方式
  • 可以使用标准委托,来实现一些特殊的功能,当然,自己也可以自定义一些
  • 其余的有待小伙伴们一起补充吆~

Vetoable\Not null

作 者:ChanghuiN 原文链接:http://www.hchstudio.cn/article/2017/9855/ 版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

Python正则表达式指南

本文介绍了Python对于正则表达式的支持,包括正则表达式基础以及Python正则表达式标准库的完整介绍及使用示例。本文的内容不包括如何编写高效的正则表达式、如...

37570
来自专栏数据科学与人工智能

【Python环境】12道 Python面试题总结

1、Python是如何进行内存管理的? Python的内存管理主要有三种机制:引用计数机制、垃圾回收机制和内存池机制。 a. 引用计数 当给一个对象分配一个新名...

25850
来自专栏用户2442861的专栏

C++编程思想重点笔记

引申:如何在const成员函数里修改成员 —— 按位和与按成员const 如果我们想要建立一个const成员函数,但仍然想在对象里改变某些数据,这时该怎...

22010
来自专栏Linux驱动

快速排序(详解)

描述: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序...

20290
来自专栏java工会

Java基础第一阶段知识点,招实习的面试官都在问这些

a) 答:Java源文件被编译成字节码的形式,无论在什么系统环境下,只要有java虚

13710
来自专栏CVer

排序算法 | 冒泡排序(含C++/Python代码实现)

排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。排序算法有很多,本文将介绍最经典的排序算法:冒泡排序...

14420
来自专栏技术墨客

JVM与字节码——2进制流字节码解析 原

本位将详细介绍字节码的2进制结构和JVM解析2进制流的规范。规范对字节码有非常严格的结构要求,其结构可以用一个JSON来描述:

10920
来自专栏开发与安全

从零开始学C++之从C到C++(一):const与#define、结构体对齐、函数重载name mangling、new/delete 等

一、bool 类型 逻辑型也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节。 声明方式...

20100
来自专栏Java帮帮-微信公众号-技术文章全总结

第十三天 面向对象-final static 匿名对象内部类包代码块【悟空教程】

15140
来自专栏JetpropelledSnake

Python学习笔记之Python正则表达式指南

正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分...

21310

扫码关注云+社区

领取腾讯云代金券