Kotlin中新引入的语法——委托。委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。 比如调用A类的methodA方法,其实背后是B类的methodB去执行。

Kotlin将委托功能分为了两种:类委托和委托属性。

类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。 比如Set,它所存储的数据是无序的,并且不能存储重复的数据。Set是一个接口,如果要使用它的话,需要使用它具体的实现类,比如HashSet。而借助于委托模式,可以轻松实现一个自己的实现类。比如这里定义 一个MySet,并让它实现Set接口,代码如下所示:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {override val size: Intget() = helperSet.sizeoverride fun contains(element: T) = helperSet.contains(element)override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)override fun isEmpty() = helperSet.isEmpty()override fun iterator() = helperSet.iterator()}

可以看到,MySet的构造函数中接收了一个HashSet参数,这就相当于一个辅助对象。然后在Set接口所有的方法实现中,都没有进行自己的实现,而是调用了辅助对象中相应的方法实现,这其实就是一种委托模式。

那么,既然都是调用辅助对象的方法实现,那还不如直接使用辅助对象。这么说确实没错,但如果我们只是让大部分的方法实现调用辅助对象中的方法,少部分的方法实现由自己来重写,甚至加入一些自己独有的方法,那么MySet就会成为一个全新的数据结构类,这就是委托模式的意义所在。

但是这种写法也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个方法的话,每个都去这样调用辅助对象中的相应方法实现,那可真是要写哭了。那么这个问题 有没有什么解决方案呢?在Java中确实没有,但是在Kotlin中可以通过类委托的功能来解决。

Kotlin中委托使用的关键字是by,只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了, 如下所示:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by heplerSet {}

这两段代码实现的效果是一模一样的,但是借助了类委托的功能之后,代码明显简化了太多。另外,如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方法仍然可以享受类委托所带来的便利,如下所示:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {fun helloWorld() = println("Hello World")override fun isEmpty() = false
}

这里新增了一个helloWorld()方法,并且重写了isEmpty()方法,让它永远返回false。这当然是一种错误的做法,这里仅仅是为了演示一下而已。现在MySet就成为了一个全新的数据结构类,它不仅永远不会为空,而且还能打印helloWorld(),至于其它Set接口中的功能,则和HashSet保持一致。这就是Kotlin的类委托所能实现的功能。

类委托的核心思想是将一个类的具体实现委托给另一个类去完成,而委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。 委托属性的语法结构,如下所示:

class MyClass {var p by Delegate()
}

可以看到,这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

因此,还得对Delegate类进行具体的实现才行,代码如下所示:

class Delegate {var propValue: Any? = nulloperator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {return propValue}operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {propValue = value}
}

这是一种标准的代码实现模板,在Delegate类中我们必须实现getValue()setValue()这 两个方法,并且都要使用operator关键字进行声明。

getValue()方法要接收两个参数:第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里写成MyClass表示仅可在MyClass类中使用;第二个参数KProperty<*>Kotlin中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在方法参数上进行声明。另外,<*>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java<?>的写法。至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行了,上述代码只是一种示例写法。

setValue()方法也是相似的,只不过它要接收3个参数。前两个参数和getValue()方法是相 同的,最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致。

整个委托属性的工作流程就是这样实现的,现在当我们给MyClassp属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClassp属性的值时,就会调用Delegate类的getValue()方法。

不过,其实还存在一种情况可以不用在Delegate类中实现setValue()方法,那就是MyClass中的p属性是使用val关键字声明的。这一点也很好理解,如果p属性是使用val关键字声明的,那么就意味着p属性是无法在初始化之后被重新赋值的,因此也就没有必要实现 setValue()方法,只需要实现getValue()方法就可以了。

Kotlin通过by关键字就可以实现委托的效果,比如的by lazy { },其实就是利用委托实现的延迟初始化语法。 以下是它的使用:

val laziness: String by lazy { // by lazy实现延迟初化效果println("I will hava a value")"I am a lazy-initialized string"
}

这里使用了一种懒加载技术,把想要延迟执行的代码放到by lazy代码块中,这样代码块中的代码在一开始的时候就不会执行,只有当laziness变量首次被调用的时候,代码块中的代码才会执行。

学习了Kotlin的委托功能之后,就可以对by lazy的工作原理进行解密了,它的基本语法结构如下:

val p by lazy { ... }

by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已。lazy函数中会创建并返回一个Delegate对象,当我们调用p属性的时候,其实调用的是Delegate对象的getValue()方法,然后getValue()方法中又会调用lazy函数传入的Lambda表达式,这样表达式中的代码就可以得到执行了,并且调用p属性后得到的值就是Lambda表达式中最后一行代码的返回值。

这样看来,Kotlin的懒加载技术也并没有那么神秘,掌握了它的实现原理之后,我们也可以实现 一个自己的lazy函数。新建一个Later.kt文件,并编写如下代码:

class Later<T>(val block: () -> T) { }

首先定义了一个Later类,并将它指定成泛型类。Later的构造函数中接收一个函数类型参数,这个函数类型参数不接收任何参数,并且返回值类型就是Later类指定的泛型。

接着在Later类中实现getValue()方法,代码如下所示:

class Later<T>(val block: () -> T) {var value: Any? = nulloperator fun getValue(any: Any?, prop: KProperty<*>): T {if (value == null) {value = block()}return value as T}
}

这里将getValue()方法的第一个参数指定成了Any?类型,表示希望Later的委托功能在所有类中都可以使用。然后使用了一个value变量对值进行缓存,如果value为空就调用构造函数中传入的函数类型参数去获取值,否则就直接返回。由于懒加载技术是不会对属性进行赋值的,因此这里就不用实现setValue()方法了。

代码写到这里,委托属性的功能就已经完成了。虽然我们可以立刻使用它,不过为了让它的用法更加类似于lazy函数,最好再定义一个顶层函数。这个函数直接写在Later.kt文件中就可以了,但是要定义在Later类的外面,因为只有不定义在任何类当中的函数才是顶层函数。代码 如下所示:

fun <T> later(block: () -> T) = Later(block)

我们将这个顶层函数也定义成了泛型函数,并且它也接收一个函数类型参数。这个顶层函数的 作用很简单:创建Later类的实例,并将接收的函数类型参数传给Later类的构造函数。

现在,我们自己编写的later懒加载函数就已经完成了,你可以直接使用它来替代之前的lazy函数,如下所示:

val uriMatcher by later {val matcher = UriMatcher(UriMatcher.NO_MATCH)matcher.addURI(authority, "book", bookDir)matcher.addURI(authority, "book/#", bookItem)matcher.addURI(authority, "category", categoryDir)matcher.addURI(authority, "category/#", categoryItem)matcher
}

但是如何才能验证later函数的懒加载功能有没有生效呢?这里我有一个非常简单方便的验证 方法,写法如下:

val p by later {Log.d("TAG", "run codes inside later block")"test later"
}

可以看到,我们在later函数的代码块中打印了一行日志。将这段代码放到任何一Activity中,并在按钮的点击事件里调用p属性。

你会发现,当Activity启动的时候,later函数中的那行日志是不会打印的。只有当你首次点击按钮的时候,日志才会打印出来,说明代码块中的代码成功执行了。而当你再次点击按钮的时候,日志也不会再打印出来,因为代码块中的代码只会执行一次。

通过这种方式就可以验证懒加载功能到底有没有生效了,你可以自己测试一下。

另外,必须说明的是,虽然我们编写了一个自己的懒加载函数,但由于简单起见,这里只是大 致还原了lazy函数的基本实现原理,在一些诸如同步、空值处理等方面并没有实现得很严谨。 因此,在正式的项目中,使用Kotlin内置的lazy函数才是最佳的选择。

通过委托可以代替多继承实现需求:

interface CanFly {fun fly()
}interface CanEat {fun eat()
}open class Flyer : CanFly {override fun fly() {println("I can fly")}
}open class Animal : CanEat {override fun eat() {println("I can eat")}
}class Bird(flyer: Flyer, animal: Animal) : CanFly by flyer, CanEat by animal { }fun main() {val flyer = Flyer()val animal = Animal()val b = Bird(flyer, animal)b.fly()b.eat()
}

有人可能会有疑问:首先,委托方式怎么跟接口实现多继承如此相似,而且好像也并没有简单多少,其次,这种方式好像跟组合也很像,那么它到底有什么优势呢?主要有以下两点:

  • 前面说到接口是无状态的,所以即使它提供了默认方法实现也是很简单的,不能实现复杂的逻辑,也不推荐在接口中实现复杂的方法逻辑。我们可以利用上面委托的这种方式,虽然它也是接口委托,但它是用一个具体的类去实现方法逻辑,可以拥有更强大的能力;
  • 假设我们需要继承的类是A,委托对象是BC、我们在具体调用的时候并不是像组合一样A.B.method,而是可以直接调用A.method,这更能表达A拥有该method的能力, 更加直观,虽然背后也是通过委托对象来执行具体的方法逻辑的;

Kotlin中的委托相关推荐

  1. 一文彻底搞懂 Kotlin 中的委托 | 开发者说·DTalk

    本文原作者: 码农西哥,原文发布于微信公众号: 技术最TOP  https://mp.weixin.qq.com/s/BD1zT80IADDZS4CAxmooPg 什么是委托? 委托,也就是委托模式, ...

  2. [译]带你揭开Kotlin中属性代理和懒加载语法糖衣

    翻译说明: 原标题: How Kotlin's delegated properties and lazy-initialization work 原文地址: https://medium.com/t ...

  3. kotlin中继承父属性使用构造方法

    kotlin中继承父属性使用构造方法 1. 定义父类,给它两个属性: abstract class AbstractResponseMessage {private var success = tru ...

  4. Kotlin 中 switch 写法

    在Kotlin 中并没有switch 操作符 , 取而代之的是when java的写法: int i= 5;switch (i){case 5:System.out.print("5&quo ...

  5. C#中的委托和事件(续)

    引言 如果你看过了 C#中的委托和事件 一文,我想你对委托和事件已经有了一个基本的认识.但那些远不是委托和事件的全部内容,还有很多的地方没有涉及.本文将讨论委托和事件一些更为细节的问题,包括一些大家常 ...

  6. kotlin中的异常处理_如何使用assertFailsWith在Kotlin中测试异常

    kotlin中的异常处理 by Daniel Newton 丹尼尔·牛顿 如何使用assertFailsWith在Kotlin中测试异常 (How to test exceptions in Kotl ...

  7. C# 中的委托和事件

    引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...

  8. C# 中的委托和事件(1)

    C# 中的委托和事件 欢迎浏览本文的后续文章: C#中的委托和事件(续) PDF 浏览:http://www.tracefact.net/Document/Delegates-and-Events-i ...

  9. Kotlin中使用简洁明了的代码替换findViewByid

    第一种(推荐使用) Kotlin Android 扩展插件(Android Studio 内置) 首先添加 apply plugin: 'kotlin-android-extensions' 官方示例 ...

最新文章

  1. 自动驾驶感知中的深度学习
  2. ORA-03113: end-of-file on communication channel Process ID: 252 Session ID: 1 Serial number: 3
  3. 2003白金一代NBA选秀
  4. linux rmp命令安装包在哪里_rpm命令_Linux rpm 命令用法详解:RPM软件包的管理工具...
  5. Apache Camel 2.16发布–十大亮点
  6. Javamysql语法转化oracle_MySQL与Oracle的语法区别详细对比
  7. 基础编程题之奇数位(偶数位)都是奇数(偶数)
  8. hover事件注册实例一枚
  9. python对Excel数据进行读写操作
  10. 计算机工程与网络学术会议怎么样,第七届计算机工程与网络国际会议(CENet2017)确保EI快速检索!...
  11. mysql8.0.12怎么用_Mysql8.0.12安装教程方法 Mysql8.0.12安装教程
  12. python怎么返回上一行代码_如何返回循环Python中的第一行代码
  13. 纯前端实现—用户注册登录界面
  14. 软件测试薪资高不高?软件测试工资水平调研公布
  15. pandas学习task05变形
  16. 三维空间坐标的旋转算法详解_三维空间几何坐标变换矩阵.ppt
  17. Codeup100000609问题 A: Jugs
  18. EDIUS和Premiere两款视频剪辑软件哪个好
  19. 第7章第31节:四图排版:四张图片两两一组并行排列 [PowerPoint精美幻灯片实战教程]
  20. uva 10859 放置街灯树形dp

热门文章

  1. 原装苹果手机_二手原装正品苹果手机及平板批发报价单353
  2. 【强烈推荐】网络安全10本入门必看书籍
  3. 工程院院士李德毅:数据挖掘就是云环境下的搜索服务
  4. python中timesleep什么意思_Python中的time.sleep()
  5. MFC使用sleep函数注意事项
  6. 波兰最大的电商平台Allegro绑定连连跨境支付收款教程!
  7. android log机制 输出log,Android log 机制 - logd 如何接收 log 数据(下)
  8. java线程池应用场景面试,含小米、腾讯、阿里
  9. 药房信息管理系统c语言程序,《C语言程序设计》药房管理系统.pdf
  10. 四川大学计算机辅助分析考试,四川大学电力系统计算机辅助分析实验报告.docx...